From a9279299d97416b6b1fddc10c9dc55e6069b1a8f Mon Sep 17 00:00:00 2001
From: Maxim Orlov <m.orlov@postgrespro.ru>
Date: Fri, 9 Oct 2020 16:57:14 +0300
Subject: [PATCH 01/22] Issue #27: Error with transactional
 pgv_insert/pgv_remove

---
 expected/pg_variables_trans.out | 111 ++++++++++++++++++++++++++++++++
 pg_variables.c                  |  31 ++++++++-
 pg_variables.h                  |   1 +
 sql/pg_variables_trans.sql      |  36 +++++++++++
 4 files changed, 176 insertions(+), 3 deletions(-)

diff --git a/expected/pg_variables_trans.out b/expected/pg_variables_trans.out
index 025cc77..e1a4247 100644
--- a/expected/pg_variables_trans.out
+++ b/expected/pg_variables_trans.out
@@ -2032,3 +2032,114 @@ SELECT pgv_free();
  
 (1 row)
 
+-- Variables should be insertable after pgv_remove
+BEGIN;
+SELECT pgv_insert('test', 'x', ROW (1::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_remove('test', 'x');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'x', ROW (1::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+ROLLBACK;
+SELECT * FROM pgv_list() order by package, name;
+ package | name | is_transactional 
+---------+------+------------------
+(0 rows)
+
+BEGIN;
+SELECT pgv_insert('test', 'x', ROW (1::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_remove('test', 'x');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'x', ROW (1::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+COMMIT;
+SELECT * FROM pgv_list() order by package, name;
+ package | name | is_transactional 
+---------+------+------------------
+ test    | x    | t
+(1 row)
+
+-- Variables should be insertable after pgv_free
+BEGIN;
+SELECT pgv_insert('test', 'y', ROW (1::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (1::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+ROLLBACK;
+SELECT * FROM pgv_list() order by package, name;
+ package | name | is_transactional 
+---------+------+------------------
+ test    | x    | t
+(1 row)
+
+BEGIN;
+SELECT pgv_insert('test', 'y', ROW (1::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (1::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+COMMIT;
+SELECT * FROM pgv_list() order by package, name;
+ package | name | is_transactional 
+---------+------+------------------
+ test    | y    | t
+(1 row)
+
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
diff --git a/pg_variables.c b/pg_variables.c
index 0c6dca2..35f48d1 100644
--- a/pg_variables.c
+++ b/pg_variables.c
@@ -403,7 +403,10 @@ variable_insert(PG_FUNCTION_ARGS)
 		 * record type or if last record has different id.
 		 */
 		tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
-		check_attributes(variable, tupdesc);
+		if (variable->is_deleted)
+			init_record(record, tupdesc, variable);
+		else
+			check_attributes(variable, tupdesc);
 	}
 
 	LastTypeId = tupType;
@@ -901,6 +904,7 @@ remove_variable(PG_FUNCTION_ARGS)
 			createSavepoint(transObject, TRANS_VARIABLE);
 			addToChangesStack(transObject, TRANS_VARIABLE);
 		}
+		variable->is_deleted = true;
 		GetActualState(variable)->is_valid = false;
 		GetPackState(package)->trans_var_num--;
 		if ((GetPackState(package)->trans_var_num + numOfRegVars(package)) == 0)
@@ -909,7 +913,7 @@ remove_variable(PG_FUNCTION_ARGS)
 	else
 		removeObject(&variable->transObject, TRANS_VARIABLE);
 
-	resetVariablesCache(false);
+	resetVariablesCache(true);
 
 	PG_FREE_IF_COPY(package_name, 0);
 	PG_FREE_IF_COPY(var_name, 1);
@@ -945,7 +949,27 @@ remove_package(PG_FUNCTION_ARGS)
 static void
 removePackageInternal(Package *package)
 {
-	TransObject *transObject;
+	TransObject			*transObject;
+	Variable			*variable;
+	HTAB				*htab;
+	HASH_SEQ_STATUS		 vstat;
+	int					 i;
+
+	/* Set all the variables from package is deleted */
+	for (i = 0; i < 2; i++)
+	{
+		if ((htab = pack_htab(package, i)) != NULL)
+		{
+			hash_seq_init(&vstat, htab);
+
+			while ((variable =
+					(Variable *) hash_seq_search(&vstat)) != NULL)
+			{
+				if (GetActualState(variable)->is_valid)
+					variable->is_deleted = true;
+			}
+		}
+	}
 
 	/* All regular variables will be freed */
 	if (package->hctxRegular)
@@ -1596,6 +1620,7 @@ createVariableInternal(Package *package, text *name, Oid typid, bool is_record,
 		variable->package = package;
 		variable->is_record = is_record;
 		variable->is_transactional = is_transactional;
+		variable->is_deleted = false;
 		initObjectHistory(transObject, TRANS_VARIABLE);
 
 		if (!isObjectChangedInCurrentTrans(&package->transObject))
diff --git a/pg_variables.h b/pg_variables.h
index 3e233bd..b08faa6 100644
--- a/pg_variables.h
+++ b/pg_variables.h
@@ -113,6 +113,7 @@ typedef struct Variable
 	 * specified only when creating a variable.
 	 */
 	bool		is_transactional;
+	bool		is_deleted;
 }			Variable;
 
 typedef struct HashRecordKey
diff --git a/sql/pg_variables_trans.sql b/sql/pg_variables_trans.sql
index c4effd9..21a656b 100644
--- a/sql/pg_variables_trans.sql
+++ b/sql/pg_variables_trans.sql
@@ -534,3 +534,39 @@ ROLLBACK;
 SELECT package FROM pgv_stats() ORDER BY package;
 
 SELECT pgv_free();
+
+-- Variables should be insertable after pgv_remove
+BEGIN;
+SELECT pgv_insert('test', 'x', ROW (1::int), TRUE);
+SELECT pgv_remove('test', 'x');
+SELECT pgv_insert('test', 'x', ROW (1::int), TRUE);
+ROLLBACK;
+
+SELECT * FROM pgv_list() order by package, name;
+
+BEGIN;
+SELECT pgv_insert('test', 'x', ROW (1::int), TRUE);
+SELECT pgv_remove('test', 'x');
+SELECT pgv_insert('test', 'x', ROW (1::int), TRUE);
+COMMIT;
+
+SELECT * FROM pgv_list() order by package, name;
+
+-- Variables should be insertable after pgv_free
+BEGIN;
+SELECT pgv_insert('test', 'y', ROW (1::int), TRUE);
+SELECT pgv_free();
+SELECT pgv_insert('test', 'y', ROW (1::int), TRUE);
+ROLLBACK;
+
+SELECT * FROM pgv_list() order by package, name;
+
+BEGIN;
+SELECT pgv_insert('test', 'y', ROW (1::int), TRUE);
+SELECT pgv_free();
+SELECT pgv_insert('test', 'y', ROW (1::int), TRUE);
+COMMIT;
+
+SELECT * FROM pgv_list() order by package, name;
+
+SELECT pgv_free();

From caf9314918f7e44aa41d2e6a895fede239229819 Mon Sep 17 00:00:00 2001
From: Maxim Orlov <m.orlov@postgrespro.ru>
Date: Wed, 14 Oct 2020 16:19:34 +0300
Subject: [PATCH 02/22] Issue #27: Error with transactional
 pgv_insert/pgv_remove - review

---
 expected/pg_variables_trans.out | 16 ++++++++--------
 pg_variables.c                  | 19 +++++++++----------
 sql/pg_variables_trans.sql      | 16 ++++++++--------
 3 files changed, 25 insertions(+), 26 deletions(-)

diff --git a/expected/pg_variables_trans.out b/expected/pg_variables_trans.out
index e1a4247..34c2f85 100644
--- a/expected/pg_variables_trans.out
+++ b/expected/pg_variables_trans.out
@@ -2034,7 +2034,7 @@ SELECT pgv_free();
 
 -- Variables should be insertable after pgv_remove
 BEGIN;
-SELECT pgv_insert('test', 'x', ROW (1::int), TRUE);
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
  pgv_insert 
 ------------
  
@@ -2046,7 +2046,7 @@ SELECT pgv_remove('test', 'x');
  
 (1 row)
 
-SELECT pgv_insert('test', 'x', ROW (1::int), TRUE);
+SELECT pgv_insert('test', 'x', ROW (3::int, 4::int), TRUE);
  pgv_insert 
 ------------
  
@@ -2059,7 +2059,7 @@ SELECT * FROM pgv_list() order by package, name;
 (0 rows)
 
 BEGIN;
-SELECT pgv_insert('test', 'x', ROW (1::int), TRUE);
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
  pgv_insert 
 ------------
  
@@ -2071,7 +2071,7 @@ SELECT pgv_remove('test', 'x');
  
 (1 row)
 
-SELECT pgv_insert('test', 'x', ROW (1::int), TRUE);
+SELECT pgv_insert('test', 'x', ROW (3::int, 4::int), TRUE);
  pgv_insert 
 ------------
  
@@ -2086,7 +2086,7 @@ SELECT * FROM pgv_list() order by package, name;
 
 -- Variables should be insertable after pgv_free
 BEGIN;
-SELECT pgv_insert('test', 'y', ROW (1::int), TRUE);
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), TRUE);
  pgv_insert 
 ------------
  
@@ -2098,7 +2098,7 @@ SELECT pgv_free();
  
 (1 row)
 
-SELECT pgv_insert('test', 'y', ROW (1::int), TRUE);
+SELECT pgv_insert('test', 'y', ROW (3::int, 4::int), TRUE);
  pgv_insert 
 ------------
  
@@ -2112,7 +2112,7 @@ SELECT * FROM pgv_list() order by package, name;
 (1 row)
 
 BEGIN;
-SELECT pgv_insert('test', 'y', ROW (1::int), TRUE);
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), TRUE);
  pgv_insert 
 ------------
  
@@ -2124,7 +2124,7 @@ SELECT pgv_free();
  
 (1 row)
 
-SELECT pgv_insert('test', 'y', ROW (1::int), TRUE);
+SELECT pgv_insert('test', 'y', ROW (3::int, 4::int), TRUE);
  pgv_insert 
 ------------
  
diff --git a/pg_variables.c b/pg_variables.c
index 35f48d1..304bf5d 100644
--- a/pg_variables.c
+++ b/pg_variables.c
@@ -56,7 +56,7 @@ static Variable *getVariableInternal(Package *package, text *name,
 static Variable *createVariableInternal(Package *package, text *name, Oid typid,
 										bool is_record, bool is_transactional);
 static void removePackageInternal(Package *package);
-static void resetVariablesCache(bool with_package);
+static void resetVariablesCache(void);
 
 /* Functions to work with transactional objects */
 static void createSavepoint(TransObject *object, TransObjectType type);
@@ -913,7 +913,7 @@ remove_variable(PG_FUNCTION_ARGS)
 	else
 		removeObject(&variable->transObject, TRANS_VARIABLE);
 
-	resetVariablesCache(true);
+	resetVariablesCache();
 
 	PG_FREE_IF_COPY(package_name, 0);
 	PG_FREE_IF_COPY(var_name, 1);
@@ -940,7 +940,7 @@ remove_package(PG_FUNCTION_ARGS)
 	package = getPackage(package_name, true);
 	removePackageInternal(package);
 
-	resetVariablesCache(true);
+	resetVariablesCache();
 
 	PG_FREE_IF_COPY(package_name, 0);
 	PG_RETURN_VOID();
@@ -955,7 +955,7 @@ removePackageInternal(Package *package)
 	HASH_SEQ_STATUS		 vstat;
 	int					 i;
 
-	/* Set all the variables from package is deleted */
+	/* Mark all the valid variables from package as deleted */
 	for (i = 0; i < 2; i++)
 	{
 		if ((htab = pack_htab(package, i)) != NULL)
@@ -1007,11 +1007,10 @@ isPackageEmpty(Package *package)
  * of some changes: removing, rollbacking, etc.
  */
 static void
-resetVariablesCache(bool with_package)
+resetVariablesCache(void)
 {
 	/* Remove package and variable from cache */
-	if (with_package)
-		LastPackage = NULL;
+	LastPackage = NULL;
 	LastVariable = NULL;
 	LastTypeId = InvalidOid;
 }
@@ -1037,7 +1036,7 @@ remove_packages(PG_FUNCTION_ARGS)
 		removePackageInternal(package);
 	}
 
-	resetVariablesCache(true);
+	resetVariablesCache();
 
 	PG_RETURN_VOID();
 }
@@ -1753,7 +1752,7 @@ removeObject(TransObject *object, TransObjectType type)
 		GetActualState(&package->transObject)->is_valid = false;
 	}
 
-	resetVariablesCache(true);
+	resetVariablesCache();
 }
 
 /*
@@ -2121,7 +2120,7 @@ processChanges(Action action)
 		MemoryContextDelete(ModuleContext);
 		packagesHash = NULL;
 		ModuleContext = NULL;
-		resetVariablesCache(true);
+		resetVariablesCache();
 		changesStack = NULL;
 		changesStackContext = NULL;
 	}
diff --git a/sql/pg_variables_trans.sql b/sql/pg_variables_trans.sql
index 21a656b..a5784c0 100644
--- a/sql/pg_variables_trans.sql
+++ b/sql/pg_variables_trans.sql
@@ -537,34 +537,34 @@ SELECT pgv_free();
 
 -- Variables should be insertable after pgv_remove
 BEGIN;
-SELECT pgv_insert('test', 'x', ROW (1::int), TRUE);
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
 SELECT pgv_remove('test', 'x');
-SELECT pgv_insert('test', 'x', ROW (1::int), TRUE);
+SELECT pgv_insert('test', 'x', ROW (3::int, 4::int), TRUE);
 ROLLBACK;
 
 SELECT * FROM pgv_list() order by package, name;
 
 BEGIN;
-SELECT pgv_insert('test', 'x', ROW (1::int), TRUE);
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
 SELECT pgv_remove('test', 'x');
-SELECT pgv_insert('test', 'x', ROW (1::int), TRUE);
+SELECT pgv_insert('test', 'x', ROW (3::int, 4::int), TRUE);
 COMMIT;
 
 SELECT * FROM pgv_list() order by package, name;
 
 -- Variables should be insertable after pgv_free
 BEGIN;
-SELECT pgv_insert('test', 'y', ROW (1::int), TRUE);
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), TRUE);
 SELECT pgv_free();
-SELECT pgv_insert('test', 'y', ROW (1::int), TRUE);
+SELECT pgv_insert('test', 'y', ROW (3::int, 4::int), TRUE);
 ROLLBACK;
 
 SELECT * FROM pgv_list() order by package, name;
 
 BEGIN;
-SELECT pgv_insert('test', 'y', ROW (1::int), TRUE);
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), TRUE);
 SELECT pgv_free();
-SELECT pgv_insert('test', 'y', ROW (1::int), TRUE);
+SELECT pgv_insert('test', 'y', ROW (3::int, 4::int), TRUE);
 COMMIT;
 
 SELECT * FROM pgv_list() order by package, name;

From 38de8dfbdc1ecd076aef213da162b54544699857 Mon Sep 17 00:00:00 2001
From: Maxim Orlov <m.orlov@postgrespro.ru>
Date: Wed, 14 Oct 2020 18:42:43 +0300
Subject: [PATCH 03/22] Issue #27: Add fix and improve tests.

---
 expected/pg_variables_trans.out | 242 ++++++++++++++++++++++++++++++--
 pg_variables.c                  |   5 +
 sql/pg_variables_trans.sql      |  58 +++++++-
 3 files changed, 289 insertions(+), 16 deletions(-)

diff --git a/expected/pg_variables_trans.out b/expected/pg_variables_trans.out
index 34c2f85..d73ef69 100644
--- a/expected/pg_variables_trans.out
+++ b/expected/pg_variables_trans.out
@@ -2032,7 +2032,7 @@ SELECT pgv_free();
  
 (1 row)
 
--- Variables should be insertable after pgv_remove
+-- Variables should be insertable after pgv_remove (variable)
 BEGIN;
 SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
  pgv_insert 
@@ -2040,18 +2040,43 @@ SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
  
 (1 row)
 
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
 SELECT pgv_remove('test', 'x');
  pgv_remove 
 ------------
  
 (1 row)
 
-SELECT pgv_insert('test', 'x', ROW (3::int, 4::int), TRUE);
+SELECT pgv_insert('test', 'x', ROW (1::int, 3::int), TRUE);
  pgv_insert 
 ------------
  
 (1 row)
 
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,3)
+(1 row)
+
+SELECT pgv_insert('test', 'x', ROW (2::int, 4::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,3)
+ (2,4)
+(2 rows)
+
 ROLLBACK;
 SELECT * FROM pgv_list() order by package, name;
  package | name | is_transactional 
@@ -2065,18 +2090,43 @@ SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
  
 (1 row)
 
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
 SELECT pgv_remove('test', 'x');
  pgv_remove 
 ------------
  
 (1 row)
 
-SELECT pgv_insert('test', 'x', ROW (3::int, 4::int), TRUE);
+SELECT pgv_insert('test', 'x', ROW (1::int, 3::int), TRUE);
  pgv_insert 
 ------------
  
 (1 row)
 
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,3)
+(1 row)
+
+SELECT pgv_insert('test', 'x', ROW (2::int, 4::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,3)
+ (2,4)
+(2 rows)
+
 COMMIT;
 SELECT * FROM pgv_list() order by package, name;
  package | name | is_transactional 
@@ -2084,7 +2134,65 @@ SELECT * FROM pgv_list() order by package, name;
  test    | x    | t
 (1 row)
 
--- Variables should be insertable after pgv_free
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,3)
+ (2,4)
+(2 rows)
+
+-- Variables should be insertable after pgv_remove (package)
+BEGIN;
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_remove('test');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (1::int, 3::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,3)
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (2::int, 4::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,3)
+ (2,4)
+(2 rows)
+
+ROLLBACK;
+SELECT * FROM pgv_list() order by package, name;
+ package | name | is_transactional 
+---------+------+------------------
+ test    | x    | t
+(1 row)
+
 BEGIN;
 SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), TRUE);
  pgv_insert 
@@ -2092,51 +2200,167 @@ SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), TRUE);
  
 (1 row)
 
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_remove('test');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (1::int, 3::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,3)
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (2::int, 4::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,3)
+ (2,4)
+(2 rows)
+
+COMMIT;
+SELECT * FROM pgv_list() order by package, name;
+ package | name | is_transactional 
+---------+------+------------------
+ test    | y    | t
+(1 row)
+
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,3)
+ (2,4)
+(2 rows)
+
+-- Variables should be insertable after pgv_free
+BEGIN;
+SELECT pgv_insert('test', 'z', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'z');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
 SELECT pgv_free();
  pgv_free 
 ----------
  
 (1 row)
 
-SELECT pgv_insert('test', 'y', ROW (3::int, 4::int), TRUE);
+SELECT pgv_insert('test', 'z', ROW (1::int, 3::int), TRUE);
  pgv_insert 
 ------------
  
 (1 row)
 
+SELECT pgv_select('test', 'z');
+ pgv_select 
+------------
+ (1,3)
+(1 row)
+
+SELECT pgv_insert('test', 'z', ROW (2::int, 4::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'z');
+ pgv_select 
+------------
+ (1,3)
+ (2,4)
+(2 rows)
+
 ROLLBACK;
 SELECT * FROM pgv_list() order by package, name;
  package | name | is_transactional 
 ---------+------+------------------
- test    | x    | t
+ test    | y    | t
 (1 row)
 
 BEGIN;
-SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), TRUE);
+SELECT pgv_insert('test', 'z', ROW (1::int, 2::int), TRUE);
  pgv_insert 
 ------------
  
 (1 row)
 
+SELECT pgv_select('test', 'z');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
 SELECT pgv_free();
  pgv_free 
 ----------
  
 (1 row)
 
-SELECT pgv_insert('test', 'y', ROW (3::int, 4::int), TRUE);
+SELECT pgv_insert('test', 'z', ROW (1::int, 3::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'z');
+ pgv_select 
+------------
+ (1,3)
+(1 row)
+
+SELECT pgv_insert('test', 'z', ROW (2::int, 4::int), TRUE);
  pgv_insert 
 ------------
  
 (1 row)
 
+SELECT pgv_select('test', 'z');
+ pgv_select 
+------------
+ (1,3)
+ (2,4)
+(2 rows)
+
 COMMIT;
 SELECT * FROM pgv_list() order by package, name;
  package | name | is_transactional 
 ---------+------+------------------
- test    | y    | t
+ test    | z    | t
 (1 row)
 
+SELECT pgv_select('test', 'z');
+ pgv_select 
+------------
+ (1,3)
+ (2,4)
+(2 rows)
+
 SELECT pgv_free();
  pgv_free 
 ----------
diff --git a/pg_variables.c b/pg_variables.c
index 304bf5d..ee1fa3d 100644
--- a/pg_variables.c
+++ b/pg_variables.c
@@ -404,9 +404,14 @@ variable_insert(PG_FUNCTION_ARGS)
 		 */
 		tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
 		if (variable->is_deleted)
+		{
 			init_record(record, tupdesc, variable);
+			variable->is_deleted = false;
+		}
 		else
+		{
 			check_attributes(variable, tupdesc);
+		}
 	}
 
 	LastTypeId = tupType;
diff --git a/sql/pg_variables_trans.sql b/sql/pg_variables_trans.sql
index a5784c0..c4b8c90 100644
--- a/sql/pg_variables_trans.sql
+++ b/sql/pg_variables_trans.sql
@@ -535,38 +535,82 @@ SELECT package FROM pgv_stats() ORDER BY package;
 
 SELECT pgv_free();
 
--- Variables should be insertable after pgv_remove
+-- Variables should be insertable after pgv_remove (variable)
 BEGIN;
 SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+SELECT pgv_select('test', 'x');
 SELECT pgv_remove('test', 'x');
-SELECT pgv_insert('test', 'x', ROW (3::int, 4::int), TRUE);
+SELECT pgv_insert('test', 'x', ROW (1::int, 3::int), TRUE);
+SELECT pgv_select('test', 'x');
+SELECT pgv_insert('test', 'x', ROW (2::int, 4::int), TRUE);
+SELECT pgv_select('test', 'x');
 ROLLBACK;
 
 SELECT * FROM pgv_list() order by package, name;
 
 BEGIN;
 SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+SELECT pgv_select('test', 'x');
 SELECT pgv_remove('test', 'x');
-SELECT pgv_insert('test', 'x', ROW (3::int, 4::int), TRUE);
+SELECT pgv_insert('test', 'x', ROW (1::int, 3::int), TRUE);
+SELECT pgv_select('test', 'x');
+SELECT pgv_insert('test', 'x', ROW (2::int, 4::int), TRUE);
+SELECT pgv_select('test', 'x');
 COMMIT;
 
 SELECT * FROM pgv_list() order by package, name;
+SELECT pgv_select('test', 'x');
 
--- Variables should be insertable after pgv_free
+-- Variables should be insertable after pgv_remove (package)
 BEGIN;
 SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), TRUE);
-SELECT pgv_free();
-SELECT pgv_insert('test', 'y', ROW (3::int, 4::int), TRUE);
+SELECT pgv_select('test', 'y');
+SELECT pgv_remove('test');
+SELECT pgv_insert('test', 'y', ROW (1::int, 3::int), TRUE);
+SELECT pgv_select('test', 'y');
+SELECT pgv_insert('test', 'y', ROW (2::int, 4::int), TRUE);
+SELECT pgv_select('test', 'y');
 ROLLBACK;
 
 SELECT * FROM pgv_list() order by package, name;
 
 BEGIN;
 SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), TRUE);
+SELECT pgv_select('test', 'y');
+SELECT pgv_remove('test');
+SELECT pgv_insert('test', 'y', ROW (1::int, 3::int), TRUE);
+SELECT pgv_select('test', 'y');
+SELECT pgv_insert('test', 'y', ROW (2::int, 4::int), TRUE);
+SELECT pgv_select('test', 'y');
+COMMIT;
+
+SELECT * FROM pgv_list() order by package, name;
+SELECT pgv_select('test', 'y');
+
+-- Variables should be insertable after pgv_free
+BEGIN;
+SELECT pgv_insert('test', 'z', ROW (1::int, 2::int), TRUE);
+SELECT pgv_select('test', 'z');
+SELECT pgv_free();
+SELECT pgv_insert('test', 'z', ROW (1::int, 3::int), TRUE);
+SELECT pgv_select('test', 'z');
+SELECT pgv_insert('test', 'z', ROW (2::int, 4::int), TRUE);
+SELECT pgv_select('test', 'z');
+ROLLBACK;
+
+SELECT * FROM pgv_list() order by package, name;
+
+BEGIN;
+SELECT pgv_insert('test', 'z', ROW (1::int, 2::int), TRUE);
+SELECT pgv_select('test', 'z');
 SELECT pgv_free();
-SELECT pgv_insert('test', 'y', ROW (3::int, 4::int), TRUE);
+SELECT pgv_insert('test', 'z', ROW (1::int, 3::int), TRUE);
+SELECT pgv_select('test', 'z');
+SELECT pgv_insert('test', 'z', ROW (2::int, 4::int), TRUE);
+SELECT pgv_select('test', 'z');
 COMMIT;
 
 SELECT * FROM pgv_list() order by package, name;
+SELECT pgv_select('test', 'z');
 
 SELECT pgv_free();

From 712d0128130df33257bfb9e08ac8105fc1ed1937 Mon Sep 17 00:00:00 2001
From: Maxim Orlov <m.orlov@postgrespro.ru>
Date: Fri, 23 Oct 2020 10:24:23 +0300
Subject: [PATCH 04/22] Issue #27: Fix for backend termination on transaction
 commit.

---
 expected/pg_variables_trans.out | 644 ++++++++++++++++++++++++++++++++
 pg_variables.c                  |  29 +-
 sql/pg_variables_trans.sql      | 246 ++++++++++++
 3 files changed, 904 insertions(+), 15 deletions(-)

diff --git a/expected/pg_variables_trans.out b/expected/pg_variables_trans.out
index d73ef69..76fc339 100644
--- a/expected/pg_variables_trans.out
+++ b/expected/pg_variables_trans.out
@@ -2367,3 +2367,647 @@ SELECT pgv_free();
  
 (1 row)
 
+-- Variables should be rollbackable if transactional
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'x', ROW (2::int, 3::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+BEGIN;
+SELECT pgv_remove('test', 'x');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+BEGIN;
+SELECT pgv_remove('test');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+BEGIN;
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+---
+--- Variables should not be rollbackable if not transactional
+---
+-- case 1 (remove var)
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (2::int, 3::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+BEGIN;
+SELECT pgv_remove('test', 'y');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+ROLLBACK;
+SELECT pgv_select('test', 'y');
+ERROR:  unrecognized variable "y"
+-- case 2 (remove pack)
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (2::int, 3::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+BEGIN;
+SELECT pgv_remove('test');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+ROLLBACK;
+SELECT pgv_select('test', 'y');
+ERROR:  unrecognized variable "y"
+-- case 3 (free)
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (2::int, 3::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+BEGIN;
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+ROLLBACK;
+SELECT pgv_select('test', 'y');
+ERROR:  unrecognized variable "y"
+-- clear all
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+---
+--- Cursors test #1 (remove var)
+---
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+SELECT pgv_remove('test', 'x');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'y');
+SELECT pgv_remove('test', 'y');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized variable "y"
+ROLLBACK;
+SELECT pgv_select('test', 'y');
+ERROR:  unrecognized variable "y"
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+---
+--- Cursors test #2 (remove pack)
+---
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+SELECT pgv_remove('test');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'y');
+SELECT pgv_remove('test');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test');
+ERROR:  function pgv_select(unknown) does not exist
+LINE 1: SELECT pgv_select('test');
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+---
+--- Cursors test #3 (free)
+---
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'y');
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test');
+ERROR:  function pgv_select(unknown) does not exist
+LINE 1: SELECT pgv_select('test');
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+---
+--- Cursor test #4
+---
+-- non transactional, remove var
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+SELECT pgv_remove('test', 'x');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ERROR:  unrecognized package "test"
+-- non transactional, remove pac
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+SELECT pgv_remove('test');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ERROR:  unrecognized package "test"
+-- non transactional, free
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ERROR:  unrecognized package "test"
+-- transactional, remove var
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'y');
+SELECT pgv_remove('test', 'y');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+-- transactional, remove pack
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'y');
+SELECT pgv_remove('test');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+-- transactional, free
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'y');
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+---
+--- Cursor test #5
+---
+-- non transactional
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_remove('test', 'x');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ERROR:  unrecognized package "test"
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_remove('test');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ERROR:  unrecognized package "test"
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ERROR:  unrecognized package "test"
+-- transactional
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_remove('test', 'x');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_remove('test');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+---
+--- Cursor test #6
+---
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_remove('test', 'x');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+COMMIT;
diff --git a/pg_variables.c b/pg_variables.c
index ee1fa3d..fe09532 100644
--- a/pg_variables.c
+++ b/pg_variables.c
@@ -595,28 +595,27 @@ variable_select(PG_FUNCTION_ARGS)
 	FuncCallContext *funcctx;
 	HASH_SEQ_STATUS *rstat;
 	HashRecordEntry *item;
+	text	   		*package_name;
+	text	   		*var_name;
+	Package	   		*package;
+	Variable   		*variable;
+
+	CHECK_ARGS_FOR_NULL();
+
+	/* Get arguments */
+	package_name = PG_GETARG_TEXT_PP(0);
+	var_name = PG_GETARG_TEXT_PP(1);
+
+	package = getPackage(package_name, true);
+	variable = getVariableInternal(package, var_name, RECORDOID, true,
+								   true);
 
 	if (SRF_IS_FIRSTCALL())
 	{
-		text	   *package_name;
-		text	   *var_name;
-		Package	   *package;
-		Variable   *variable;
 		MemoryContext oldcontext;
 		RecordVar  *record;
 
-		CHECK_ARGS_FOR_NULL();
-
-		/* Get arguments */
-		package_name = PG_GETARG_TEXT_PP(0);
-		var_name = PG_GETARG_TEXT_PP(1);
-
-		package = getPackage(package_name, true);
-		variable = getVariableInternal(package, var_name, RECORDOID, true,
-									   true);
-
 		record = &(GetActualValue(variable).record);
-
 		funcctx = SRF_FIRSTCALL_INIT();
 		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
 
diff --git a/sql/pg_variables_trans.sql b/sql/pg_variables_trans.sql
index c4b8c90..b4f28d5 100644
--- a/sql/pg_variables_trans.sql
+++ b/sql/pg_variables_trans.sql
@@ -614,3 +614,249 @@ SELECT * FROM pgv_list() order by package, name;
 SELECT pgv_select('test', 'z');
 
 SELECT pgv_free();
+
+-- Variables should be rollbackable if transactional
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+SELECT pgv_insert('test', 'x', ROW (2::int, 3::int), TRUE);
+SELECT pgv_select('test', 'x');
+
+BEGIN;
+SELECT pgv_remove('test', 'x');
+ROLLBACK;
+
+SELECT pgv_select('test', 'x');
+
+BEGIN;
+SELECT pgv_remove('test');
+ROLLBACK;
+
+SELECT pgv_select('test', 'x');
+
+BEGIN;
+SELECT pgv_free();
+ROLLBACK;
+
+SELECT pgv_select('test', 'x');
+
+---
+--- Variables should not be rollbackable if not transactional
+---
+-- case 1 (remove var)
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), FALSE);
+SELECT pgv_insert('test', 'y', ROW (2::int, 3::int), FALSE);
+SELECT pgv_select('test', 'y');
+
+BEGIN;
+SELECT pgv_remove('test', 'y');
+ROLLBACK;
+
+SELECT pgv_select('test', 'y');
+-- case 2 (remove pack)
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), FALSE);
+SELECT pgv_insert('test', 'y', ROW (2::int, 3::int), FALSE);
+SELECT pgv_select('test', 'y');
+
+BEGIN;
+SELECT pgv_remove('test');
+ROLLBACK;
+
+SELECT pgv_select('test', 'y');
+-- case 3 (free)
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), FALSE);
+SELECT pgv_insert('test', 'y', ROW (2::int, 3::int), FALSE);
+SELECT pgv_select('test', 'y');
+
+BEGIN;
+SELECT pgv_free();
+ROLLBACK;
+
+SELECT pgv_select('test', 'y');
+-- clear all
+SELECT pgv_free();
+
+---
+--- Cursors test #1 (remove var)
+---
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+SELECT pgv_remove('test', 'x');
+FETCH 1 in r1_cur;
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), FALSE);
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'y');
+SELECT pgv_remove('test', 'y');
+FETCH 1 in r1_cur;
+ROLLBACK;
+SELECT pgv_select('test', 'y');
+
+SELECT pgv_free();
+---
+--- Cursors test #2 (remove pack)
+---
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+SELECT pgv_remove('test');
+FETCH 1 in r1_cur;
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), FALSE);
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'y');
+SELECT pgv_remove('test');
+FETCH 1 in r1_cur;
+ROLLBACK;
+SELECT pgv_select('test');
+
+SELECT pgv_free();
+---
+--- Cursors test #3 (free)
+---
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+SELECT pgv_free();
+FETCH 1 in r1_cur;
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), FALSE);
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'y');
+SELECT pgv_free();
+FETCH 1 in r1_cur;
+ROLLBACK;
+SELECT pgv_select('test');
+
+SELECT pgv_free();
+---
+--- Cursor test #4
+---
+
+-- non transactional, remove var
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), FALSE);
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+SELECT pgv_remove('test', 'x');
+FETCH 1 in r1_cur;
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+
+-- non transactional, remove pac
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), FALSE);
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+SELECT pgv_remove('test');
+FETCH 1 in r1_cur;
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+
+-- non transactional, free
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), FALSE);
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+SELECT pgv_free();
+FETCH 1 in r1_cur;
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+
+-- transactional, remove var
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), TRUE);
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'y');
+SELECT pgv_remove('test', 'y');
+FETCH 1 in r1_cur;
+ROLLBACK;
+SELECT pgv_select('test', 'y');
+
+-- transactional, remove pack
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), TRUE);
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'y');
+SELECT pgv_remove('test');
+FETCH 1 in r1_cur;
+ROLLBACK;
+SELECT pgv_select('test', 'y');
+
+-- transactional, free
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), TRUE);
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'y');
+SELECT pgv_free();
+FETCH 1 in r1_cur;
+ROLLBACK;
+SELECT pgv_select('test', 'y');
+
+SELECT pgv_free();
+---
+--- Cursor test #5
+---
+
+-- non transactional
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), FALSE);
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+SELECT pgv_remove('test', 'x');
+FETCH 1 in r1_cur;
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), FALSE);
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+SELECT pgv_remove('test');
+FETCH 1 in r1_cur;
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), FALSE);
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+SELECT pgv_free();
+FETCH 1 in r1_cur;
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+
+-- transactional
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+SELECT pgv_remove('test', 'x');
+FETCH 1 in r1_cur;
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+SELECT pgv_remove('test');
+FETCH 1 in r1_cur;
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+SELECT pgv_free();
+FETCH 1 in r1_cur;
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+
+---
+--- Cursor test #6
+---
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+SELECT pgv_remove('test', 'x');
+COMMIT;

From 1b9dc66f2559de925df0271f84039a5099776a28 Mon Sep 17 00:00:00 2001
From: Maxim Orlov <m.orlov@postgrespro.ru>
Date: Fri, 23 Oct 2020 10:43:39 +0300
Subject: [PATCH 05/22]  Issue #27: Add fix for leaked hash_seq_search.

 Fix warning: leaked hash_seq_search scan for hash table.
---
 expected/pg_variables_trans.out | 499 ++++++++++++++++++++++++++++++++
 pg_variables.c                  |  44 ++-
 sql/pg_variables_trans.sql      | 147 ++++++++++
 3 files changed, 683 insertions(+), 7 deletions(-)

diff --git a/expected/pg_variables_trans.out b/expected/pg_variables_trans.out
index 76fc339..7f05a55 100644
--- a/expected/pg_variables_trans.out
+++ b/expected/pg_variables_trans.out
@@ -3011,3 +3011,502 @@ SELECT pgv_remove('test', 'x');
 (1 row)
 
 COMMIT;
+---
+--- Tests for "leaked hash_seq_search scan for hash table"
+---
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'x', ROW (2::int, 3::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'x', ROW (3::int, 4::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'x') LIMIT 1;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_select('test', 'x') LIMIT 2;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+SELECT pgv_select('test', 'x') LIMIT 3;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+BEGIN;
+SELECT pgv_select('test', 'x') LIMIT 1;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_select('test', 'x') LIMIT 2;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+SELECT pgv_select('test', 'x') LIMIT 3;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+ROLLBACK;
+BEGIN;
+SELECT pgv_select('test', 'x') LIMIT 1;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_select('test', 'x') LIMIT 2;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+SELECT pgv_select('test', 'x') LIMIT 3;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+COMMIT;
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (2::int, 3::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (3::int, 4::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'y') LIMIT 1;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_select('test', 'y') LIMIT 2;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+SELECT pgv_select('test', 'y') LIMIT 3;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+BEGIN;
+SELECT pgv_select('test', 'y') LIMIT 1;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_select('test', 'y') LIMIT 2;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+SELECT pgv_select('test', 'y') LIMIT 3;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+ROLLBACK;
+BEGIN;
+SELECT pgv_select('test', 'y') LIMIT 1;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_select('test', 'y') LIMIT 2;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+SELECT pgv_select('test', 'y') LIMIT 3;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+COMMIT;
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+DECLARE r2_cur CURSOR FOR SELECT pgv_select('test', 'y');
+DECLARE r3_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+FETCH 1 in r2_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+FETCH 1 in r3_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+ROLLBACK;
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+DECLARE r2_cur CURSOR FOR SELECT pgv_select('test', 'y');
+DECLARE r3_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 2 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+FETCH 2 in r2_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+FETCH 2 in r3_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+ROLLBACK;
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+DECLARE r2_cur CURSOR FOR SELECT pgv_select('test', 'y');
+DECLARE r3_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 3 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+FETCH 3 in r2_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+FETCH 3 in r3_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+ROLLBACK;
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+DECLARE r2_cur CURSOR FOR SELECT pgv_select('test', 'y');
+DECLARE r3_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+FETCH 1 in r2_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+FETCH 1 in r3_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+COMMIT;
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+DECLARE r2_cur CURSOR FOR SELECT pgv_select('test', 'y');
+DECLARE r3_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 2 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+FETCH 2 in r2_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+FETCH 2 in r3_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+COMMIT;
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+DECLARE r2_cur CURSOR FOR SELECT pgv_select('test', 'y');
+DECLARE r3_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 3 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+FETCH 3 in r2_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+FETCH 3 in r3_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+COMMIT;
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+DECLARE r2_cur CURSOR FOR SELECT pgv_select('test', 'y');
+DECLARE r3_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+FETCH 2 in r2_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+FETCH 3 in r3_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+ROLLBACK;
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+DECLARE r2_cur CURSOR FOR SELECT pgv_select('test', 'y');
+DECLARE r3_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+FETCH 2 in r2_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+FETCH 3 in r3_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+COMMIT;
+---
+--- Some special cases
+---
+-- take #1
+SELECT pgv_insert('test', 'z1', ROW (2::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'z1');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (2,2)
+(1 row)
+
+SELECT pgv_remove('test', 'z1');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized variable "z1"
+ROLLBACK;
+SELECT pgv_select('test', 'z1');
+ pgv_select 
+------------
+ (2,2)
+(1 row)
+
+-- take #2
+SELECT pgv_insert('test', 'z2', ROW (2::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'z2');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (2,2)
+(1 row)
+
+SELECT pgv_remove('test', 'z2');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized variable "z2"
+ROLLBACK;
+SELECT pgv_select('test', 'z2');
+ERROR:  unrecognized variable "z2"
+SELECT pgv_insert('test', 'z2', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+-- take #3
+SELECT pgv_insert('test', 'z3', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'z3');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_remove('test', 'z3');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized variable "z3"
+ROLLBACK;
+SELECT pgv_select('test', 'z3');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'z3');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_remove('test', 'z3');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+COMMIT;
+SELECT pgv_select('test', 'z3');
+ERROR:  unrecognized variable "z3"
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
diff --git a/pg_variables.c b/pg_variables.c
index fe09532..bad8711 100644
--- a/pg_variables.c
+++ b/pg_variables.c
@@ -129,6 +129,9 @@ static ExecutorEnd_hook_type prev_ExecutorEnd = NULL;
 static dlist_head *changesStack = NULL;
 static MemoryContext changesStackContext = NULL;
 
+/* List to store all the running hash_seq_search scan for hash table */
+static List *rstats = NIL; 
+
 /* Returns a lists of packages and variables changed at current subxact level */
 #define get_actual_changes_list() \
 	( \
@@ -595,10 +598,10 @@ variable_select(PG_FUNCTION_ARGS)
 	FuncCallContext *funcctx;
 	HASH_SEQ_STATUS *rstat;
 	HashRecordEntry *item;
-	text	   		*package_name;
-	text	   		*var_name;
-	Package	   		*package;
-	Variable   		*variable;
+	text			*package_name;
+	text			*var_name;
+	Package			*package;
+	Variable		*variable;
 
 	CHECK_ARGS_FOR_NULL();
 
@@ -612,12 +615,13 @@ variable_select(PG_FUNCTION_ARGS)
 
 	if (SRF_IS_FIRSTCALL())
 	{
-		MemoryContext oldcontext;
-		RecordVar  *record;
+		MemoryContext	 oldcontext;
+		RecordVar		*record;
 
 		record = &(GetActualValue(variable).record);
 		funcctx = SRF_FIRSTCALL_INIT();
-		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+		oldcontext = MemoryContextSwitchTo(TopTransactionContext);
 
 		funcctx->tuple_desc = record->tupdesc;
 
@@ -625,6 +629,8 @@ variable_select(PG_FUNCTION_ARGS)
 		hash_seq_init(rstat, record->rhash);
 		funcctx->user_fctx = rstat;
 
+		rstats = lcons((void *)rstat, rstats);
+
 		MemoryContextSwitchTo(oldcontext);
 		PG_FREE_IF_COPY(package_name, 0);
 		PG_FREE_IF_COPY(var_name, 1);
@@ -644,6 +650,7 @@ variable_select(PG_FUNCTION_ARGS)
 	}
 	else
 	{
+		rstats = list_delete(rstats, rstat);
 		pfree(rstat);
 		SRF_RETURN_DONE(funcctx);
 	}
@@ -2196,6 +2203,18 @@ pgvTransCallback(XactEvent event, void *arg)
 				break;
 		}
 	}
+
+	if (event == XACT_EVENT_PRE_COMMIT || event == XACT_EVENT_ABORT)
+	{
+		ListCell *cell;
+
+		foreach(cell, rstats)
+		{
+			hash_seq_term((HASH_SEQ_STATUS *) lfirst(cell));
+		}
+
+		rstats = NIL;
+	}
 }
 
 /*
@@ -2208,6 +2227,17 @@ variable_ExecutorEnd(QueryDesc *queryDesc)
 		prev_ExecutorEnd(queryDesc);
 	else
 		standard_ExecutorEnd(queryDesc);
+
+	{
+		ListCell *cell;
+
+		foreach(cell, rstats)
+		{
+			hash_seq_term((HASH_SEQ_STATUS *) lfirst(cell));
+		}
+
+		rstats = NIL;
+	}
 }
 
 /*
diff --git a/sql/pg_variables_trans.sql b/sql/pg_variables_trans.sql
index b4f28d5..c9cb2ad 100644
--- a/sql/pg_variables_trans.sql
+++ b/sql/pg_variables_trans.sql
@@ -860,3 +860,150 @@ DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
 FETCH 1 in r1_cur;
 SELECT pgv_remove('test', 'x');
 COMMIT;
+
+---
+--- Tests for "leaked hash_seq_search scan for hash table"
+---
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), FALSE);
+SELECT pgv_insert('test', 'x', ROW (2::int, 3::int), FALSE);
+SELECT pgv_insert('test', 'x', ROW (3::int, 4::int), FALSE);
+SELECT pgv_select('test', 'x') LIMIT 1;
+SELECT pgv_select('test', 'x') LIMIT 2;
+SELECT pgv_select('test', 'x') LIMIT 3;
+BEGIN;
+SELECT pgv_select('test', 'x') LIMIT 1;
+SELECT pgv_select('test', 'x') LIMIT 2;
+SELECT pgv_select('test', 'x') LIMIT 3;
+ROLLBACK;
+BEGIN;
+SELECT pgv_select('test', 'x') LIMIT 1;
+SELECT pgv_select('test', 'x') LIMIT 2;
+SELECT pgv_select('test', 'x') LIMIT 3;
+COMMIT;
+
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), TRUE);
+SELECT pgv_insert('test', 'y', ROW (2::int, 3::int), TRUE);
+SELECT pgv_insert('test', 'y', ROW (3::int, 4::int), TRUE);
+SELECT pgv_select('test', 'y') LIMIT 1;
+SELECT pgv_select('test', 'y') LIMIT 2;
+SELECT pgv_select('test', 'y') LIMIT 3;
+BEGIN;
+SELECT pgv_select('test', 'y') LIMIT 1;
+SELECT pgv_select('test', 'y') LIMIT 2;
+SELECT pgv_select('test', 'y') LIMIT 3;
+ROLLBACK;
+BEGIN;
+SELECT pgv_select('test', 'y') LIMIT 1;
+SELECT pgv_select('test', 'y') LIMIT 2;
+SELECT pgv_select('test', 'y') LIMIT 3;
+COMMIT;
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+DECLARE r2_cur CURSOR FOR SELECT pgv_select('test', 'y');
+DECLARE r3_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+FETCH 1 in r2_cur;
+FETCH 1 in r3_cur;
+ROLLBACK;
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+DECLARE r2_cur CURSOR FOR SELECT pgv_select('test', 'y');
+DECLARE r3_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 2 in r1_cur;
+FETCH 2 in r2_cur;
+FETCH 2 in r3_cur;
+ROLLBACK;
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+DECLARE r2_cur CURSOR FOR SELECT pgv_select('test', 'y');
+DECLARE r3_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 3 in r1_cur;
+FETCH 3 in r2_cur;
+FETCH 3 in r3_cur;
+ROLLBACK;
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+DECLARE r2_cur CURSOR FOR SELECT pgv_select('test', 'y');
+DECLARE r3_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+FETCH 1 in r2_cur;
+FETCH 1 in r3_cur;
+COMMIT;
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+DECLARE r2_cur CURSOR FOR SELECT pgv_select('test', 'y');
+DECLARE r3_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 2 in r1_cur;
+FETCH 2 in r2_cur;
+FETCH 2 in r3_cur;
+COMMIT;
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+DECLARE r2_cur CURSOR FOR SELECT pgv_select('test', 'y');
+DECLARE r3_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 3 in r1_cur;
+FETCH 3 in r2_cur;
+FETCH 3 in r3_cur;
+COMMIT;
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+DECLARE r2_cur CURSOR FOR SELECT pgv_select('test', 'y');
+DECLARE r3_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+FETCH 2 in r2_cur;
+FETCH 3 in r3_cur;
+ROLLBACK;
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+DECLARE r2_cur CURSOR FOR SELECT pgv_select('test', 'y');
+DECLARE r3_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+FETCH 2 in r2_cur;
+FETCH 3 in r3_cur;
+COMMIT;
+
+---
+--- Some special cases
+---
+-- take #1
+SELECT pgv_insert('test', 'z1', ROW (2::int, 2::int), TRUE);
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'z1');
+FETCH 1 in r1_cur;
+SELECT pgv_remove('test', 'z1');
+FETCH 1 in r1_cur;
+ROLLBACK;
+SELECT pgv_select('test', 'z1');
+-- take #2
+SELECT pgv_insert('test', 'z2', ROW (2::int, 2::int), FALSE);
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'z2');
+FETCH 1 in r1_cur;
+SELECT pgv_remove('test', 'z2');
+FETCH 1 in r1_cur;
+ROLLBACK;
+SELECT pgv_select('test', 'z2');
+SELECT pgv_insert('test', 'z2', ROW (1::int, 2::int), FALSE);
+-- take #3
+SELECT pgv_insert('test', 'z3', ROW (1::int, 2::int), TRUE);
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'z3');
+FETCH 1 in r1_cur;
+SELECT pgv_remove('test', 'z3');
+FETCH 1 in r1_cur;
+ROLLBACK;
+SELECT pgv_select('test', 'z3');
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'z3');
+FETCH 1 in r1_cur;
+SELECT pgv_remove('test', 'z3');
+COMMIT;
+SELECT pgv_select('test', 'z3');
+
+SELECT pgv_free();

From ddf4a22ac2c1723429ee28803a75613627a233f1 Mon Sep 17 00:00:00 2001
From: Maxim Orlov <m.orlov@postgrespro.ru>
Date: Fri, 23 Oct 2020 17:09:35 +0300
Subject: [PATCH 06/22] Issue #27: Add tests and comment.

---
 expected/pg_variables_trans.out | 46 +++++++++++++++++++++++++++++++++
 pg_variables.c                  | 15 ++++++++++-
 sql/pg_variables_trans.sql      | 23 +++++++++++++++++
 3 files changed, 83 insertions(+), 1 deletion(-)

diff --git a/expected/pg_variables_trans.out b/expected/pg_variables_trans.out
index 7f05a55..eac7be2 100644
--- a/expected/pg_variables_trans.out
+++ b/expected/pg_variables_trans.out
@@ -3510,3 +3510,49 @@ SELECT pgv_free();
  
 (1 row)
 
+-- take #4
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'x', ROW (2::int, 3::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+SAVEPOINT sp1;
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+ROLLBACK TO SAVEPOINT sp1;
+COMMIT;
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+SAVEPOINT sp1;
+FETCH 2 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+ROLLBACK TO SAVEPOINT sp1;
+COMMIT;
+BEGIN;
+SAVEPOINT sp1;
+SELECT pgv_select('test', 'x') LIMIT 1;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+ROLLBACK TO SAVEPOINT sp1;
+COMMIT;
diff --git a/pg_variables.c b/pg_variables.c
index bad8711..c4fd2b4 100644
--- a/pg_variables.c
+++ b/pg_variables.c
@@ -129,7 +129,20 @@ static ExecutorEnd_hook_type prev_ExecutorEnd = NULL;
 static dlist_head *changesStack = NULL;
 static MemoryContext changesStackContext = NULL;
 
-/* List to store all the running hash_seq_search scan for hash table */
+/*
+ * List to store all the running hash_seq_search scan for hash table.
+ *
+ * NOTE: In function variable_select we use hash_seq_search to find next tuple.
+ * So, in case user do not get all the data from set at once (use cursors or
+ * LIMIT) we have to call hash_seq_term to not to leak hash_seq_search scans.
+ *
+ * For doing this, we alloc all of the rstats in the TopTransactionContext and
+ * save pointers to the rstats into list. Once transaction ended (commited or
+ * aborted) we clear all the "active" hash_seq_search by calling hash_seq_term.
+ *
+ * TopTransactionContext is handy here, becouse it wount be reset by the time
+ * pgvTransCallback is called.
+ */
 static List *rstats = NIL; 
 
 /* Returns a lists of packages and variables changed at current subxact level */
diff --git a/sql/pg_variables_trans.sql b/sql/pg_variables_trans.sql
index c9cb2ad..19ee43d 100644
--- a/sql/pg_variables_trans.sql
+++ b/sql/pg_variables_trans.sql
@@ -1007,3 +1007,26 @@ COMMIT;
 SELECT pgv_select('test', 'z3');
 
 SELECT pgv_free();
+-- take #4
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+SELECT pgv_insert('test', 'x', ROW (2::int, 3::int), TRUE);
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+SAVEPOINT sp1;
+FETCH 1 in r1_cur;
+ROLLBACK TO SAVEPOINT sp1;
+COMMIT;
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+SAVEPOINT sp1;
+FETCH 2 in r1_cur;
+ROLLBACK TO SAVEPOINT sp1;
+COMMIT;
+
+BEGIN;
+SAVEPOINT sp1;
+SELECT pgv_select('test', 'x') LIMIT 1;
+ROLLBACK TO SAVEPOINT sp1;
+COMMIT;

From 1f5e5971942b6cc0723829152ca1d8b62a7c2db6 Mon Sep 17 00:00:00 2001
From: Maxim Orlov <m.orlov@postgrespro.ru>
Date: Fri, 23 Oct 2020 18:14:41 +0300
Subject: [PATCH 07/22] Issue #27: Add fix for leaked hash_seq_search for
 pgv_stats.

---
 expected/pg_variables_trans.out | 97 +++++++++++++++++++++++++++++++++
 pg_variables.c                  | 74 ++++++++++++++++---------
 sql/pg_variables_trans.sql      | 45 +++++++++++++++
 3 files changed, 190 insertions(+), 26 deletions(-)

diff --git a/expected/pg_variables_trans.out b/expected/pg_variables_trans.out
index eac7be2..2ce8138 100644
--- a/expected/pg_variables_trans.out
+++ b/expected/pg_variables_trans.out
@@ -3556,3 +3556,100 @@ SELECT pgv_select('test', 'x') LIMIT 1;
 
 ROLLBACK TO SAVEPOINT sp1;
 COMMIT;
+---
+--- Test cases for pgv_stats
+---
+SELECT pgv_insert('test1', 'y', ROW (2::float, 1::float), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test1', 'x', ROW (2::float, 1::float), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
+DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
+FETCH 1 in r1_cur;
+  pgv_stats   
+--------------
+ (test,32768)
+(1 row)
+
+FETCH 1 in r2_cur;
+  pgv_stats   
+--------------
+ (test,32768)
+(1 row)
+
+COMMIT;
+SELECT pgv_insert('test1', 'y', ROW (2::float, 1::float), TRUE);
+ERROR:  there is a record in the variable "y" with same key
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
+DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
+FETCH 1 in r1_cur;
+  pgv_stats   
+--------------
+ (test,32768)
+(1 row)
+
+FETCH 1 in r2_cur;
+  pgv_stats   
+--------------
+ (test,32768)
+(1 row)
+
+ROLLBACK;
+SELECT pgv_insert('test1', 'y', ROW (2::float, 1::float), FALSE);
+ERROR:  variable "y" already created as TRANSACTIONAL
+SELECT pgv_insert('test1', 'y', ROW (2::float, 1::float), FALSE);
+ERROR:  variable "y" already created as TRANSACTIONAL
+SELECT pgv_insert('test1', 'x', ROW (2::float, 1::float), FALSE);
+ERROR:  variable "x" already created as TRANSACTIONAL
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
+DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
+FETCH 1 in r1_cur;
+  pgv_stats   
+--------------
+ (test,32768)
+(1 row)
+
+FETCH 1 in r2_cur;
+  pgv_stats   
+--------------
+ (test,32768)
+(1 row)
+
+COMMIT;
+SELECT pgv_insert('test1', 'y', ROW (2::float, 1::float), FALSE);
+ERROR:  variable "y" already created as TRANSACTIONAL
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
+DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
+FETCH 1 in r1_cur;
+  pgv_stats   
+--------------
+ (test,32768)
+(1 row)
+
+FETCH 1 in r2_cur;
+  pgv_stats   
+--------------
+ (test,32768)
+(1 row)
+
+ROLLBACK;
+SELECT pgv_insert('test1', 'y', ROW (2::float, 1::float), FALSE);
+ERROR:  variable "y" already created as TRANSACTIONAL
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
diff --git a/pg_variables.c b/pg_variables.c
index c4fd2b4..0af9120 100644
--- a/pg_variables.c
+++ b/pg_variables.c
@@ -143,8 +143,10 @@ static MemoryContext changesStackContext = NULL;
  * TopTransactionContext is handy here, becouse it wount be reset by the time
  * pgvTransCallback is called.
  */
-static List *rstats = NIL; 
+static List *variables_stats = NIL; 
+static List *packages_stats = NIL;
 
+static void freeStatsLists(bool deep);
 /* Returns a lists of packages and variables changed at current subxact level */
 #define get_actual_changes_list() \
 	( \
@@ -642,7 +644,7 @@ variable_select(PG_FUNCTION_ARGS)
 		hash_seq_init(rstat, record->rhash);
 		funcctx->user_fctx = rstat;
 
-		rstats = lcons((void *)rstat, rstats);
+		variables_stats = lcons((void *)rstat, variables_stats);
 
 		MemoryContextSwitchTo(oldcontext);
 		PG_FREE_IF_COPY(package_name, 0);
@@ -663,7 +665,7 @@ variable_select(PG_FUNCTION_ARGS)
 	}
 	else
 	{
-		rstats = list_delete(rstats, rstat);
+		variables_stats = list_delete(variables_stats, rstat);
 		pfree(rstat);
 		SRF_RETURN_DONE(funcctx);
 	}
@@ -1232,13 +1234,14 @@ get_packages_stats(PG_FUNCTION_ARGS)
 {
 	FuncCallContext *funcctx;
 	MemoryContext oldcontext;
-	HASH_SEQ_STATUS *pstat;
+	HASH_SEQ_STATUS *rstat;
 	Package	   *package;
 
 	if (SRF_IS_FIRSTCALL())
 	{
 		TupleDesc	tupdesc;
 
+		//elog(INFO, " >>> ");
 		funcctx = SRF_FIRSTCALL_INIT();
 		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
 
@@ -1257,11 +1260,16 @@ get_packages_stats(PG_FUNCTION_ARGS)
 		 */
 		if (packagesHash)
 		{
-			pstat = (HASH_SEQ_STATUS *) palloc0(sizeof(HASH_SEQ_STATUS));
+			MemoryContext ctx;
+
+			ctx = MemoryContextSwitchTo(TopTransactionContext);
+			rstat = (HASH_SEQ_STATUS *) palloc0(sizeof(HASH_SEQ_STATUS));
 			/* Get packages list */
-			hash_seq_init(pstat, packagesHash);
+			hash_seq_init(rstat, packagesHash);
 
-			funcctx->user_fctx = pstat;
+			funcctx->user_fctx = rstat;
+			packages_stats = lcons((void *)rstat, packages_stats);
+			MemoryContextSwitchTo(ctx);
 		}
 		else
 			funcctx->user_fctx = NULL;
@@ -1274,9 +1282,9 @@ get_packages_stats(PG_FUNCTION_ARGS)
 		SRF_RETURN_DONE(funcctx);
 
 	/* Get packages list */
-	pstat = (HASH_SEQ_STATUS *) funcctx->user_fctx;
+	rstat = (HASH_SEQ_STATUS *) funcctx->user_fctx;
 
-	package = (Package *) hash_seq_search(pstat);
+	package = (Package *) hash_seq_search(rstat);
 	if (package != NULL)
 	{
 		Datum		values[2];
@@ -1308,7 +1316,8 @@ get_packages_stats(PG_FUNCTION_ARGS)
 	}
 	else
 	{
-		pfree(pstat);
+		packages_stats = list_delete(packages_stats, rstat);
+		pfree(rstat);
 		SRF_RETURN_DONE(funcctx);
 	}
 }
@@ -2218,16 +2227,7 @@ pgvTransCallback(XactEvent event, void *arg)
 	}
 
 	if (event == XACT_EVENT_PRE_COMMIT || event == XACT_EVENT_ABORT)
-	{
-		ListCell *cell;
-
-		foreach(cell, rstats)
-		{
-			hash_seq_term((HASH_SEQ_STATUS *) lfirst(cell));
-		}
-
-		rstats = NIL;
-	}
+		freeStatsLists(true);
 }
 
 /*
@@ -2241,16 +2241,38 @@ variable_ExecutorEnd(QueryDesc *queryDesc)
 	else
 		standard_ExecutorEnd(queryDesc);
 
+	freeStatsLists(false);
+}
+
+/*
+ * Free hash_seq_search scans 
+ */
+void
+freeStatsLists(bool deep)
+{
+	ListCell		*cell;
+	HASH_SEQ_STATUS	*status;
+
+	foreach(cell, variables_stats)
 	{
-		ListCell *cell;
+		status = (HASH_SEQ_STATUS *) lfirst(cell);
+		hash_seq_term(status);
 
-		foreach(cell, rstats)
-		{
-			hash_seq_term((HASH_SEQ_STATUS *) lfirst(cell));
-		}
+		if (deep)
+			pfree(status);
+	}
+
+	variables_stats = NIL;
 
-		rstats = NIL;
+	foreach(cell, packages_stats)
+	{
+		status = (HASH_SEQ_STATUS *) lfirst(cell);
+		hash_seq_term(status);
+
+		pfree(status);
 	}
+
+	packages_stats = NIL;
 }
 
 /*
diff --git a/sql/pg_variables_trans.sql b/sql/pg_variables_trans.sql
index 19ee43d..2b83ab5 100644
--- a/sql/pg_variables_trans.sql
+++ b/sql/pg_variables_trans.sql
@@ -1030,3 +1030,48 @@ SAVEPOINT sp1;
 SELECT pgv_select('test', 'x') LIMIT 1;
 ROLLBACK TO SAVEPOINT sp1;
 COMMIT;
+
+---
+--- Test cases for pgv_stats
+---
+SELECT pgv_insert('test1', 'y', ROW (2::float, 1::float), TRUE);
+SELECT pgv_insert('test1', 'x', ROW (2::float, 1::float), TRUE);
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
+DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
+FETCH 1 in r1_cur;
+FETCH 1 in r2_cur;
+COMMIT;
+
+SELECT pgv_insert('test1', 'y', ROW (2::float, 1::float), TRUE);
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
+DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
+FETCH 1 in r1_cur;
+FETCH 1 in r2_cur;
+ROLLBACK;
+
+SELECT pgv_insert('test1', 'y', ROW (2::float, 1::float), FALSE);
+
+SELECT pgv_insert('test1', 'y', ROW (2::float, 1::float), FALSE);
+SELECT pgv_insert('test1', 'x', ROW (2::float, 1::float), FALSE);
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
+DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
+FETCH 1 in r1_cur;
+FETCH 1 in r2_cur;
+COMMIT;
+
+SELECT pgv_insert('test1', 'y', ROW (2::float, 1::float), FALSE);
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
+DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
+FETCH 1 in r1_cur;
+FETCH 1 in r2_cur;
+ROLLBACK;
+
+SELECT pgv_insert('test1', 'y', ROW (2::float, 1::float), FALSE);
+
+SELECT pgv_free();

From d90fd21bb6161506fa4c6b45a98c55f8f107d9fc Mon Sep 17 00:00:00 2001
From: Maxim Orlov <m.orlov@postgrespro.ru>
Date: Tue, 27 Oct 2020 14:05:09 +0300
Subject: [PATCH 08/22] Issue #27: Add fix for VALGRING failed test.

---
 pg_variables.c | 148 +++++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 130 insertions(+), 18 deletions(-)

diff --git a/pg_variables.c b/pg_variables.c
index 0af9120..7e29b76 100644
--- a/pg_variables.c
+++ b/pg_variables.c
@@ -130,7 +130,8 @@ static dlist_head *changesStack = NULL;
 static MemoryContext changesStackContext = NULL;
 
 /*
- * List to store all the running hash_seq_search scan for hash table.
+ * List to store all the running hash_seq_search, variable and package scan for
+ * hash table.
  *
  * NOTE: In function variable_select we use hash_seq_search to find next tuple.
  * So, in case user do not get all the data from set at once (use cursors or
@@ -143,10 +144,116 @@ static MemoryContext changesStackContext = NULL;
  * TopTransactionContext is handy here, becouse it wount be reset by the time
  * pgvTransCallback is called.
  */
-static List *variables_stats = NIL; 
+static List *variables_stats = NIL;
 static List *packages_stats = NIL;
 
-static void freeStatsLists(bool deep);
+typedef struct tagHtabToStat {
+	HTAB			*hash;
+	HASH_SEQ_STATUS	*status;
+	Variable		*variable;
+	Package			*package;
+} HtabToStat;
+
+/*
+ * A bunch of comp functions for HtabToStat members here.
+ */
+static bool
+HtabToStat_status_eq(HtabToStat *entry, void *value)
+{
+	return entry->status == (HASH_SEQ_STATUS *) value;
+}
+
+static bool
+HtabToStat_variable_eq(HtabToStat *entry, void *value)
+{
+	return entry->variable == (Variable *) value;
+}
+
+static bool
+HtabToStat_package_eq(HtabToStat *entry, void *value)
+{
+	return entry->package == (Package *) value;
+}
+
+static bool
+HtabToStat_eq_all(HtabToStat *entry, void *value)
+{
+	return true;
+}
+
+/*
+ * Generic remove_if algorithm for HtabToStat.
+ *
+ *  + eq if function pointer used to compare list entry to the value.
+ *  + if match_first is true return on first match.
+ */
+static void
+HtabToStat_remove_if(List **l, void *value,
+					 bool (*eq)(HtabToStat *, void *),
+					 bool match_first)
+{
+	ListCell	*cell, *next, *prev = NULL;
+	HtabToStat	*entry = NULL;
+
+	for (cell = list_head(*l); cell; cell = next)
+	{
+		entry = (HtabToStat *) lfirst(cell);
+		next = lnext(cell);
+
+		if (eq(entry, value))
+		{
+			*l = list_delete_cell(*l, cell, prev);
+			pfree(entry->status);
+			pfree(entry);
+
+			if (match_first)
+				return;
+		}
+		else
+		{
+			prev = cell;
+		}
+	}
+}
+
+/*
+ * Remove first entry for status.
+ */
+static void
+remove_variables_status(List **l, HASH_SEQ_STATUS *status)
+{
+	HtabToStat_remove_if(l, status, HtabToStat_status_eq, true);
+}
+
+/*
+ * Remove first entry for variable.
+ */
+static void
+remove_variables_variable(List **l, Variable *variable)
+{
+	HtabToStat_remove_if(l, variable, HtabToStat_variable_eq, true);
+}
+
+/*
+ * Remove all the entrys for package.
+ */
+static void
+remove_variables_package(List **l, Package *package)
+{
+	HtabToStat_remove_if(l, package, HtabToStat_package_eq, false);
+}
+
+/*
+ * Remove all.
+ */
+static void
+remove_variables_all(List **l)
+{
+	HtabToStat_remove_if(l, NULL, HtabToStat_eq_all, false);
+	*l = NIL;
+}
+
+static void freeStatsLists(void);
 /* Returns a lists of packages and variables changed at current subxact level */
 #define get_actual_changes_list() \
 	( \
@@ -632,6 +739,7 @@ variable_select(PG_FUNCTION_ARGS)
 	{
 		MemoryContext	 oldcontext;
 		RecordVar		*record;
+		HtabToStat		*htab_to_stat;
 
 		record = &(GetActualValue(variable).record);
 		funcctx = SRF_FIRSTCALL_INIT();
@@ -644,7 +752,12 @@ variable_select(PG_FUNCTION_ARGS)
 		hash_seq_init(rstat, record->rhash);
 		funcctx->user_fctx = rstat;
 
-		variables_stats = lcons((void *)rstat, variables_stats);
+		htab_to_stat = palloc0(sizeof(HtabToStat));
+		htab_to_stat->hash = record->rhash;
+		htab_to_stat->status = rstat;
+		htab_to_stat->variable = variable;
+		htab_to_stat->package = package;
+		variables_stats = lcons((void *)htab_to_stat, variables_stats);
 
 		MemoryContextSwitchTo(oldcontext);
 		PG_FREE_IF_COPY(package_name, 0);
@@ -665,8 +778,7 @@ variable_select(PG_FUNCTION_ARGS)
 	}
 	else
 	{
-		variables_stats = list_delete(variables_stats, rstat);
-		pfree(rstat);
+		remove_variables_status(&variables_stats, rstat);
 		SRF_RETURN_DONE(funcctx);
 	}
 }
@@ -968,6 +1080,8 @@ remove_package(PG_FUNCTION_ARGS)
 
 	resetVariablesCache();
 
+	remove_variables_package(&variables_stats, package);
+
 	PG_FREE_IF_COPY(package_name, 0);
 	PG_RETURN_VOID();
 }
@@ -1063,6 +1177,7 @@ remove_packages(PG_FUNCTION_ARGS)
 	}
 
 	resetVariablesCache();
+	remove_variables_all(&variables_stats);
 
 	PG_RETURN_VOID();
 }
@@ -1317,7 +1432,7 @@ get_packages_stats(PG_FUNCTION_ARGS)
 	else
 	{
 		packages_stats = list_delete(packages_stats, rstat);
-		pfree(rstat);
+		// pfree(rstat);
 		SRF_RETURN_DONE(funcctx);
 	}
 }
@@ -1777,6 +1892,7 @@ removeObject(TransObject *object, TransObjectType type)
 
 	/* Remove object from hash table */
 	hash_search(hash, object->name, HASH_REMOVE, &found);
+	remove_variables_variable(&variables_stats, (Variable *) object);
 
 	/* Remove package if it became empty */
 	if (type == TRANS_VARIABLE && isPackageEmpty(package))
@@ -2227,7 +2343,7 @@ pgvTransCallback(XactEvent event, void *arg)
 	}
 
 	if (event == XACT_EVENT_PRE_COMMIT || event == XACT_EVENT_ABORT)
-		freeStatsLists(true);
+		freeStatsLists();
 }
 
 /*
@@ -2241,25 +2357,23 @@ variable_ExecutorEnd(QueryDesc *queryDesc)
 	else
 		standard_ExecutorEnd(queryDesc);
 
-	freeStatsLists(false);
+	freeStatsLists();
 }
 
 /*
  * Free hash_seq_search scans 
  */
-void
-freeStatsLists(bool deep)
+static void
+freeStatsLists(void)
 {
 	ListCell		*cell;
 	HASH_SEQ_STATUS	*status;
+	HtabToStat		*htab_to_stat;
 
 	foreach(cell, variables_stats)
 	{
-		status = (HASH_SEQ_STATUS *) lfirst(cell);
-		hash_seq_term(status);
-
-		if (deep)
-			pfree(status);
+		htab_to_stat = (HtabToStat *) lfirst(cell);
+		hash_seq_term(htab_to_stat->status);
 	}
 
 	variables_stats = NIL;
@@ -2268,8 +2382,6 @@ freeStatsLists(bool deep)
 	{
 		status = (HASH_SEQ_STATUS *) lfirst(cell);
 		hash_seq_term(status);
-
-		pfree(status);
 	}
 
 	packages_stats = NIL;

From 0730e206979f6c8572fa23e6ed97a4c5c1705ec7 Mon Sep 17 00:00:00 2001
From: Maxim Orlov <m.orlov@postgrespro.ru>
Date: Wed, 28 Oct 2020 14:34:52 +0300
Subject: [PATCH 09/22] Issue #27: Add fix for Travis Ci failed tests.

---
 pg_variables.c | 40 ++++++++++++++++++++++++++++++++++++++--
 1 file changed, 38 insertions(+), 2 deletions(-)

diff --git a/pg_variables.c b/pg_variables.c
index 7e29b76..b22dd29 100644
--- a/pg_variables.c
+++ b/pg_variables.c
@@ -152,6 +152,7 @@ typedef struct tagHtabToStat {
 	HASH_SEQ_STATUS	*status;
 	Variable		*variable;
 	Package			*package;
+	int				level;
 } HtabToStat;
 
 /*
@@ -181,6 +182,12 @@ HtabToStat_eq_all(HtabToStat *entry, void *value)
 	return true;
 }
 
+static bool
+HtabToStat_level_eq(HtabToStat *entry, void *value)
+{
+	return entry->level == *(int *) value;
+}
+
 /*
  * Generic remove_if algorithm for HtabToStat.
  *
@@ -192,6 +199,7 @@ HtabToStat_remove_if(List **l, void *value,
 					 bool (*eq)(HtabToStat *, void *),
 					 bool match_first)
 {
+#if (PG_VERSION_NUM < 130000)
 	ListCell	*cell, *next, *prev = NULL;
 	HtabToStat	*entry = NULL;
 
@@ -214,6 +222,23 @@ HtabToStat_remove_if(List **l, void *value,
 			prev = cell;
 		}
 	}
+#else
+	/*
+	 * See https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=1cff1b95ab6ddae32faa3efe0d95a820dbfdc164
+	 *
+	 * Version > 13 have different lists interface.
+	 */
+	ListCell	*cell;
+	HtabToStat	*entry = NULL;
+
+	foreach(cell, *l)
+	{
+		entry = (HtabToStat *) lfirst(cell);
+
+		if (eq(entry, value))
+			*l = foreach_delete_current(*l, cell);
+	}
+#endif
 }
 
 /*
@@ -243,6 +268,15 @@ remove_variables_package(List **l, Package *package)
 	HtabToStat_remove_if(l, package, HtabToStat_package_eq, false);
 }
 
+/*
+ * Remove all the entrys for level.
+ */
+static void
+remove_variables_level(List **l, int level)
+{
+	HtabToStat_remove_if(l, &level, HtabToStat_level_eq, false);
+}
+
 /*
  * Remove all.
  */
@@ -757,6 +791,7 @@ variable_select(PG_FUNCTION_ARGS)
 		htab_to_stat->status = rstat;
 		htab_to_stat->variable = variable;
 		htab_to_stat->package = package;
+		htab_to_stat->level = GetCurrentTransactionNestLevel();
 		variables_stats = lcons((void *)htab_to_stat, variables_stats);
 
 		MemoryContextSwitchTo(oldcontext);
@@ -1356,7 +1391,6 @@ get_packages_stats(PG_FUNCTION_ARGS)
 	{
 		TupleDesc	tupdesc;
 
-		//elog(INFO, " >>> ");
 		funcctx = SRF_FIRSTCALL_INIT();
 		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
 
@@ -1432,7 +1466,7 @@ get_packages_stats(PG_FUNCTION_ARGS)
 	else
 	{
 		packages_stats = list_delete(packages_stats, rstat);
-		// pfree(rstat);
+		pfree(rstat);
 		SRF_RETURN_DONE(funcctx);
 	}
 }
@@ -2312,6 +2346,8 @@ pgvSubTransCallback(SubXactEvent event, SubTransactionId mySubid,
 				break;
 		}
 	}
+
+	remove_variables_level(&variables_stats, GetCurrentTransactionNestLevel());
 }
 
 /*

From 1a868834571e592bf2efadbc21e551285c856351 Mon Sep 17 00:00:00 2001
From: Maxim Orlov <m.orlov@postgrespro.ru>
Date: Wed, 28 Oct 2020 17:53:00 +0300
Subject: [PATCH 10/22] Issue #27: Fix tests for pgv_stats on 9.5

---
 expected/pg_variables_trans_1.out | 3655 +++++++++++++++++++++++++++++
 1 file changed, 3655 insertions(+)
 create mode 100644 expected/pg_variables_trans_1.out

diff --git a/expected/pg_variables_trans_1.out b/expected/pg_variables_trans_1.out
new file mode 100644
index 0000000..bc55b84
--- /dev/null
+++ b/expected/pg_variables_trans_1.out
@@ -0,0 +1,3655 @@
+SET timezone = 'Europe/Moscow'; -- Need to proper output of datetime variables
+--CHECK SAVEPOINT RELEASE
+BEGIN;
+-- Declare variables
+SELECT pgv_set('vars', 'any1', 'some value'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT pgv_set('vars', 'any2', 'some value'::text);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT pgv_set_int('vars', 'int1', 101, true);
+ pgv_set_int 
+-------------
+ 
+(1 row)
+
+SELECT pgv_set_int('vars', 'int2', 102);
+ pgv_set_int 
+-------------
+ 
+(1 row)
+
+SELECT pgv_set_int('vars', 'intNULL', NULL, true);
+ pgv_set_int 
+-------------
+ 
+(1 row)
+
+SELECT pgv_set_text('vars', 'str1', 's101', true);
+ pgv_set_text 
+--------------
+ 
+(1 row)
+
+SELECT pgv_set_text('vars', 'str2', 's102');
+ pgv_set_text 
+--------------
+ 
+(1 row)
+
+SELECT pgv_set_numeric('vars', 'num1', 1.01, true);
+ pgv_set_numeric 
+-----------------
+ 
+(1 row)
+
+SELECT pgv_set_numeric('vars', 'num2', 1.02);
+ pgv_set_numeric 
+-----------------
+ 
+(1 row)
+
+SELECT pgv_set_timestamp('vars', 'ts1', '2016-03-30 10:00:00', true);
+ pgv_set_timestamp 
+-------------------
+ 
+(1 row)
+
+SELECT pgv_set_timestamp('vars', 'ts2', '2016-03-30 11:00:00');
+ pgv_set_timestamp 
+-------------------
+ 
+(1 row)
+
+SELECT pgv_set_timestamptz('vars', 'tstz1', '2016-03-30 10:00:00 GMT+01', true);
+ pgv_set_timestamptz 
+---------------------
+ 
+(1 row)
+
+SELECT pgv_set_timestamptz('vars', 'tstz2', '2016-03-30 11:00:00 GMT+02');
+ pgv_set_timestamptz 
+---------------------
+ 
+(1 row)
+
+SELECT pgv_set_date('vars', 'd1', '2016-03-29', true);
+ pgv_set_date 
+--------------
+ 
+(1 row)
+
+SELECT pgv_set_date('vars', 'd2', '2016-03-30');
+ pgv_set_date 
+--------------
+ 
+(1 row)
+
+SELECT pgv_set_jsonb('vars2', 'j1', '[1, 2, "foo", null]', true);
+ pgv_set_jsonb 
+---------------
+ 
+(1 row)
+
+SELECT pgv_set_jsonb('vars2', 'j2', '{"bar": "baz", "balance": 7.77, "active": false}');
+ pgv_set_jsonb 
+---------------
+ 
+(1 row)
+
+SAVEPOINT comm;
+-- Set new values
+SELECT pgv_set('vars', 'any1', 'another value'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT pgv_set('vars', 'any2', 'another value'::text);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT pgv_set_int('vars', 'int1', 103, true);
+ pgv_set_int 
+-------------
+ 
+(1 row)
+
+SELECT pgv_set_int('vars', 'int2', 103);
+ pgv_set_int 
+-------------
+ 
+(1 row)
+
+SELECT pgv_set_int('vars', 'intNULL', 104, true);
+ pgv_set_int 
+-------------
+ 
+(1 row)
+
+SELECT pgv_set_text('vars', 'str1', 's103', true);
+ pgv_set_text 
+--------------
+ 
+(1 row)
+
+SELECT pgv_set_text('vars', 'str2', 's103');
+ pgv_set_text 
+--------------
+ 
+(1 row)
+
+SELECT pgv_set_numeric('vars', 'num1', 1.03, true);
+ pgv_set_numeric 
+-----------------
+ 
+(1 row)
+
+SELECT pgv_set_numeric('vars', 'num2', 1.03);
+ pgv_set_numeric 
+-----------------
+ 
+(1 row)
+
+SELECT pgv_set_timestamp('vars', 'ts1', '2016-03-30 12:00:00', true);
+ pgv_set_timestamp 
+-------------------
+ 
+(1 row)
+
+SELECT pgv_set_timestamp('vars', 'ts2', '2016-03-30 12:00:00');
+ pgv_set_timestamp 
+-------------------
+ 
+(1 row)
+
+SELECT pgv_set_timestamptz('vars', 'tstz1', '2016-03-30 12:00:00 GMT+03', true);
+ pgv_set_timestamptz 
+---------------------
+ 
+(1 row)
+
+SELECT pgv_set_timestamptz('vars', 'tstz2', '2016-03-30 12:00:00 GMT+03');
+ pgv_set_timestamptz 
+---------------------
+ 
+(1 row)
+
+SELECT pgv_set_date('vars', 'd1', '2016-04-02', true);
+ pgv_set_date 
+--------------
+ 
+(1 row)
+
+SELECT pgv_set_date('vars', 'd2', '2016-04-02');
+ pgv_set_date 
+--------------
+ 
+(1 row)
+
+SELECT pgv_set_jsonb('vars2', 'j1', '{"foo": [true, "bar"], "tags": {"a": 1, "b": null}}', true);
+ pgv_set_jsonb 
+---------------
+ 
+(1 row)
+
+SELECT pgv_set_jsonb('vars2', 'j2', '{"foo": [true, "bar"], "tags": {"a": 1, "b": null}}');
+ pgv_set_jsonb 
+---------------
+ 
+(1 row)
+
+-- Check values before releasing savepoint
+SELECT pgv_get('vars', 'any1',NULL::text);
+    pgv_get    
+---------------
+ another value
+(1 row)
+
+SELECT pgv_get('vars', 'any2',NULL::text);
+    pgv_get    
+---------------
+ another value
+(1 row)
+
+SELECT pgv_get_int('vars', 'int1');
+ pgv_get_int 
+-------------
+         103
+(1 row)
+
+SELECT pgv_get_int('vars', 'int2');
+ pgv_get_int 
+-------------
+         103
+(1 row)
+
+SELECT pgv_get_int('vars', 'intNULL');
+ pgv_get_int 
+-------------
+         104
+(1 row)
+
+SELECT pgv_get_text('vars', 'str1');
+ pgv_get_text 
+--------------
+ s103
+(1 row)
+
+SELECT pgv_get_text('vars', 'str2');
+ pgv_get_text 
+--------------
+ s103
+(1 row)
+
+SELECT pgv_get_numeric('vars', 'num1');
+ pgv_get_numeric 
+-----------------
+            1.03
+(1 row)
+
+SELECT pgv_get_numeric('vars', 'num2');
+ pgv_get_numeric 
+-----------------
+            1.03
+(1 row)
+
+SELECT pgv_get_timestamp('vars', 'ts1');
+    pgv_get_timestamp     
+--------------------------
+ Wed Mar 30 12:00:00 2016
+(1 row)
+
+SELECT pgv_get_timestamp('vars', 'ts2');
+    pgv_get_timestamp     
+--------------------------
+ Wed Mar 30 12:00:00 2016
+(1 row)
+
+SELECT pgv_get_timestamptz('vars', 'tstz1');
+     pgv_get_timestamptz      
+------------------------------
+ Wed Mar 30 18:00:00 2016 MSK
+(1 row)
+
+SELECT pgv_get_timestamptz('vars', 'tstz2');
+     pgv_get_timestamptz      
+------------------------------
+ Wed Mar 30 18:00:00 2016 MSK
+(1 row)
+
+SELECT pgv_get_date('vars', 'd1');
+ pgv_get_date 
+--------------
+ 04-02-2016
+(1 row)
+
+SELECT pgv_get_date('vars', 'd2');
+ pgv_get_date 
+--------------
+ 04-02-2016
+(1 row)
+
+SELECT pgv_get_jsonb('vars2', 'j1');
+                    pgv_get_jsonb                    
+-----------------------------------------------------
+ {"foo": [true, "bar"], "tags": {"a": 1, "b": null}}
+(1 row)
+
+SELECT pgv_get_jsonb('vars2', 'j2');
+                    pgv_get_jsonb                    
+-----------------------------------------------------
+ {"foo": [true, "bar"], "tags": {"a": 1, "b": null}}
+(1 row)
+
+-- Check values after releasing savepoint
+RELEASE comm;
+SELECT pgv_get('vars', 'any1',NULL::text);
+    pgv_get    
+---------------
+ another value
+(1 row)
+
+SELECT pgv_get('vars', 'any2',NULL::text);
+    pgv_get    
+---------------
+ another value
+(1 row)
+
+SELECT pgv_get_int('vars', 'int1');
+ pgv_get_int 
+-------------
+         103
+(1 row)
+
+SELECT pgv_get_int('vars', 'int2');
+ pgv_get_int 
+-------------
+         103
+(1 row)
+
+SELECT pgv_get_int('vars', 'intNULL');
+ pgv_get_int 
+-------------
+         104
+(1 row)
+
+SELECT pgv_get_text('vars', 'str1');
+ pgv_get_text 
+--------------
+ s103
+(1 row)
+
+SELECT pgv_get_text('vars', 'str2');
+ pgv_get_text 
+--------------
+ s103
+(1 row)
+
+SELECT pgv_get_numeric('vars', 'num1');
+ pgv_get_numeric 
+-----------------
+            1.03
+(1 row)
+
+SELECT pgv_get_numeric('vars', 'num2');
+ pgv_get_numeric 
+-----------------
+            1.03
+(1 row)
+
+SELECT pgv_get_timestamp('vars', 'ts1');
+    pgv_get_timestamp     
+--------------------------
+ Wed Mar 30 12:00:00 2016
+(1 row)
+
+SELECT pgv_get_timestamp('vars', 'ts2');
+    pgv_get_timestamp     
+--------------------------
+ Wed Mar 30 12:00:00 2016
+(1 row)
+
+SELECT pgv_get_timestamptz('vars', 'tstz1');
+     pgv_get_timestamptz      
+------------------------------
+ Wed Mar 30 18:00:00 2016 MSK
+(1 row)
+
+SELECT pgv_get_timestamptz('vars', 'tstz2');
+     pgv_get_timestamptz      
+------------------------------
+ Wed Mar 30 18:00:00 2016 MSK
+(1 row)
+
+SELECT pgv_get_date('vars', 'd1');
+ pgv_get_date 
+--------------
+ 04-02-2016
+(1 row)
+
+SELECT pgv_get_date('vars', 'd2');
+ pgv_get_date 
+--------------
+ 04-02-2016
+(1 row)
+
+SELECT pgv_get_jsonb('vars2', 'j1');
+                    pgv_get_jsonb                    
+-----------------------------------------------------
+ {"foo": [true, "bar"], "tags": {"a": 1, "b": null}}
+(1 row)
+
+SELECT pgv_get_jsonb('vars2', 'j2');
+                    pgv_get_jsonb                    
+-----------------------------------------------------
+ {"foo": [true, "bar"], "tags": {"a": 1, "b": null}}
+(1 row)
+
+COMMIT;
+CREATE TABLE tab (id int, t varchar);
+INSERT INTO tab VALUES (0, 'str00'), (1, 'str33'), (2, NULL), (NULL, 'strNULL');
+BEGIN;
+SELECT pgv_insert('vars3', 'r1', tab, true) FROM tab;
+ pgv_insert 
+------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT pgv_insert('vars3', 'r2', tab) FROM tab;
+ pgv_insert 
+------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SAVEPOINT comm;
+SELECT pgv_insert('vars3', 'r1', row(5 :: integer, 'str55' :: varchar),true);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('vars3', 'r2', row(5 :: integer, 'str55' :: varchar));
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('vars3', 'r1');
+ pgv_select 
+------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (5,str55)
+ (0,str00)
+(5 rows)
+
+SELECT pgv_select('vars3', 'r2');
+ pgv_select 
+------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (5,str55)
+ (0,str00)
+(5 rows)
+
+RELEASE comm;
+SELECT pgv_select('vars3', 'r1');
+ pgv_select 
+------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (5,str55)
+ (0,str00)
+(5 rows)
+
+SELECT pgv_select('vars3', 'r2');
+ pgv_select 
+------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (5,str55)
+ (0,str00)
+(5 rows)
+
+COMMIT;
+--CHECK SAVEPOINT ROLLBACK
+BEGIN;
+-- Variables are already declared
+SAVEPOINT comm2;
+-- Set new values
+SELECT pgv_set('vars', 'any1', 'one more value'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT pgv_set('vars', 'any2', 'one more value'::text);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT pgv_set_int('vars', 'int1', 101, true);
+ pgv_set_int 
+-------------
+ 
+(1 row)
+
+SELECT pgv_set_int('vars', 'int2', 102);
+ pgv_set_int 
+-------------
+ 
+(1 row)
+
+SELECT pgv_set_int('vars', 'intNULL', NULL, true);
+ pgv_set_int 
+-------------
+ 
+(1 row)
+
+SELECT pgv_set_text('vars', 'str1', 's101', true);
+ pgv_set_text 
+--------------
+ 
+(1 row)
+
+SELECT pgv_set_text('vars', 'str2', 's102');
+ pgv_set_text 
+--------------
+ 
+(1 row)
+
+SELECT pgv_set_numeric('vars', 'num1', 1.01, true);
+ pgv_set_numeric 
+-----------------
+ 
+(1 row)
+
+SELECT pgv_set_numeric('vars', 'num2', 1.02);
+ pgv_set_numeric 
+-----------------
+ 
+(1 row)
+
+SELECT pgv_set_timestamp('vars', 'ts1', '2016-03-30 10:00:00', true);
+ pgv_set_timestamp 
+-------------------
+ 
+(1 row)
+
+SELECT pgv_set_timestamp('vars', 'ts2', '2016-03-30 11:00:00');
+ pgv_set_timestamp 
+-------------------
+ 
+(1 row)
+
+SELECT pgv_set_timestamptz('vars', 'tstz1', '2016-03-30 10:00:00 GMT+01', true);
+ pgv_set_timestamptz 
+---------------------
+ 
+(1 row)
+
+SELECT pgv_set_timestamptz('vars', 'tstz2', '2016-03-30 11:00:00 GMT+02');
+ pgv_set_timestamptz 
+---------------------
+ 
+(1 row)
+
+SELECT pgv_set_date('vars', 'd1', '2016-03-29', true);
+ pgv_set_date 
+--------------
+ 
+(1 row)
+
+SELECT pgv_set_date('vars', 'd2', '2016-03-30');
+ pgv_set_date 
+--------------
+ 
+(1 row)
+
+SELECT pgv_set_jsonb('vars2', 'j1', '[1, 2, "foo", null]', true);
+ pgv_set_jsonb 
+---------------
+ 
+(1 row)
+
+SELECT pgv_set_jsonb('vars2', 'j2', '{"bar": "baz", "balance": 7.77, "active": false}');
+ pgv_set_jsonb 
+---------------
+ 
+(1 row)
+
+-- Check values before rollback to savepoint
+SELECT pgv_get('vars', 'any1',NULL::text);
+    pgv_get     
+----------------
+ one more value
+(1 row)
+
+SELECT pgv_get('vars', 'any2',NULL::text);
+    pgv_get     
+----------------
+ one more value
+(1 row)
+
+SELECT pgv_get_int('vars', 'int1');
+ pgv_get_int 
+-------------
+         101
+(1 row)
+
+SELECT pgv_get_int('vars', 'int2');
+ pgv_get_int 
+-------------
+         102
+(1 row)
+
+SELECT pgv_get_int('vars', 'intNULL');
+ pgv_get_int 
+-------------
+            
+(1 row)
+
+SELECT pgv_get_text('vars', 'str1');
+ pgv_get_text 
+--------------
+ s101
+(1 row)
+
+SELECT pgv_get_text('vars', 'str2');
+ pgv_get_text 
+--------------
+ s102
+(1 row)
+
+SELECT pgv_get_numeric('vars', 'num1');
+ pgv_get_numeric 
+-----------------
+            1.01
+(1 row)
+
+SELECT pgv_get_numeric('vars', 'num2');
+ pgv_get_numeric 
+-----------------
+            1.02
+(1 row)
+
+SELECT pgv_get_timestamp('vars', 'ts1');
+    pgv_get_timestamp     
+--------------------------
+ Wed Mar 30 10:00:00 2016
+(1 row)
+
+SELECT pgv_get_timestamp('vars', 'ts2');
+    pgv_get_timestamp     
+--------------------------
+ Wed Mar 30 11:00:00 2016
+(1 row)
+
+SELECT pgv_get_timestamptz('vars', 'tstz1');
+     pgv_get_timestamptz      
+------------------------------
+ Wed Mar 30 14:00:00 2016 MSK
+(1 row)
+
+SELECT pgv_get_timestamptz('vars', 'tstz2');
+     pgv_get_timestamptz      
+------------------------------
+ Wed Mar 30 16:00:00 2016 MSK
+(1 row)
+
+SELECT pgv_get_date('vars', 'd1');
+ pgv_get_date 
+--------------
+ 03-29-2016
+(1 row)
+
+SELECT pgv_get_date('vars', 'd2');
+ pgv_get_date 
+--------------
+ 03-30-2016
+(1 row)
+
+SELECT pgv_get_jsonb('vars2', 'j1');
+    pgv_get_jsonb    
+---------------------
+ [1, 2, "foo", null]
+(1 row)
+
+SELECT pgv_get_jsonb('vars2', 'j2');
+                  pgv_get_jsonb                   
+--------------------------------------------------
+ {"bar": "baz", "active": false, "balance": 7.77}
+(1 row)
+
+-- Check values after rollback to savepoint
+ROLLBACK TO comm2;
+SELECT pgv_get('vars', 'any1',NULL::text);
+    pgv_get    
+---------------
+ another value
+(1 row)
+
+SELECT pgv_get('vars', 'any2',NULL::text);
+    pgv_get     
+----------------
+ one more value
+(1 row)
+
+SELECT pgv_get_int('vars', 'int1');
+ pgv_get_int 
+-------------
+         103
+(1 row)
+
+SELECT pgv_get_int('vars', 'int2');
+ pgv_get_int 
+-------------
+         102
+(1 row)
+
+SELECT pgv_get_int('vars', 'intNULL');
+ pgv_get_int 
+-------------
+         104
+(1 row)
+
+SELECT pgv_get_text('vars', 'str1');
+ pgv_get_text 
+--------------
+ s103
+(1 row)
+
+SELECT pgv_get_text('vars', 'str2');
+ pgv_get_text 
+--------------
+ s102
+(1 row)
+
+SELECT pgv_get_numeric('vars', 'num1');
+ pgv_get_numeric 
+-----------------
+            1.03
+(1 row)
+
+SELECT pgv_get_numeric('vars', 'num2');
+ pgv_get_numeric 
+-----------------
+            1.02
+(1 row)
+
+SELECT pgv_get_timestamp('vars', 'ts1');
+    pgv_get_timestamp     
+--------------------------
+ Wed Mar 30 12:00:00 2016
+(1 row)
+
+SELECT pgv_get_timestamp('vars', 'ts2');
+    pgv_get_timestamp     
+--------------------------
+ Wed Mar 30 11:00:00 2016
+(1 row)
+
+SELECT pgv_get_timestamptz('vars', 'tstz1');
+     pgv_get_timestamptz      
+------------------------------
+ Wed Mar 30 18:00:00 2016 MSK
+(1 row)
+
+SELECT pgv_get_timestamptz('vars', 'tstz2');
+     pgv_get_timestamptz      
+------------------------------
+ Wed Mar 30 16:00:00 2016 MSK
+(1 row)
+
+SELECT pgv_get_date('vars', 'd1');
+ pgv_get_date 
+--------------
+ 04-02-2016
+(1 row)
+
+SELECT pgv_get_date('vars', 'd2');
+ pgv_get_date 
+--------------
+ 03-30-2016
+(1 row)
+
+SELECT pgv_get_jsonb('vars2', 'j1');
+                    pgv_get_jsonb                    
+-----------------------------------------------------
+ {"foo": [true, "bar"], "tags": {"a": 1, "b": null}}
+(1 row)
+
+SELECT pgv_get_jsonb('vars2', 'j2');
+                  pgv_get_jsonb                   
+--------------------------------------------------
+ {"bar": "baz", "active": false, "balance": 7.77}
+(1 row)
+
+COMMIT;
+-- Record variables
+BEGIN;
+SAVEPOINT comm2;
+SELECT pgv_delete('vars3', 'r1', 5);
+ pgv_delete 
+------------
+ t
+(1 row)
+
+SELECT pgv_delete('vars3', 'r2', 5);
+ pgv_delete 
+------------
+ t
+(1 row)
+
+SELECT pgv_select('vars3', 'r1');
+ pgv_select 
+------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (0,str00)
+(4 rows)
+
+SELECT pgv_select('vars3', 'r2');
+ pgv_select 
+------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (0,str00)
+(4 rows)
+
+ROLLBACK to comm2;
+SELECT pgv_select('vars3', 'r1');
+ pgv_select 
+------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (5,str55)
+ (0,str00)
+(5 rows)
+
+SELECT pgv_select('vars3', 'r2');
+ pgv_select 
+------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (0,str00)
+(4 rows)
+
+COMMIT;
+-- TRYING TO CHANGE FLAG 'IS_TRANSACTIONAL'
+SELECT pgv_set('vars', 'any1', 'value'::text);
+ERROR:  variable "any1" already created as TRANSACTIONAL
+SELECT pgv_set('vars', 'any2', 'value'::text, true);
+ERROR:  variable "any2" already created as NOT TRANSACTIONAL
+SELECT pgv_set_int('vars', 'int1', 301);
+ERROR:  variable "int1" already created as TRANSACTIONAL
+SELECT pgv_set_int('vars', 'int2', 302, true);
+ERROR:  variable "int2" already created as NOT TRANSACTIONAL
+SELECT pgv_set_text('vars', 'str1', 's301');
+ERROR:  variable "str1" already created as TRANSACTIONAL
+SELECT pgv_set_text('vars', 'str2', 's302', true);
+ERROR:  variable "str2" already created as NOT TRANSACTIONAL
+SELECT pgv_set_numeric('vars', 'num1', 3.01);
+ERROR:  variable "num1" already created as TRANSACTIONAL
+SELECT pgv_set_numeric('vars', 'num2', 3.02, true);
+ERROR:  variable "num2" already created as NOT TRANSACTIONAL
+SELECT pgv_set_timestamp('vars', 'ts1', '2016-03-30 20:00:00');
+ERROR:  variable "ts1" already created as TRANSACTIONAL
+SELECT pgv_set_timestamp('vars', 'ts2', '2016-03-30 21:00:00', true);
+ERROR:  variable "ts2" already created as NOT TRANSACTIONAL
+SELECT pgv_set_timestamptz('vars', 'tstz1', '2016-03-30 20:00:00 GMT+01');
+ERROR:  variable "tstz1" already created as TRANSACTIONAL
+SELECT pgv_set_timestamptz('vars', 'tstz2', '2016-03-30 21:00:00 GMT+02', true);
+ERROR:  variable "tstz2" already created as NOT TRANSACTIONAL
+SELECT pgv_set_date('vars', 'd1', '2016-04-29');
+ERROR:  variable "d1" already created as TRANSACTIONAL
+SELECT pgv_set_date('vars', 'd2', '2016-04-30', true);
+ERROR:  variable "d2" already created as NOT TRANSACTIONAL
+SELECT pgv_set_jsonb('vars2', 'j1', '[1, 2, "foo2", null]');
+ERROR:  variable "j1" already created as TRANSACTIONAL
+SELECT pgv_set_jsonb('vars2', 'j2', '{"bar": "baz2", "balance": 7.77, "active": true}', true);
+ERROR:  variable "j2" already created as NOT TRANSACTIONAL
+SELECT pgv_insert('vars3', 'r1', row(6 :: integer, 'str66' :: varchar));
+ERROR:  variable "r1" already created as TRANSACTIONAL
+SELECT pgv_insert('vars3', 'r2', row(6 :: integer, 'str66' :: varchar),true);
+ERROR:  variable "r2" already created as NOT TRANSACTIONAL
+-- CHECK pgv_list() WHILE WE HAVE A LOT OF MISCELLANEOUS VARIABLES
+SELECT * FROM pgv_list() order by package, name;
+ package |  name   | is_transactional 
+---------+---------+------------------
+ vars    | any1    | t
+ vars    | any2    | f
+ vars    | d1      | t
+ vars    | d2      | f
+ vars    | int1    | t
+ vars    | int2    | f
+ vars    | intNULL | t
+ vars    | num1    | t
+ vars    | num2    | f
+ vars    | str1    | t
+ vars    | str2    | f
+ vars    | ts1     | t
+ vars    | ts2     | f
+ vars    | tstz1   | t
+ vars    | tstz2   | f
+ vars2   | j1      | t
+ vars2   | j2      | f
+ vars3   | r1      | t
+ vars3   | r2      | f
+(19 rows)
+
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+-- VARIABLES DECLARED IN SUBTRANSACTION SHOULD BE DESTROYED AFTER ROLLBACK TO SAVEPOINT
+-- For better readability we don't use deprecated api functions in test below
+BEGIN;
+SAVEPOINT sp_to_rollback;
+SELECT pgv_set('vars', 'any1', 'text value'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT pgv_set('vars', 'any2', 'text value'::text);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT pgv_insert('vars3', 'r1', row(6 :: integer, 'str44' :: varchar), true);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('vars3', 'r2', row(6 :: integer, 'str44' :: varchar));
+ pgv_insert 
+------------
+ 
+(1 row)
+
+ROLLBACK TO sp_to_rollback;
+COMMIT;
+SELECT pgv_get('vars', 'any1',NULL::text);
+ERROR:  unrecognized variable "any1"
+SELECT pgv_get('vars', 'any2',NULL::text);
+  pgv_get   
+------------
+ text value
+(1 row)
+
+SELECT pgv_select('vars3', 'r1');
+ERROR:  unrecognized variable "r1"
+SELECT pgv_select('vars3', 'r2');
+ pgv_select 
+------------
+ (6,str44)
+(1 row)
+
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+-- CHECK ROLLBACK AFTER COMMITTING SUBTRANSACTION
+BEGIN;
+SELECT pgv_set('vars', 'any1', 'before savepoint sp1'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SAVEPOINT sp1;
+SELECT pgv_set('vars', 'any1', 'after savepoint sp1'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SAVEPOINT sp2;
+SELECT pgv_set('vars', 'any1', 'after savepoint sp2'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+RELEASE sp2;
+SELECT pgv_get('vars', 'any1',NULL::text);
+       pgv_get       
+---------------------
+ after savepoint sp2
+(1 row)
+
+ROLLBACK TO sp1;
+SELECT pgv_get('vars', 'any1',NULL::text);
+       pgv_get        
+----------------------
+ before savepoint sp1
+(1 row)
+
+COMMIT;
+BEGIN;
+SAVEPOINT sp1;
+SAVEPOINT sp2;
+SELECT pgv_set('vars2', 'any1', 'variable exists'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+RELEASE sp2;
+SELECT pgv_get('vars2', 'any1',NULL::text);
+     pgv_get     
+-----------------
+ variable exists
+(1 row)
+
+ROLLBACK TO sp1;
+COMMIT;
+SELECT pgv_get('vars2', 'any1',NULL::text);
+ERROR:  unrecognized package "vars2"
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+--CHECK TRANSACTION COMMIT
+-- Declare variables
+SELECT pgv_set('vars', 'any1', 'some value'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT pgv_set('vars', 'any2', 'some value'::text);
+ pgv_set 
+---------
+ 
+(1 row)
+
+BEGIN;
+-- Set new values
+SELECT pgv_set('vars', 'any1', 'another value'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT pgv_set('vars', 'any2', 'another value'::text);
+ pgv_set 
+---------
+ 
+(1 row)
+
+-- Check values before committing transaction
+SELECT pgv_get('vars', 'any1',NULL::text);
+    pgv_get    
+---------------
+ another value
+(1 row)
+
+SELECT pgv_get('vars', 'any2',NULL::text);
+    pgv_get    
+---------------
+ another value
+(1 row)
+
+-- Check values after committing transaction
+COMMIT;
+SELECT pgv_get('vars', 'any1',NULL::text);
+    pgv_get    
+---------------
+ another value
+(1 row)
+
+SELECT pgv_get('vars', 'any2',NULL::text);
+    pgv_get    
+---------------
+ another value
+(1 row)
+
+SELECT pgv_insert('vars3', 'r1', tab, true) FROM tab;
+ pgv_insert 
+------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT pgv_insert('vars3', 'r2', tab) FROM tab;
+ pgv_insert 
+------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+BEGIN;
+SELECT pgv_insert('vars3', 'r1', row(5 :: integer, 'str55' :: varchar),true);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('vars3', 'r2', row(5 :: integer, 'str55' :: varchar));
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('vars3', 'r1');
+ pgv_select 
+------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (5,str55)
+ (0,str00)
+(5 rows)
+
+SELECT pgv_select('vars3', 'r2');
+ pgv_select 
+------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (5,str55)
+ (0,str00)
+(5 rows)
+
+COMMIT;
+SELECT pgv_select('vars3', 'r1');
+ pgv_select 
+------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (5,str55)
+ (0,str00)
+(5 rows)
+
+SELECT pgv_select('vars3', 'r2');
+ pgv_select 
+------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (5,str55)
+ (0,str00)
+(5 rows)
+
+-- CHECK TRANSACTION ROLLBACK
+-- Variables are already declared
+BEGIN;
+-- Set new values
+SELECT pgv_set('vars', 'any1', 'one more value'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT pgv_set('vars', 'any2', 'one more value'::text);
+ pgv_set 
+---------
+ 
+(1 row)
+
+-- Check values before rollback
+SELECT pgv_get('vars', 'any1',NULL::text);
+    pgv_get     
+----------------
+ one more value
+(1 row)
+
+SELECT pgv_get('vars', 'any2',NULL::text);
+    pgv_get     
+----------------
+ one more value
+(1 row)
+
+-- Check values after rollback
+ROLLBACK;
+SELECT pgv_get('vars', 'any1',NULL::text);
+    pgv_get    
+---------------
+ another value
+(1 row)
+
+SELECT pgv_get('vars', 'any2',NULL::text);
+    pgv_get     
+----------------
+ one more value
+(1 row)
+
+-- Record variables
+BEGIN;
+SELECT pgv_delete('vars3', 'r1', 5);
+ pgv_delete 
+------------
+ t
+(1 row)
+
+SELECT pgv_delete('vars3', 'r2', 5);
+ pgv_delete 
+------------
+ t
+(1 row)
+
+SELECT pgv_select('vars3', 'r1');
+ pgv_select 
+------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (0,str00)
+(4 rows)
+
+SELECT pgv_select('vars3', 'r2');
+ pgv_select 
+------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (0,str00)
+(4 rows)
+
+ROLLBACK;
+SELECT pgv_select('vars3', 'r1');
+ pgv_select 
+------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (5,str55)
+ (0,str00)
+(5 rows)
+
+SELECT pgv_select('vars3', 'r2');
+ pgv_select 
+------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (0,str00)
+(4 rows)
+
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+-- VARIABLES DECLARED IN TRANSACTION SHOULD BE DESTROYED AFTER ROLLBACK
+BEGIN;
+SELECT pgv_set('vars', 'any1', 'text value'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT pgv_set('vars', 'any2', 'text value'::text);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT pgv_insert('vars', 'r1', row(6 :: integer, 'str44' :: varchar), true);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('vars', 'r2', row(6 :: integer, 'str44' :: varchar));
+ pgv_insert 
+------------
+ 
+(1 row)
+
+ROLLBACK;
+SELECT pgv_get('vars', 'any1',NULL::text);
+ERROR:  unrecognized variable "any1"
+SELECT pgv_get('vars', 'any2',NULL::text);
+  pgv_get   
+------------
+ text value
+(1 row)
+
+SELECT pgv_select('vars', 'r1');
+ERROR:  unrecognized variable "r1"
+SELECT pgv_select('vars', 'r2');
+ pgv_select 
+------------
+ (6,str44)
+(1 row)
+
+SELECT pgv_remove('vars');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+-- CHECK ROLLBACK AFTER COMMITTING SUBTRANSACTION
+SELECT pgv_set('vars', 'any1', 'before transaction block'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+BEGIN;
+SELECT pgv_set('vars', 'any1', 'before savepoint sp1'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SAVEPOINT sp1;
+SELECT pgv_set('vars', 'any1', 'after savepoint sp1'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SAVEPOINT sp2;
+SELECT pgv_set('vars', 'any1', 'after savepoint sp2'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+RELEASE sp2;
+SELECT pgv_get('vars', 'any1',NULL::text);
+       pgv_get       
+---------------------
+ after savepoint sp2
+(1 row)
+
+ROLLBACK TO sp1;
+SELECT pgv_get('vars', 'any1',NULL::text);
+       pgv_get        
+----------------------
+ before savepoint sp1
+(1 row)
+
+ROLLBACK;
+SELECT pgv_get('vars', 'any1',NULL::text);
+         pgv_get          
+--------------------------
+ before transaction block
+(1 row)
+
+BEGIN;
+SAVEPOINT sp1;
+SELECT pgv_set('vars2', 'any1', 'variable exists'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+RELEASE sp1;
+SELECT pgv_get('vars2', 'any1',NULL::text);
+     pgv_get     
+-----------------
+ variable exists
+(1 row)
+
+ROLLBACK;
+SELECT pgv_get('vars2', 'any1',NULL::text);
+ERROR:  unrecognized package "vars2"
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+-- Additional tests
+SELECT pgv_insert('vars3', 'r1', tab, true) FROM tab;
+ pgv_insert 
+------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+BEGIN;
+SELECT pgv_insert('vars3', 'r1', row(5 :: integer, 'before savepoint sp1' :: varchar),true);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SAVEPOINT sp1;
+SELECT pgv_update('vars3', 'r1', row(5 :: integer, 'after savepoint sp1' :: varchar));
+ pgv_update 
+------------
+ t
+(1 row)
+
+SAVEPOINT sp2;
+SELECT pgv_insert('vars3', 'r1', row(7 :: integer, 'row after sp2 to remove in sp4' :: varchar),true);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SAVEPOINT sp3;
+SAVEPOINT sp4;
+SELECT pgv_delete('vars3', 'r1', 7);
+ pgv_delete 
+------------
+ t
+(1 row)
+
+SAVEPOINT sp5;
+SELECT pgv_select('vars3', 'r1');
+        pgv_select         
+---------------------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (5,"after savepoint sp1")
+ (0,str00)
+(5 rows)
+
+ROLLBACK TO sp5;
+SELECT pgv_select('vars3', 'r1');
+        pgv_select         
+---------------------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (5,"after savepoint sp1")
+ (0,str00)
+(5 rows)
+
+RELEASE sp4;
+SELECT pgv_select('vars3', 'r1');
+        pgv_select         
+---------------------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (5,"after savepoint sp1")
+ (0,str00)
+(5 rows)
+
+ROLLBACK TO sp3;
+SELECT pgv_select('vars3', 'r1');
+              pgv_select              
+--------------------------------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (5,"after savepoint sp1")
+ (0,str00)
+ (7,"row after sp2 to remove in sp4")
+(6 rows)
+
+RELEASE sp2;
+SELECT pgv_select('vars3', 'r1');
+              pgv_select              
+--------------------------------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (5,"after savepoint sp1")
+ (0,str00)
+ (7,"row after sp2 to remove in sp4")
+(6 rows)
+
+ROLLBACK TO sp1;
+SELECT pgv_select('vars3', 'r1');
+         pgv_select         
+----------------------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (5,"before savepoint sp1")
+ (0,str00)
+(5 rows)
+
+COMMIT;
+SELECT pgv_select('vars3', 'r1');
+         pgv_select         
+----------------------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (5,"before savepoint sp1")
+ (0,str00)
+(5 rows)
+
+SELECT pgv_set('vars', 'any1', 'outer'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+BEGIN;
+SELECT pgv_set('vars', 'any1', 'begin'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SAVEPOINT sp1;
+SELECT pgv_set('vars', 'any1', 'sp1'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SAVEPOINT sp2;
+SELECT pgv_set('vars', 'any1', 'sp2'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SAVEPOINT sp3;
+SAVEPOINT sp4;
+SELECT pgv_set('vars', 'any1', 'sp4'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SAVEPOINT sp5;
+SELECT pgv_get('vars', 'any1',NULL::text);
+ pgv_get 
+---------
+ sp4
+(1 row)
+
+ROLLBACK TO sp5;
+SELECT pgv_get('vars', 'any1',NULL::text);
+ pgv_get 
+---------
+ sp4
+(1 row)
+
+RELEASE sp4;
+SELECT pgv_get('vars', 'any1',NULL::text);
+ pgv_get 
+---------
+ sp4
+(1 row)
+
+ROLLBACK TO sp3;
+SELECT pgv_get('vars', 'any1',NULL::text);
+ pgv_get 
+---------
+ sp2
+(1 row)
+
+RELEASE sp2;
+SELECT pgv_get('vars', 'any1',NULL::text);
+ pgv_get 
+---------
+ sp2
+(1 row)
+
+ROLLBACK TO sp1;
+SELECT pgv_get('vars', 'any1',NULL::text);
+ pgv_get 
+---------
+ begin
+(1 row)
+
+ROLLBACK;
+SELECT pgv_get('vars', 'any1',NULL::text);
+ pgv_get 
+---------
+ outer
+(1 row)
+
+BEGIN;
+SELECT pgv_set('vars', 'any1', 'wrong type'::varchar, true);
+ERROR:  variable "any1" requires "text" value
+COMMIT;
+-- THE REMOVAL OF THE VARIABLE MUST BE CANCELED ON ROLLBACK
+SELECT pgv_set('vars', 'any1', 'variable exists'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+BEGIN;
+SELECT pgv_remove('vars', 'any1');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+SELECT pgv_exists('vars', 'any1');
+ pgv_exists 
+------------
+ f
+(1 row)
+
+ROLLBACK;
+SELECT pgv_exists('vars', 'any1');
+ pgv_exists 
+------------
+ t
+(1 row)
+
+SELECT pgv_get('vars', 'any1',NULL::text);
+     pgv_get     
+-----------------
+ variable exists
+(1 row)
+
+BEGIN;
+SELECT pgv_remove('vars', 'any1');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+SELECT pgv_exists('vars', 'any1');
+ pgv_exists 
+------------
+ f
+(1 row)
+
+COMMIT;
+SELECT pgv_exists('vars', 'any1');
+ pgv_exists 
+------------
+ f
+(1 row)
+
+SELECT pgv_get('vars', 'any1',NULL::text);
+ERROR:  unrecognized package "vars"
+SELECT * FROM pgv_list() ORDER BY package, name;
+ package | name | is_transactional 
+---------+------+------------------
+ vars3   | r1   | t
+(1 row)
+
+BEGIN;
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+ROLLBACK;
+SELECT * FROM pgv_list() ORDER BY package, name;
+ package | name | is_transactional 
+---------+------+------------------
+ vars3   | r1   | t
+(1 row)
+
+BEGIN;
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+COMMIT;
+SELECT * FROM pgv_list() ORDER BY package, name;
+ package | name | is_transactional 
+---------+------+------------------
+(0 rows)
+
+SELECT pgv_set('vars', 'regular', 'regular variable exists'::text);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT pgv_set('vars', 'trans1', 'trans1 variable exists'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+BEGIN;
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+SELECT pgv_free(); -- Check sequential package removal in one subtransaction
+ pgv_free 
+----------
+ 
+(1 row)
+
+SELECT * FROM pgv_list() ORDER BY package, name;
+ package | name | is_transactional 
+---------+------+------------------
+(0 rows)
+
+SELECT pgv_set('vars', 'trans2', 'trans2 variable exists'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT * FROM pgv_list() ORDER BY package, name;
+ package |  name  | is_transactional 
+---------+--------+------------------
+ vars    | trans2 | t
+(1 row)
+
+SELECT pgv_remove('vars');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+SELECT * FROM pgv_list() ORDER BY package, name;
+ package | name | is_transactional 
+---------+------+------------------
+(0 rows)
+
+ROLLBACK;
+SELECT * FROM pgv_list() ORDER BY package, name;
+ package |  name  | is_transactional 
+---------+--------+------------------
+ vars    | trans1 | t
+(1 row)
+
+BEGIN;
+SAVEPOINT sp1;
+SAVEPOINT sp2;
+SAVEPOINT sp3;
+SELECT pgv_set('vars2', 'trans2', 'trans2 variable exists'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SAVEPOINT sp4;
+SAVEPOINT sp5;
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+SELECT package FROM pgv_stats() ORDER BY package;
+ package 
+---------
+ vars
+ vars2
+(2 rows)
+
+SELECT * FROM pgv_list() ORDER BY package, name;
+ package | name | is_transactional 
+---------+------+------------------
+(0 rows)
+
+RELEASE sp5;
+SELECT package FROM pgv_stats() ORDER BY package;
+ package 
+---------
+ vars
+ vars2
+(2 rows)
+
+SELECT * FROM pgv_list() ORDER BY package, name;
+ package | name | is_transactional 
+---------+------+------------------
+(0 rows)
+
+RELEASE sp4;
+SELECT package FROM pgv_stats() ORDER BY package;
+ package 
+---------
+ vars
+ vars2
+(2 rows)
+
+SELECT * FROM pgv_list() ORDER BY package, name;
+ package | name | is_transactional 
+---------+------+------------------
+(0 rows)
+
+COMMIT;
+SELECT package FROM pgv_stats() ORDER BY package;
+ package 
+---------
+(0 rows)
+
+BEGIN;
+SELECT pgv_set('vars', 'trans1', 'package created'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT pgv_remove('vars');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+SELECT * FROM pgv_list() ORDER BY package, name;
+ package | name | is_transactional 
+---------+------+------------------
+(0 rows)
+
+SELECT pgv_set('vars', 'trans1', 'package restored'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT * FROM pgv_list() ORDER BY package, name;
+ package |  name  | is_transactional 
+---------+--------+------------------
+ vars    | trans1 | t
+(1 row)
+
+COMMIT;
+SELECT pgv_remove('vars');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+-- REMOVED TRANSACTIONAL VARIABLE SHOULD BE NOT ACCESSIBLE THROUGH LastVariable
+SELECT pgv_insert('package', 'errs',row(n), true)
+FROM generate_series(1,5) AS gs(n) WHERE 1.0/(n-3)<>0;
+ERROR:  division by zero
+SELECT pgv_insert('package', 'errs',row(1), true);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+-- Variable should not exists in case when error occurs during creation
+SELECT pgv_insert('vars4', 'r1', row('str1', 'str1'));
+ERROR:  could not identify a hash function for type unknown
+SELECT pgv_select('vars4', 'r1', 0);
+ERROR:  unrecognized package "vars4"
+-- If variable created and removed in same transaction level,
+-- it should be totally removed and should not be present
+-- in changes list and cache.
+BEGIN;
+SELECT pgv_set('vars', 'any1', 'some value'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SAVEPOINT comm;
+SELECT pgv_remove('vars', 'any1');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+RELEASE comm;
+SELECT pgv_get('vars', 'any1',NULL::text);
+ERROR:  unrecognized package "vars"
+COMMIT;
+-- Tests for PGPRO-2440
+SELECT pgv_insert('vars3', 'r3', row(1 :: integer, NULL::varchar), true);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+SELECT pgv_insert('vars3', 'r3', row(2 :: integer, NULL::varchar), true);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SAVEPOINT comm;
+SELECT pgv_insert('vars3', 'r3', row(3 :: integer, NULL::varchar), true);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+COMMIT;
+SELECT pgv_delete('vars3', 'r3', 3);
+ pgv_delete 
+------------
+ t
+(1 row)
+
+BEGIN;
+SELECT pgv_set('vars1', 't1', ''::text);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT pgv_set('vars2', 't2', ''::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SAVEPOINT sp1;
+SAVEPOINT sp2;
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+ERROR;
+ERROR:  syntax error at or near "ERROR"
+LINE 1: ERROR;
+        ^
+COMMIT;
+BEGIN;
+SELECT pgv_set('vars', 'any1', 'some value'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+SAVEPOINT sp_to_rollback;
+SELECT pgv_set('vars', 'any1', 'some value'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+ROLLBACK TO sp_to_rollback;
+COMMIT;
+SELECT package FROM pgv_stats() ORDER BY package;
+ package 
+---------
+(0 rows)
+
+-- Package should exist after rollback if it contains regular variable
+BEGIN;
+SELECT pgv_set('vars', 'any1', 'some value'::text);
+ pgv_set 
+---------
+ 
+(1 row)
+
+ROLLBACK;
+SELECT package FROM pgv_stats() ORDER BY package;
+ package 
+---------
+ vars
+(1 row)
+
+-- Package should not exist if it becomes empty in rolled back transaction
+BEGIN;
+SAVEPOINT comm2;
+SELECT pgv_remove('vars');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+ROLLBACK TO comm2;
+SELECT pgv_exists('vars');
+ pgv_exists 
+------------
+ f
+(1 row)
+
+SELECT package FROM pgv_stats() ORDER BY package;
+ package 
+---------
+ vars
+(1 row)
+
+COMMIT;
+SELECT package FROM pgv_stats() ORDER BY package;
+ package 
+---------
+(0 rows)
+
+SELECT pgv_set('vars', 'any1', 'some value'::text);
+ pgv_set 
+---------
+ 
+(1 row)
+
+BEGIN;
+SELECT pgv_remove('vars');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+ROLLBACK;
+SELECT package FROM pgv_stats() ORDER BY package;
+ package 
+---------
+(0 rows)
+
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+-- Variables should be insertable after pgv_remove (variable)
+BEGIN;
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_remove('test', 'x');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'x', ROW (1::int, 3::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,3)
+(1 row)
+
+SELECT pgv_insert('test', 'x', ROW (2::int, 4::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,3)
+ (2,4)
+(2 rows)
+
+ROLLBACK;
+SELECT * FROM pgv_list() order by package, name;
+ package | name | is_transactional 
+---------+------+------------------
+(0 rows)
+
+BEGIN;
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_remove('test', 'x');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'x', ROW (1::int, 3::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,3)
+(1 row)
+
+SELECT pgv_insert('test', 'x', ROW (2::int, 4::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,3)
+ (2,4)
+(2 rows)
+
+COMMIT;
+SELECT * FROM pgv_list() order by package, name;
+ package | name | is_transactional 
+---------+------+------------------
+ test    | x    | t
+(1 row)
+
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,3)
+ (2,4)
+(2 rows)
+
+-- Variables should be insertable after pgv_remove (package)
+BEGIN;
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_remove('test');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (1::int, 3::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,3)
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (2::int, 4::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,3)
+ (2,4)
+(2 rows)
+
+ROLLBACK;
+SELECT * FROM pgv_list() order by package, name;
+ package | name | is_transactional 
+---------+------+------------------
+ test    | x    | t
+(1 row)
+
+BEGIN;
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_remove('test');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (1::int, 3::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,3)
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (2::int, 4::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,3)
+ (2,4)
+(2 rows)
+
+COMMIT;
+SELECT * FROM pgv_list() order by package, name;
+ package | name | is_transactional 
+---------+------+------------------
+ test    | y    | t
+(1 row)
+
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,3)
+ (2,4)
+(2 rows)
+
+-- Variables should be insertable after pgv_free
+BEGIN;
+SELECT pgv_insert('test', 'z', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'z');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'z', ROW (1::int, 3::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'z');
+ pgv_select 
+------------
+ (1,3)
+(1 row)
+
+SELECT pgv_insert('test', 'z', ROW (2::int, 4::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'z');
+ pgv_select 
+------------
+ (1,3)
+ (2,4)
+(2 rows)
+
+ROLLBACK;
+SELECT * FROM pgv_list() order by package, name;
+ package | name | is_transactional 
+---------+------+------------------
+ test    | y    | t
+(1 row)
+
+BEGIN;
+SELECT pgv_insert('test', 'z', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'z');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'z', ROW (1::int, 3::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'z');
+ pgv_select 
+------------
+ (1,3)
+(1 row)
+
+SELECT pgv_insert('test', 'z', ROW (2::int, 4::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'z');
+ pgv_select 
+------------
+ (1,3)
+ (2,4)
+(2 rows)
+
+COMMIT;
+SELECT * FROM pgv_list() order by package, name;
+ package | name | is_transactional 
+---------+------+------------------
+ test    | z    | t
+(1 row)
+
+SELECT pgv_select('test', 'z');
+ pgv_select 
+------------
+ (1,3)
+ (2,4)
+(2 rows)
+
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+-- Variables should be rollbackable if transactional
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'x', ROW (2::int, 3::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+BEGIN;
+SELECT pgv_remove('test', 'x');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+BEGIN;
+SELECT pgv_remove('test');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+BEGIN;
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+---
+--- Variables should not be rollbackable if not transactional
+---
+-- case 1 (remove var)
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (2::int, 3::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+BEGIN;
+SELECT pgv_remove('test', 'y');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+ROLLBACK;
+SELECT pgv_select('test', 'y');
+ERROR:  unrecognized variable "y"
+-- case 2 (remove pack)
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (2::int, 3::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+BEGIN;
+SELECT pgv_remove('test');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+ROLLBACK;
+SELECT pgv_select('test', 'y');
+ERROR:  unrecognized variable "y"
+-- case 3 (free)
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (2::int, 3::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+BEGIN;
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+ROLLBACK;
+SELECT pgv_select('test', 'y');
+ERROR:  unrecognized variable "y"
+-- clear all
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+---
+--- Cursors test #1 (remove var)
+---
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+SELECT pgv_remove('test', 'x');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'y');
+SELECT pgv_remove('test', 'y');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized variable "y"
+ROLLBACK;
+SELECT pgv_select('test', 'y');
+ERROR:  unrecognized variable "y"
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+---
+--- Cursors test #2 (remove pack)
+---
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+SELECT pgv_remove('test');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'y');
+SELECT pgv_remove('test');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test');
+ERROR:  function pgv_select(unknown) does not exist
+LINE 1: SELECT pgv_select('test');
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+---
+--- Cursors test #3 (free)
+---
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'y');
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test');
+ERROR:  function pgv_select(unknown) does not exist
+LINE 1: SELECT pgv_select('test');
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+---
+--- Cursor test #4
+---
+-- non transactional, remove var
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+SELECT pgv_remove('test', 'x');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ERROR:  unrecognized package "test"
+-- non transactional, remove pac
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+SELECT pgv_remove('test');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ERROR:  unrecognized package "test"
+-- non transactional, free
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ERROR:  unrecognized package "test"
+-- transactional, remove var
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'y');
+SELECT pgv_remove('test', 'y');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+-- transactional, remove pack
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'y');
+SELECT pgv_remove('test');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+-- transactional, free
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'y');
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+---
+--- Cursor test #5
+---
+-- non transactional
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_remove('test', 'x');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ERROR:  unrecognized package "test"
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_remove('test');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ERROR:  unrecognized package "test"
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ERROR:  unrecognized package "test"
+-- transactional
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_remove('test', 'x');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_remove('test');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+---
+--- Cursor test #6
+---
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_remove('test', 'x');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+COMMIT;
+---
+--- Tests for "leaked hash_seq_search scan for hash table"
+---
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'x', ROW (2::int, 3::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'x', ROW (3::int, 4::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'x') LIMIT 1;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_select('test', 'x') LIMIT 2;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+SELECT pgv_select('test', 'x') LIMIT 3;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+BEGIN;
+SELECT pgv_select('test', 'x') LIMIT 1;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_select('test', 'x') LIMIT 2;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+SELECT pgv_select('test', 'x') LIMIT 3;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+ROLLBACK;
+BEGIN;
+SELECT pgv_select('test', 'x') LIMIT 1;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_select('test', 'x') LIMIT 2;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+SELECT pgv_select('test', 'x') LIMIT 3;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+COMMIT;
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (2::int, 3::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (3::int, 4::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'y') LIMIT 1;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_select('test', 'y') LIMIT 2;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+SELECT pgv_select('test', 'y') LIMIT 3;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+BEGIN;
+SELECT pgv_select('test', 'y') LIMIT 1;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_select('test', 'y') LIMIT 2;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+SELECT pgv_select('test', 'y') LIMIT 3;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+ROLLBACK;
+BEGIN;
+SELECT pgv_select('test', 'y') LIMIT 1;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_select('test', 'y') LIMIT 2;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+SELECT pgv_select('test', 'y') LIMIT 3;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+COMMIT;
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+DECLARE r2_cur CURSOR FOR SELECT pgv_select('test', 'y');
+DECLARE r3_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+FETCH 1 in r2_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+FETCH 1 in r3_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+ROLLBACK;
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+DECLARE r2_cur CURSOR FOR SELECT pgv_select('test', 'y');
+DECLARE r3_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 2 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+FETCH 2 in r2_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+FETCH 2 in r3_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+ROLLBACK;
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+DECLARE r2_cur CURSOR FOR SELECT pgv_select('test', 'y');
+DECLARE r3_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 3 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+FETCH 3 in r2_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+FETCH 3 in r3_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+ROLLBACK;
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+DECLARE r2_cur CURSOR FOR SELECT pgv_select('test', 'y');
+DECLARE r3_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+FETCH 1 in r2_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+FETCH 1 in r3_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+COMMIT;
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+DECLARE r2_cur CURSOR FOR SELECT pgv_select('test', 'y');
+DECLARE r3_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 2 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+FETCH 2 in r2_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+FETCH 2 in r3_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+COMMIT;
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+DECLARE r2_cur CURSOR FOR SELECT pgv_select('test', 'y');
+DECLARE r3_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 3 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+FETCH 3 in r2_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+FETCH 3 in r3_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+COMMIT;
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+DECLARE r2_cur CURSOR FOR SELECT pgv_select('test', 'y');
+DECLARE r3_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+FETCH 2 in r2_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+FETCH 3 in r3_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+ROLLBACK;
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+DECLARE r2_cur CURSOR FOR SELECT pgv_select('test', 'y');
+DECLARE r3_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+FETCH 2 in r2_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+FETCH 3 in r3_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+COMMIT;
+---
+--- Some special cases
+---
+-- take #1
+SELECT pgv_insert('test', 'z1', ROW (2::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'z1');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (2,2)
+(1 row)
+
+SELECT pgv_remove('test', 'z1');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized variable "z1"
+ROLLBACK;
+SELECT pgv_select('test', 'z1');
+ pgv_select 
+------------
+ (2,2)
+(1 row)
+
+-- take #2
+SELECT pgv_insert('test', 'z2', ROW (2::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'z2');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (2,2)
+(1 row)
+
+SELECT pgv_remove('test', 'z2');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized variable "z2"
+ROLLBACK;
+SELECT pgv_select('test', 'z2');
+ERROR:  unrecognized variable "z2"
+SELECT pgv_insert('test', 'z2', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+-- take #3
+SELECT pgv_insert('test', 'z3', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'z3');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_remove('test', 'z3');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized variable "z3"
+ROLLBACK;
+SELECT pgv_select('test', 'z3');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'z3');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_remove('test', 'z3');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+COMMIT;
+SELECT pgv_select('test', 'z3');
+ERROR:  unrecognized variable "z3"
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+-- take #4
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'x', ROW (2::int, 3::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+SAVEPOINT sp1;
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+ROLLBACK TO SAVEPOINT sp1;
+COMMIT;
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+SAVEPOINT sp1;
+FETCH 2 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+ROLLBACK TO SAVEPOINT sp1;
+COMMIT;
+BEGIN;
+SAVEPOINT sp1;
+SELECT pgv_select('test', 'x') LIMIT 1;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+ROLLBACK TO SAVEPOINT sp1;
+COMMIT;
+---
+--- Test cases for pgv_stats
+---
+SELECT pgv_insert('test1', 'y', ROW (2::float, 1::float), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test1', 'x', ROW (2::float, 1::float), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
+DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
+FETCH 1 in r1_cur;
+ pgv_stats 
+-----------
+ (test,0)
+(1 row)
+
+FETCH 1 in r2_cur;
+ pgv_stats 
+-----------
+ (test,0)
+(1 row)
+
+COMMIT;
+SELECT pgv_insert('test1', 'y', ROW (2::float, 1::float), TRUE);
+ERROR:  there is a record in the variable "y" with same key
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
+DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
+FETCH 1 in r1_cur;
+ pgv_stats 
+-----------
+ (test,0)
+(1 row)
+
+FETCH 1 in r2_cur;
+ pgv_stats 
+-----------
+ (test,0)
+(1 row)
+
+ROLLBACK;
+SELECT pgv_insert('test1', 'y', ROW (2::float, 1::float), FALSE);
+ERROR:  variable "y" already created as TRANSACTIONAL
+SELECT pgv_insert('test1', 'y', ROW (2::float, 1::float), FALSE);
+ERROR:  variable "y" already created as TRANSACTIONAL
+SELECT pgv_insert('test1', 'x', ROW (2::float, 1::float), FALSE);
+ERROR:  variable "x" already created as TRANSACTIONAL
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
+DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
+FETCH 1 in r1_cur;
+ pgv_stats 
+-----------
+ (test,0)
+(1 row)
+
+FETCH 1 in r2_cur;
+ pgv_stats 
+-----------
+ (test,0)
+(1 row)
+
+COMMIT;
+SELECT pgv_insert('test1', 'y', ROW (2::float, 1::float), FALSE);
+ERROR:  variable "y" already created as TRANSACTIONAL
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
+DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
+FETCH 1 in r1_cur;
+ pgv_stats 
+-----------
+ (test,0)
+(1 row)
+
+FETCH 1 in r2_cur;
+ pgv_stats 
+-----------
+ (test,0)
+(1 row)
+
+ROLLBACK;
+SELECT pgv_insert('test1', 'y', ROW (2::float, 1::float), FALSE);
+ERROR:  variable "y" already created as TRANSACTIONAL
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+

From 20f4ef3272f58129522e54cb72dfa5307ae08b60 Mon Sep 17 00:00:00 2001
From: Alexey Kondratov <kondratov.aleksey@gmail.com>
Date: Fri, 30 Oct 2020 12:57:53 +0300
Subject: [PATCH 11/22] Fix some comments

---
 pg_variables.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/pg_variables.c b/pg_variables.c
index b22dd29..9e83352 100644
--- a/pg_variables.c
+++ b/pg_variables.c
@@ -141,8 +141,8 @@ static MemoryContext changesStackContext = NULL;
  * save pointers to the rstats into list. Once transaction ended (commited or
  * aborted) we clear all the "active" hash_seq_search by calling hash_seq_term.
  *
- * TopTransactionContext is handy here, becouse it wount be reset by the time
- * pgvTransCallback is called.
+ * TopTransactionContext is handy here, because it would not be reset by the
+ * time pgvTransCallback is called.
  */
 static List *variables_stats = NIL;
 static List *packages_stats = NIL;
@@ -191,8 +191,8 @@ HtabToStat_level_eq(HtabToStat *entry, void *value)
 /*
  * Generic remove_if algorithm for HtabToStat.
  *
- *  + eq if function pointer used to compare list entry to the value.
- *  + if match_first is true return on first match.
+ * 'eq' - is a function pointer used to compare list entries to the 'value'.
+ * 'match_first' - if true return on first match.
  */
 static void
 HtabToStat_remove_if(List **l, void *value,
@@ -260,7 +260,7 @@ remove_variables_variable(List **l, Variable *variable)
 }
 
 /*
- * Remove all the entrys for package.
+ * Remove all the entries for package.
  */
 static void
 remove_variables_package(List **l, Package *package)
@@ -269,7 +269,7 @@ remove_variables_package(List **l, Package *package)
 }
 
 /*
- * Remove all the entrys for level.
+ * Remove all the entries for level.
  */
 static void
 remove_variables_level(List **l, int level)

From 4b85a7cab60cbea89a2ce731d649f7801bc4ec6c Mon Sep 17 00:00:00 2001
From: Maxim Orlov <m.orlov@postgrespro.ru>
Date: Tue, 3 Nov 2020 12:02:19 +0300
Subject: [PATCH 12/22] Issue #27: Fix list remove generic algo.

---
 pg_variables.c | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/pg_variables.c b/pg_variables.c
index 9e83352..120509f 100644
--- a/pg_variables.c
+++ b/pg_variables.c
@@ -226,7 +226,7 @@ HtabToStat_remove_if(List **l, void *value,
 	/*
 	 * See https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=1cff1b95ab6ddae32faa3efe0d95a820dbfdc164
 	 *
-	 * Version > 13 have different lists interface.
+	 * Version >= 13 have different lists interface.
 	 */
 	ListCell	*cell;
 	HtabToStat	*entry = NULL;
@@ -236,7 +236,14 @@ HtabToStat_remove_if(List **l, void *value,
 		entry = (HtabToStat *) lfirst(cell);
 
 		if (eq(entry, value))
+		{
 			*l = foreach_delete_current(*l, cell);
+			pfree(entry->status);
+			pfree(entry);
+
+			if (match_first)
+				return;
+		}
 	}
 #endif
 }

From a68605013157bb76fa45434ba67f7dd9874cc1f6 Mon Sep 17 00:00:00 2001
From: Maxim Orlov <m.orlov@postgrespro.ru>
Date: Tue, 3 Nov 2020 14:15:36 +0300
Subject: [PATCH 13/22] Issue #27: Fix savepoint in transaction issue.

---
 expected/pg_variables_trans.out   | 129 ++++++++++++++++++++++
 expected/pg_variables_trans_1.out | 129 ++++++++++++++++++++++
 pg_variables.c                    | 177 ++++++++++++++++++++++++++++--
 3 files changed, 425 insertions(+), 10 deletions(-)

diff --git a/expected/pg_variables_trans.out b/expected/pg_variables_trans.out
index 2ce8138..bee104d 100644
--- a/expected/pg_variables_trans.out
+++ b/expected/pg_variables_trans.out
@@ -3653,3 +3653,132 @@ SELECT pgv_free();
  
 (1 row)
 
+---
+--- Some special cases
+---
+-- 1
+BEGIN;
+SAVEPOINT comm2;
+SELECT pgv_insert('test', 'x1', ROW (2::float, 1::float), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
+DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
+FETCH 1 in r1_cur;
+  pgv_stats   
+--------------
+ (test,32768)
+(1 row)
+
+FETCH 1 in r2_cur;
+  pgv_stats   
+--------------
+ (test,32768)
+(1 row)
+
+COMMIT;
+-- 2
+BEGIN;
+SELECT pgv_insert('test', 'x2', ROW (2::float, 1::float), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SAVEPOINT comm2;
+DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
+DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
+FETCH 1 in r1_cur;
+  pgv_stats   
+--------------
+ (test,49152)
+(1 row)
+
+FETCH 1 in r2_cur;
+  pgv_stats   
+--------------
+ (test,49152)
+(1 row)
+
+COMMIT;
+-- 3
+BEGIN;
+SELECT pgv_insert('test', 'x3', ROW (2::float, 1::float), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
+DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
+SAVEPOINT comm2;
+FETCH 1 in r1_cur;
+  pgv_stats   
+--------------
+ (test,65536)
+(1 row)
+
+FETCH 1 in r2_cur;
+  pgv_stats   
+--------------
+ (test,65536)
+(1 row)
+
+COMMIT;
+-- 4
+BEGIN;
+SELECT pgv_insert('test', 'x4', ROW (2::float, 1::float), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
+DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
+FETCH 1 in r1_cur;
+  pgv_stats   
+--------------
+ (test,81920)
+(1 row)
+
+SAVEPOINT comm2;
+FETCH 1 in r2_cur;
+  pgv_stats   
+--------------
+ (test,81920)
+(1 row)
+
+COMMIT;
+-- 5
+BEGIN;
+SELECT pgv_insert('test', 'x5', ROW (2::float, 1::float), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
+DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
+FETCH 1 in r1_cur;
+  pgv_stats   
+--------------
+ (test,98304)
+(1 row)
+
+FETCH 1 in r2_cur;
+  pgv_stats   
+--------------
+ (test,98304)
+(1 row)
+
+SAVEPOINT comm2;
+COMMIT;
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
diff --git a/expected/pg_variables_trans_1.out b/expected/pg_variables_trans_1.out
index bc55b84..ad84f9d 100644
--- a/expected/pg_variables_trans_1.out
+++ b/expected/pg_variables_trans_1.out
@@ -3653,3 +3653,132 @@ SELECT pgv_free();
  
 (1 row)
 
+---
+--- Some special cases
+---
+-- 1
+BEGIN;
+SAVEPOINT comm2;
+SELECT pgv_insert('test', 'x1', ROW (2::float, 1::float), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
+DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
+FETCH 1 in r1_cur;
+  pgv_stats   
+--------------
+ (test,0)
+(1 row)
+
+FETCH 1 in r2_cur;
+  pgv_stats   
+--------------
+ (test,0)
+(1 row)
+
+COMMIT;
+-- 2
+BEGIN;
+SELECT pgv_insert('test', 'x2', ROW (2::float, 1::float), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SAVEPOINT comm2;
+DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
+DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
+FETCH 1 in r1_cur;
+  pgv_stats   
+--------------
+ (test,0)
+(1 row)
+
+FETCH 1 in r2_cur;
+  pgv_stats   
+--------------
+ (test,0)
+(1 row)
+
+COMMIT;
+-- 3
+BEGIN;
+SELECT pgv_insert('test', 'x3', ROW (2::float, 1::float), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
+DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
+SAVEPOINT comm2;
+FETCH 1 in r1_cur;
+  pgv_stats   
+--------------
+ (test,0)
+(1 row)
+
+FETCH 1 in r2_cur;
+  pgv_stats   
+--------------
+ (test,0)
+(1 row)
+
+COMMIT;
+-- 4
+BEGIN;
+SELECT pgv_insert('test', 'x4', ROW (2::float, 1::float), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
+DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
+FETCH 1 in r1_cur;
+  pgv_stats   
+--------------
+ (test,0)
+(1 row)
+
+SAVEPOINT comm2;
+FETCH 1 in r2_cur;
+  pgv_stats   
+--------------
+ (test,0)
+(1 row)
+
+COMMIT;
+-- 5
+BEGIN;
+SELECT pgv_insert('test', 'x5', ROW (2::float, 1::float), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
+DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
+FETCH 1 in r1_cur;
+  pgv_stats   
+--------------
+ (test,0)
+(1 row)
+
+FETCH 1 in r2_cur;
+  pgv_stats   
+--------------
+ (test,0)
+(1 row)
+
+SAVEPOINT comm2;
+COMMIT;
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
diff --git a/pg_variables.c b/pg_variables.c
index 120509f..0fb344b 100644
--- a/pg_variables.c
+++ b/pg_variables.c
@@ -147,16 +147,23 @@ static MemoryContext changesStackContext = NULL;
 static List *variables_stats = NIL;
 static List *packages_stats = NIL;
 
-typedef struct tagHtabToStat {
+typedef struct tagHtabToStat
+{
 	HTAB			*hash;
 	HASH_SEQ_STATUS	*status;
 	Variable		*variable;
 	Package			*package;
-	int				level;
+	int				 level;
 } HtabToStat;
 
+typedef struct tagPackageStatEntry
+{
+	HASH_SEQ_STATUS	*status;
+	int				 level;
+} PackageStatEntry;
+
 /*
- * A bunch of comp functions for HtabToStat members here.
+ * A bunch of comp functions for HtabToStat and PackageStatEntry  members here.
  */
 static bool
 HtabToStat_status_eq(HtabToStat *entry, void *value)
@@ -188,6 +195,18 @@ HtabToStat_level_eq(HtabToStat *entry, void *value)
 	return entry->level == *(int *) value;
 }
 
+static bool
+PackageStatEntry_status_eq(PackageStatEntry *entry, void *value)
+{
+	return entry->status == (HASH_SEQ_STATUS *) value;
+}
+
+static bool
+PackageStatEntry_level_eq(PackageStatEntry *entry, void *value)
+{
+	return entry->level == *(int *) value;
+}
+
 /*
  * Generic remove_if algorithm for HtabToStat.
  *
@@ -248,6 +267,75 @@ HtabToStat_remove_if(List **l, void *value,
 #endif
 }
 
+/*
+ * Generic remove_if algorithm for PackageStatEntry.
+ *
+ * 'eq' - is a function pointer used to compare list entries to the 'value'.
+ * 'match_first' - if true return on first match.
+ */
+static void
+PackageStatEntry_remove_if(List **l, void *value,
+						   bool (*eq)(PackageStatEntry *, void *),
+						   bool match_first,
+						   bool term)
+{
+#if (PG_VERSION_NUM < 130000)
+	ListCell			*cell, *next, *prev = NULL;
+	PackageStatEntry	*entry = NULL;
+
+	for (cell = list_head(*l); cell; cell = next)
+	{
+		entry = (PackageStatEntry *) lfirst(cell);
+		next = lnext(cell);
+
+		if (eq(entry, value))
+		{
+			*l = list_delete_cell(*l, cell, prev);
+
+			if (term)
+				hash_seq_term(entry->status);
+
+			pfree(entry->status);
+			pfree(entry);
+
+			if (match_first)
+				return;
+		}
+		else
+		{
+			prev = cell;
+		}
+	}
+#else
+	/*
+	 * See https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=1cff1b95ab6ddae32faa3efe0d95a820dbfdc164
+	 *
+	 * Version >= 13 have different lists interface.
+	 */
+	ListCell			*cell;
+	PackageStatEntry	*entry = NULL;
+
+	foreach(cell, *l)
+	{
+		entry = (PackageStatEntry *) lfirst(cell);
+
+		if (eq(entry, value))
+		{
+			*l = foreach_delete_current(*l, cell);
+
+			if (term)
+				hash_seq_term(entry->status);
+
+			pfree(entry->status);
+			pfree(entry);
+
+			if (match_first)
+				return;
+		}
+	}
+#endif
+}
+
 /*
  * Remove first entry for status.
  */
@@ -294,6 +382,26 @@ remove_variables_all(List **l)
 	*l = NIL;
 }
 
+/*
+ * Remove first entry for status for packages.
+ */
+static void
+remove_packages_status(List **l, HASH_SEQ_STATUS *status)
+{
+	/* Match first, not term */
+	PackageStatEntry_remove_if(l, status, PackageStatEntry_status_eq, true, false);
+}
+
+/*
+ * Remove first entry for status for packages.
+ */
+static void
+remove_packages_level(List **l, int level)
+{
+	/* Match all, term */
+	PackageStatEntry_remove_if(l, &level, PackageStatEntry_level_eq, false, true);
+}
+
 static void freeStatsLists(void);
 /* Returns a lists of packages and variables changed at current subxact level */
 #define get_actual_changes_list() \
@@ -1416,7 +1524,8 @@ get_packages_stats(PG_FUNCTION_ARGS)
 		 */
 		if (packagesHash)
 		{
-			MemoryContext ctx;
+			MemoryContext	 ctx;
+			PackageStatEntry *entry;
 
 			ctx = MemoryContextSwitchTo(TopTransactionContext);
 			rstat = (HASH_SEQ_STATUS *) palloc0(sizeof(HASH_SEQ_STATUS));
@@ -1424,7 +1533,10 @@ get_packages_stats(PG_FUNCTION_ARGS)
 			hash_seq_init(rstat, packagesHash);
 
 			funcctx->user_fctx = rstat;
-			packages_stats = lcons((void *)rstat, packages_stats);
+			entry = palloc0(sizeof(PackageStatEntry));
+			entry->status = rstat;
+			entry->level = GetCurrentTransactionNestLevel();
+			packages_stats = lcons((void *)entry, packages_stats);
 			MemoryContextSwitchTo(ctx);
 		}
 		else
@@ -1472,8 +1584,9 @@ get_packages_stats(PG_FUNCTION_ARGS)
 	}
 	else
 	{
-		packages_stats = list_delete(packages_stats, rstat);
-		pfree(rstat);
+		//packages_stats = list_delete(packages_stats, rstat);
+		//pfree(rstat);
+		remove_packages_status(&packages_stats, rstat);
 		SRF_RETURN_DONE(funcctx);
 	}
 }
@@ -2355,6 +2468,50 @@ pgvSubTransCallback(SubXactEvent event, SubTransactionId mySubid,
 	}
 
 	remove_variables_level(&variables_stats, GetCurrentTransactionNestLevel());
+	remove_packages_level(&packages_stats, GetCurrentTransactionNestLevel());
+
+/*#if (PG_VERSION_NUM < 130000)
+	{
+		ListCell			*cell, *next, *prev = NULL;
+		PackageStatEntry	*entry = NULL;
+
+		for (cell = list_head(packages_stats); cell; cell = next)
+		{
+			entry = (PackageStatEntry *) lfirst(cell);
+			next = lnext(cell);
+
+			if (entry->level == GetCurrentTransactionNestLevel())
+			{
+				packages_stats = list_delete_cell(packages_stats, cell, prev);
+				hash_seq_term(entry->status);
+				pfree(entry->status);
+				pfree(entry);
+			}
+			else
+			{
+				prev = cell;
+			}
+		}
+	}
+#else
+	{
+		ListCell			*cell;
+		PackageStatEntry	*entry = NULL;
+
+		foreach(cell, packages_stats)
+		{
+			entry = (PackageStatEntry *) lfirst(cell);
+
+			if (entry->level == GetCurrentTransactionNestLevel())
+			{
+				packages_stats = foreach_delete_current(packages_stats, cell);
+				hash_seq_term(entry->status);
+				pfree(entry->status);
+				pfree(entry);
+			}
+		}
+	}
+#endif*/
 }
 
 /*
@@ -2410,8 +2567,8 @@ static void
 freeStatsLists(void)
 {
 	ListCell		*cell;
-	HASH_SEQ_STATUS	*status;
 	HtabToStat		*htab_to_stat;
+	PackageStatEntry *entry;
 
 	foreach(cell, variables_stats)
 	{
@@ -2423,8 +2580,8 @@ freeStatsLists(void)
 
 	foreach(cell, packages_stats)
 	{
-		status = (HASH_SEQ_STATUS *) lfirst(cell);
-		hash_seq_term(status);
+		entry = (PackageStatEntry *) lfirst(cell);
+		hash_seq_term(entry->status);
 	}
 
 	packages_stats = NIL;

From 2e9c17564db7b0f1f01027d9c84db81eefeff988 Mon Sep 17 00:00:00 2001
From: Maxim Orlov <m.orlov@postgrespro.ru>
Date: Tue, 3 Nov 2020 15:19:10 +0300
Subject: [PATCH 14/22] Issue #27: Fix savepoint in transaction issue, add sql.

---
 sql/pg_variables_trans.sql | 55 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 55 insertions(+)

diff --git a/sql/pg_variables_trans.sql b/sql/pg_variables_trans.sql
index 2b83ab5..b6fd7a3 100644
--- a/sql/pg_variables_trans.sql
+++ b/sql/pg_variables_trans.sql
@@ -1075,3 +1075,58 @@ ROLLBACK;
 SELECT pgv_insert('test1', 'y', ROW (2::float, 1::float), FALSE);
 
 SELECT pgv_free();
+
+---
+--- Some special cases
+---
+-- 1
+BEGIN;
+SAVEPOINT comm2;
+SELECT pgv_insert('test', 'x1', ROW (2::float, 1::float), TRUE);
+DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
+DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
+FETCH 1 in r1_cur;
+FETCH 1 in r2_cur;
+COMMIT;
+
+-- 2
+BEGIN;
+SELECT pgv_insert('test', 'x2', ROW (2::float, 1::float), TRUE);
+SAVEPOINT comm2;
+DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
+DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
+FETCH 1 in r1_cur;
+FETCH 1 in r2_cur;
+COMMIT;
+
+-- 3
+BEGIN;
+SELECT pgv_insert('test', 'x3', ROW (2::float, 1::float), TRUE);
+DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
+DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
+SAVEPOINT comm2;
+FETCH 1 in r1_cur;
+FETCH 1 in r2_cur;
+COMMIT;
+
+-- 4
+BEGIN;
+SELECT pgv_insert('test', 'x4', ROW (2::float, 1::float), TRUE);
+DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
+DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
+FETCH 1 in r1_cur;
+SAVEPOINT comm2;
+FETCH 1 in r2_cur;
+COMMIT;
+
+-- 5
+BEGIN;
+SELECT pgv_insert('test', 'x5', ROW (2::float, 1::float), TRUE);
+DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
+DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
+FETCH 1 in r1_cur;
+FETCH 1 in r2_cur;
+SAVEPOINT comm2;
+COMMIT;
+
+SELECT pgv_free();

From ef847a46a94b793acbd651971d2b6cffde5d5d8e Mon Sep 17 00:00:00 2001
From: Maxim Orlov <m.orlov@postgrespro.ru>
Date: Tue, 3 Nov 2020 15:36:47 +0300
Subject: [PATCH 15/22] Issue #27: Fix savepoint in transaction issue, add
 expected result for 9 and 10.

---
 expected/pg_variables_trans_2.out | 3784 +++++++++++++++++++++++++++++
 1 file changed, 3784 insertions(+)
 create mode 100644 expected/pg_variables_trans_2.out

diff --git a/expected/pg_variables_trans_2.out b/expected/pg_variables_trans_2.out
new file mode 100644
index 0000000..102d550
--- /dev/null
+++ b/expected/pg_variables_trans_2.out
@@ -0,0 +1,3784 @@
+SET timezone = 'Europe/Moscow'; -- Need to proper output of datetime variables
+--CHECK SAVEPOINT RELEASE
+BEGIN;
+-- Declare variables
+SELECT pgv_set('vars', 'any1', 'some value'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT pgv_set('vars', 'any2', 'some value'::text);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT pgv_set_int('vars', 'int1', 101, true);
+ pgv_set_int 
+-------------
+ 
+(1 row)
+
+SELECT pgv_set_int('vars', 'int2', 102);
+ pgv_set_int 
+-------------
+ 
+(1 row)
+
+SELECT pgv_set_int('vars', 'intNULL', NULL, true);
+ pgv_set_int 
+-------------
+ 
+(1 row)
+
+SELECT pgv_set_text('vars', 'str1', 's101', true);
+ pgv_set_text 
+--------------
+ 
+(1 row)
+
+SELECT pgv_set_text('vars', 'str2', 's102');
+ pgv_set_text 
+--------------
+ 
+(1 row)
+
+SELECT pgv_set_numeric('vars', 'num1', 1.01, true);
+ pgv_set_numeric 
+-----------------
+ 
+(1 row)
+
+SELECT pgv_set_numeric('vars', 'num2', 1.02);
+ pgv_set_numeric 
+-----------------
+ 
+(1 row)
+
+SELECT pgv_set_timestamp('vars', 'ts1', '2016-03-30 10:00:00', true);
+ pgv_set_timestamp 
+-------------------
+ 
+(1 row)
+
+SELECT pgv_set_timestamp('vars', 'ts2', '2016-03-30 11:00:00');
+ pgv_set_timestamp 
+-------------------
+ 
+(1 row)
+
+SELECT pgv_set_timestamptz('vars', 'tstz1', '2016-03-30 10:00:00 GMT+01', true);
+ pgv_set_timestamptz 
+---------------------
+ 
+(1 row)
+
+SELECT pgv_set_timestamptz('vars', 'tstz2', '2016-03-30 11:00:00 GMT+02');
+ pgv_set_timestamptz 
+---------------------
+ 
+(1 row)
+
+SELECT pgv_set_date('vars', 'd1', '2016-03-29', true);
+ pgv_set_date 
+--------------
+ 
+(1 row)
+
+SELECT pgv_set_date('vars', 'd2', '2016-03-30');
+ pgv_set_date 
+--------------
+ 
+(1 row)
+
+SELECT pgv_set_jsonb('vars2', 'j1', '[1, 2, "foo", null]', true);
+ pgv_set_jsonb 
+---------------
+ 
+(1 row)
+
+SELECT pgv_set_jsonb('vars2', 'j2', '{"bar": "baz", "balance": 7.77, "active": false}');
+ pgv_set_jsonb 
+---------------
+ 
+(1 row)
+
+SAVEPOINT comm;
+-- Set new values
+SELECT pgv_set('vars', 'any1', 'another value'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT pgv_set('vars', 'any2', 'another value'::text);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT pgv_set_int('vars', 'int1', 103, true);
+ pgv_set_int 
+-------------
+ 
+(1 row)
+
+SELECT pgv_set_int('vars', 'int2', 103);
+ pgv_set_int 
+-------------
+ 
+(1 row)
+
+SELECT pgv_set_int('vars', 'intNULL', 104, true);
+ pgv_set_int 
+-------------
+ 
+(1 row)
+
+SELECT pgv_set_text('vars', 'str1', 's103', true);
+ pgv_set_text 
+--------------
+ 
+(1 row)
+
+SELECT pgv_set_text('vars', 'str2', 's103');
+ pgv_set_text 
+--------------
+ 
+(1 row)
+
+SELECT pgv_set_numeric('vars', 'num1', 1.03, true);
+ pgv_set_numeric 
+-----------------
+ 
+(1 row)
+
+SELECT pgv_set_numeric('vars', 'num2', 1.03);
+ pgv_set_numeric 
+-----------------
+ 
+(1 row)
+
+SELECT pgv_set_timestamp('vars', 'ts1', '2016-03-30 12:00:00', true);
+ pgv_set_timestamp 
+-------------------
+ 
+(1 row)
+
+SELECT pgv_set_timestamp('vars', 'ts2', '2016-03-30 12:00:00');
+ pgv_set_timestamp 
+-------------------
+ 
+(1 row)
+
+SELECT pgv_set_timestamptz('vars', 'tstz1', '2016-03-30 12:00:00 GMT+03', true);
+ pgv_set_timestamptz 
+---------------------
+ 
+(1 row)
+
+SELECT pgv_set_timestamptz('vars', 'tstz2', '2016-03-30 12:00:00 GMT+03');
+ pgv_set_timestamptz 
+---------------------
+ 
+(1 row)
+
+SELECT pgv_set_date('vars', 'd1', '2016-04-02', true);
+ pgv_set_date 
+--------------
+ 
+(1 row)
+
+SELECT pgv_set_date('vars', 'd2', '2016-04-02');
+ pgv_set_date 
+--------------
+ 
+(1 row)
+
+SELECT pgv_set_jsonb('vars2', 'j1', '{"foo": [true, "bar"], "tags": {"a": 1, "b": null}}', true);
+ pgv_set_jsonb 
+---------------
+ 
+(1 row)
+
+SELECT pgv_set_jsonb('vars2', 'j2', '{"foo": [true, "bar"], "tags": {"a": 1, "b": null}}');
+ pgv_set_jsonb 
+---------------
+ 
+(1 row)
+
+-- Check values before releasing savepoint
+SELECT pgv_get('vars', 'any1',NULL::text);
+    pgv_get    
+---------------
+ another value
+(1 row)
+
+SELECT pgv_get('vars', 'any2',NULL::text);
+    pgv_get    
+---------------
+ another value
+(1 row)
+
+SELECT pgv_get_int('vars', 'int1');
+ pgv_get_int 
+-------------
+         103
+(1 row)
+
+SELECT pgv_get_int('vars', 'int2');
+ pgv_get_int 
+-------------
+         103
+(1 row)
+
+SELECT pgv_get_int('vars', 'intNULL');
+ pgv_get_int 
+-------------
+         104
+(1 row)
+
+SELECT pgv_get_text('vars', 'str1');
+ pgv_get_text 
+--------------
+ s103
+(1 row)
+
+SELECT pgv_get_text('vars', 'str2');
+ pgv_get_text 
+--------------
+ s103
+(1 row)
+
+SELECT pgv_get_numeric('vars', 'num1');
+ pgv_get_numeric 
+-----------------
+            1.03
+(1 row)
+
+SELECT pgv_get_numeric('vars', 'num2');
+ pgv_get_numeric 
+-----------------
+            1.03
+(1 row)
+
+SELECT pgv_get_timestamp('vars', 'ts1');
+    pgv_get_timestamp     
+--------------------------
+ Wed Mar 30 12:00:00 2016
+(1 row)
+
+SELECT pgv_get_timestamp('vars', 'ts2');
+    pgv_get_timestamp     
+--------------------------
+ Wed Mar 30 12:00:00 2016
+(1 row)
+
+SELECT pgv_get_timestamptz('vars', 'tstz1');
+     pgv_get_timestamptz      
+------------------------------
+ Wed Mar 30 18:00:00 2016 MSK
+(1 row)
+
+SELECT pgv_get_timestamptz('vars', 'tstz2');
+     pgv_get_timestamptz      
+------------------------------
+ Wed Mar 30 18:00:00 2016 MSK
+(1 row)
+
+SELECT pgv_get_date('vars', 'd1');
+ pgv_get_date 
+--------------
+ 04-02-2016
+(1 row)
+
+SELECT pgv_get_date('vars', 'd2');
+ pgv_get_date 
+--------------
+ 04-02-2016
+(1 row)
+
+SELECT pgv_get_jsonb('vars2', 'j1');
+                    pgv_get_jsonb                    
+-----------------------------------------------------
+ {"foo": [true, "bar"], "tags": {"a": 1, "b": null}}
+(1 row)
+
+SELECT pgv_get_jsonb('vars2', 'j2');
+                    pgv_get_jsonb                    
+-----------------------------------------------------
+ {"foo": [true, "bar"], "tags": {"a": 1, "b": null}}
+(1 row)
+
+-- Check values after releasing savepoint
+RELEASE comm;
+SELECT pgv_get('vars', 'any1',NULL::text);
+    pgv_get    
+---------------
+ another value
+(1 row)
+
+SELECT pgv_get('vars', 'any2',NULL::text);
+    pgv_get    
+---------------
+ another value
+(1 row)
+
+SELECT pgv_get_int('vars', 'int1');
+ pgv_get_int 
+-------------
+         103
+(1 row)
+
+SELECT pgv_get_int('vars', 'int2');
+ pgv_get_int 
+-------------
+         103
+(1 row)
+
+SELECT pgv_get_int('vars', 'intNULL');
+ pgv_get_int 
+-------------
+         104
+(1 row)
+
+SELECT pgv_get_text('vars', 'str1');
+ pgv_get_text 
+--------------
+ s103
+(1 row)
+
+SELECT pgv_get_text('vars', 'str2');
+ pgv_get_text 
+--------------
+ s103
+(1 row)
+
+SELECT pgv_get_numeric('vars', 'num1');
+ pgv_get_numeric 
+-----------------
+            1.03
+(1 row)
+
+SELECT pgv_get_numeric('vars', 'num2');
+ pgv_get_numeric 
+-----------------
+            1.03
+(1 row)
+
+SELECT pgv_get_timestamp('vars', 'ts1');
+    pgv_get_timestamp     
+--------------------------
+ Wed Mar 30 12:00:00 2016
+(1 row)
+
+SELECT pgv_get_timestamp('vars', 'ts2');
+    pgv_get_timestamp     
+--------------------------
+ Wed Mar 30 12:00:00 2016
+(1 row)
+
+SELECT pgv_get_timestamptz('vars', 'tstz1');
+     pgv_get_timestamptz      
+------------------------------
+ Wed Mar 30 18:00:00 2016 MSK
+(1 row)
+
+SELECT pgv_get_timestamptz('vars', 'tstz2');
+     pgv_get_timestamptz      
+------------------------------
+ Wed Mar 30 18:00:00 2016 MSK
+(1 row)
+
+SELECT pgv_get_date('vars', 'd1');
+ pgv_get_date 
+--------------
+ 04-02-2016
+(1 row)
+
+SELECT pgv_get_date('vars', 'd2');
+ pgv_get_date 
+--------------
+ 04-02-2016
+(1 row)
+
+SELECT pgv_get_jsonb('vars2', 'j1');
+                    pgv_get_jsonb                    
+-----------------------------------------------------
+ {"foo": [true, "bar"], "tags": {"a": 1, "b": null}}
+(1 row)
+
+SELECT pgv_get_jsonb('vars2', 'j2');
+                    pgv_get_jsonb                    
+-----------------------------------------------------
+ {"foo": [true, "bar"], "tags": {"a": 1, "b": null}}
+(1 row)
+
+COMMIT;
+CREATE TABLE tab (id int, t varchar);
+INSERT INTO tab VALUES (0, 'str00'), (1, 'str33'), (2, NULL), (NULL, 'strNULL');
+BEGIN;
+SELECT pgv_insert('vars3', 'r1', tab, true) FROM tab;
+ pgv_insert 
+------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT pgv_insert('vars3', 'r2', tab) FROM tab;
+ pgv_insert 
+------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SAVEPOINT comm;
+SELECT pgv_insert('vars3', 'r1', row(5 :: integer, 'str55' :: varchar),true);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('vars3', 'r2', row(5 :: integer, 'str55' :: varchar));
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('vars3', 'r1');
+ pgv_select 
+------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (5,str55)
+ (0,str00)
+(5 rows)
+
+SELECT pgv_select('vars3', 'r2');
+ pgv_select 
+------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (5,str55)
+ (0,str00)
+(5 rows)
+
+RELEASE comm;
+SELECT pgv_select('vars3', 'r1');
+ pgv_select 
+------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (5,str55)
+ (0,str00)
+(5 rows)
+
+SELECT pgv_select('vars3', 'r2');
+ pgv_select 
+------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (5,str55)
+ (0,str00)
+(5 rows)
+
+COMMIT;
+--CHECK SAVEPOINT ROLLBACK
+BEGIN;
+-- Variables are already declared
+SAVEPOINT comm2;
+-- Set new values
+SELECT pgv_set('vars', 'any1', 'one more value'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT pgv_set('vars', 'any2', 'one more value'::text);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT pgv_set_int('vars', 'int1', 101, true);
+ pgv_set_int 
+-------------
+ 
+(1 row)
+
+SELECT pgv_set_int('vars', 'int2', 102);
+ pgv_set_int 
+-------------
+ 
+(1 row)
+
+SELECT pgv_set_int('vars', 'intNULL', NULL, true);
+ pgv_set_int 
+-------------
+ 
+(1 row)
+
+SELECT pgv_set_text('vars', 'str1', 's101', true);
+ pgv_set_text 
+--------------
+ 
+(1 row)
+
+SELECT pgv_set_text('vars', 'str2', 's102');
+ pgv_set_text 
+--------------
+ 
+(1 row)
+
+SELECT pgv_set_numeric('vars', 'num1', 1.01, true);
+ pgv_set_numeric 
+-----------------
+ 
+(1 row)
+
+SELECT pgv_set_numeric('vars', 'num2', 1.02);
+ pgv_set_numeric 
+-----------------
+ 
+(1 row)
+
+SELECT pgv_set_timestamp('vars', 'ts1', '2016-03-30 10:00:00', true);
+ pgv_set_timestamp 
+-------------------
+ 
+(1 row)
+
+SELECT pgv_set_timestamp('vars', 'ts2', '2016-03-30 11:00:00');
+ pgv_set_timestamp 
+-------------------
+ 
+(1 row)
+
+SELECT pgv_set_timestamptz('vars', 'tstz1', '2016-03-30 10:00:00 GMT+01', true);
+ pgv_set_timestamptz 
+---------------------
+ 
+(1 row)
+
+SELECT pgv_set_timestamptz('vars', 'tstz2', '2016-03-30 11:00:00 GMT+02');
+ pgv_set_timestamptz 
+---------------------
+ 
+(1 row)
+
+SELECT pgv_set_date('vars', 'd1', '2016-03-29', true);
+ pgv_set_date 
+--------------
+ 
+(1 row)
+
+SELECT pgv_set_date('vars', 'd2', '2016-03-30');
+ pgv_set_date 
+--------------
+ 
+(1 row)
+
+SELECT pgv_set_jsonb('vars2', 'j1', '[1, 2, "foo", null]', true);
+ pgv_set_jsonb 
+---------------
+ 
+(1 row)
+
+SELECT pgv_set_jsonb('vars2', 'j2', '{"bar": "baz", "balance": 7.77, "active": false}');
+ pgv_set_jsonb 
+---------------
+ 
+(1 row)
+
+-- Check values before rollback to savepoint
+SELECT pgv_get('vars', 'any1',NULL::text);
+    pgv_get     
+----------------
+ one more value
+(1 row)
+
+SELECT pgv_get('vars', 'any2',NULL::text);
+    pgv_get     
+----------------
+ one more value
+(1 row)
+
+SELECT pgv_get_int('vars', 'int1');
+ pgv_get_int 
+-------------
+         101
+(1 row)
+
+SELECT pgv_get_int('vars', 'int2');
+ pgv_get_int 
+-------------
+         102
+(1 row)
+
+SELECT pgv_get_int('vars', 'intNULL');
+ pgv_get_int 
+-------------
+            
+(1 row)
+
+SELECT pgv_get_text('vars', 'str1');
+ pgv_get_text 
+--------------
+ s101
+(1 row)
+
+SELECT pgv_get_text('vars', 'str2');
+ pgv_get_text 
+--------------
+ s102
+(1 row)
+
+SELECT pgv_get_numeric('vars', 'num1');
+ pgv_get_numeric 
+-----------------
+            1.01
+(1 row)
+
+SELECT pgv_get_numeric('vars', 'num2');
+ pgv_get_numeric 
+-----------------
+            1.02
+(1 row)
+
+SELECT pgv_get_timestamp('vars', 'ts1');
+    pgv_get_timestamp     
+--------------------------
+ Wed Mar 30 10:00:00 2016
+(1 row)
+
+SELECT pgv_get_timestamp('vars', 'ts2');
+    pgv_get_timestamp     
+--------------------------
+ Wed Mar 30 11:00:00 2016
+(1 row)
+
+SELECT pgv_get_timestamptz('vars', 'tstz1');
+     pgv_get_timestamptz      
+------------------------------
+ Wed Mar 30 14:00:00 2016 MSK
+(1 row)
+
+SELECT pgv_get_timestamptz('vars', 'tstz2');
+     pgv_get_timestamptz      
+------------------------------
+ Wed Mar 30 16:00:00 2016 MSK
+(1 row)
+
+SELECT pgv_get_date('vars', 'd1');
+ pgv_get_date 
+--------------
+ 03-29-2016
+(1 row)
+
+SELECT pgv_get_date('vars', 'd2');
+ pgv_get_date 
+--------------
+ 03-30-2016
+(1 row)
+
+SELECT pgv_get_jsonb('vars2', 'j1');
+    pgv_get_jsonb    
+---------------------
+ [1, 2, "foo", null]
+(1 row)
+
+SELECT pgv_get_jsonb('vars2', 'j2');
+                  pgv_get_jsonb                   
+--------------------------------------------------
+ {"bar": "baz", "active": false, "balance": 7.77}
+(1 row)
+
+-- Check values after rollback to savepoint
+ROLLBACK TO comm2;
+SELECT pgv_get('vars', 'any1',NULL::text);
+    pgv_get    
+---------------
+ another value
+(1 row)
+
+SELECT pgv_get('vars', 'any2',NULL::text);
+    pgv_get     
+----------------
+ one more value
+(1 row)
+
+SELECT pgv_get_int('vars', 'int1');
+ pgv_get_int 
+-------------
+         103
+(1 row)
+
+SELECT pgv_get_int('vars', 'int2');
+ pgv_get_int 
+-------------
+         102
+(1 row)
+
+SELECT pgv_get_int('vars', 'intNULL');
+ pgv_get_int 
+-------------
+         104
+(1 row)
+
+SELECT pgv_get_text('vars', 'str1');
+ pgv_get_text 
+--------------
+ s103
+(1 row)
+
+SELECT pgv_get_text('vars', 'str2');
+ pgv_get_text 
+--------------
+ s102
+(1 row)
+
+SELECT pgv_get_numeric('vars', 'num1');
+ pgv_get_numeric 
+-----------------
+            1.03
+(1 row)
+
+SELECT pgv_get_numeric('vars', 'num2');
+ pgv_get_numeric 
+-----------------
+            1.02
+(1 row)
+
+SELECT pgv_get_timestamp('vars', 'ts1');
+    pgv_get_timestamp     
+--------------------------
+ Wed Mar 30 12:00:00 2016
+(1 row)
+
+SELECT pgv_get_timestamp('vars', 'ts2');
+    pgv_get_timestamp     
+--------------------------
+ Wed Mar 30 11:00:00 2016
+(1 row)
+
+SELECT pgv_get_timestamptz('vars', 'tstz1');
+     pgv_get_timestamptz      
+------------------------------
+ Wed Mar 30 18:00:00 2016 MSK
+(1 row)
+
+SELECT pgv_get_timestamptz('vars', 'tstz2');
+     pgv_get_timestamptz      
+------------------------------
+ Wed Mar 30 16:00:00 2016 MSK
+(1 row)
+
+SELECT pgv_get_date('vars', 'd1');
+ pgv_get_date 
+--------------
+ 04-02-2016
+(1 row)
+
+SELECT pgv_get_date('vars', 'd2');
+ pgv_get_date 
+--------------
+ 03-30-2016
+(1 row)
+
+SELECT pgv_get_jsonb('vars2', 'j1');
+                    pgv_get_jsonb                    
+-----------------------------------------------------
+ {"foo": [true, "bar"], "tags": {"a": 1, "b": null}}
+(1 row)
+
+SELECT pgv_get_jsonb('vars2', 'j2');
+                  pgv_get_jsonb                   
+--------------------------------------------------
+ {"bar": "baz", "active": false, "balance": 7.77}
+(1 row)
+
+COMMIT;
+-- Record variables
+BEGIN;
+SAVEPOINT comm2;
+SELECT pgv_delete('vars3', 'r1', 5);
+ pgv_delete 
+------------
+ t
+(1 row)
+
+SELECT pgv_delete('vars3', 'r2', 5);
+ pgv_delete 
+------------
+ t
+(1 row)
+
+SELECT pgv_select('vars3', 'r1');
+ pgv_select 
+------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (0,str00)
+(4 rows)
+
+SELECT pgv_select('vars3', 'r2');
+ pgv_select 
+------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (0,str00)
+(4 rows)
+
+ROLLBACK to comm2;
+SELECT pgv_select('vars3', 'r1');
+ pgv_select 
+------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (5,str55)
+ (0,str00)
+(5 rows)
+
+SELECT pgv_select('vars3', 'r2');
+ pgv_select 
+------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (0,str00)
+(4 rows)
+
+COMMIT;
+-- TRYING TO CHANGE FLAG 'IS_TRANSACTIONAL'
+SELECT pgv_set('vars', 'any1', 'value'::text);
+ERROR:  variable "any1" already created as TRANSACTIONAL
+SELECT pgv_set('vars', 'any2', 'value'::text, true);
+ERROR:  variable "any2" already created as NOT TRANSACTIONAL
+SELECT pgv_set_int('vars', 'int1', 301);
+ERROR:  variable "int1" already created as TRANSACTIONAL
+SELECT pgv_set_int('vars', 'int2', 302, true);
+ERROR:  variable "int2" already created as NOT TRANSACTIONAL
+SELECT pgv_set_text('vars', 'str1', 's301');
+ERROR:  variable "str1" already created as TRANSACTIONAL
+SELECT pgv_set_text('vars', 'str2', 's302', true);
+ERROR:  variable "str2" already created as NOT TRANSACTIONAL
+SELECT pgv_set_numeric('vars', 'num1', 3.01);
+ERROR:  variable "num1" already created as TRANSACTIONAL
+SELECT pgv_set_numeric('vars', 'num2', 3.02, true);
+ERROR:  variable "num2" already created as NOT TRANSACTIONAL
+SELECT pgv_set_timestamp('vars', 'ts1', '2016-03-30 20:00:00');
+ERROR:  variable "ts1" already created as TRANSACTIONAL
+SELECT pgv_set_timestamp('vars', 'ts2', '2016-03-30 21:00:00', true);
+ERROR:  variable "ts2" already created as NOT TRANSACTIONAL
+SELECT pgv_set_timestamptz('vars', 'tstz1', '2016-03-30 20:00:00 GMT+01');
+ERROR:  variable "tstz1" already created as TRANSACTIONAL
+SELECT pgv_set_timestamptz('vars', 'tstz2', '2016-03-30 21:00:00 GMT+02', true);
+ERROR:  variable "tstz2" already created as NOT TRANSACTIONAL
+SELECT pgv_set_date('vars', 'd1', '2016-04-29');
+ERROR:  variable "d1" already created as TRANSACTIONAL
+SELECT pgv_set_date('vars', 'd2', '2016-04-30', true);
+ERROR:  variable "d2" already created as NOT TRANSACTIONAL
+SELECT pgv_set_jsonb('vars2', 'j1', '[1, 2, "foo2", null]');
+ERROR:  variable "j1" already created as TRANSACTIONAL
+SELECT pgv_set_jsonb('vars2', 'j2', '{"bar": "baz2", "balance": 7.77, "active": true}', true);
+ERROR:  variable "j2" already created as NOT TRANSACTIONAL
+SELECT pgv_insert('vars3', 'r1', row(6 :: integer, 'str66' :: varchar));
+ERROR:  variable "r1" already created as TRANSACTIONAL
+SELECT pgv_insert('vars3', 'r2', row(6 :: integer, 'str66' :: varchar),true);
+ERROR:  variable "r2" already created as NOT TRANSACTIONAL
+-- CHECK pgv_list() WHILE WE HAVE A LOT OF MISCELLANEOUS VARIABLES
+SELECT * FROM pgv_list() order by package, name;
+ package |  name   | is_transactional 
+---------+---------+------------------
+ vars    | any1    | t
+ vars    | any2    | f
+ vars    | d1      | t
+ vars    | d2      | f
+ vars    | int1    | t
+ vars    | int2    | f
+ vars    | intNULL | t
+ vars    | num1    | t
+ vars    | num2    | f
+ vars    | str1    | t
+ vars    | str2    | f
+ vars    | ts1     | t
+ vars    | ts2     | f
+ vars    | tstz1   | t
+ vars    | tstz2   | f
+ vars2   | j1      | t
+ vars2   | j2      | f
+ vars3   | r1      | t
+ vars3   | r2      | f
+(19 rows)
+
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+-- VARIABLES DECLARED IN SUBTRANSACTION SHOULD BE DESTROYED AFTER ROLLBACK TO SAVEPOINT
+-- For better readability we don't use deprecated api functions in test below
+BEGIN;
+SAVEPOINT sp_to_rollback;
+SELECT pgv_set('vars', 'any1', 'text value'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT pgv_set('vars', 'any2', 'text value'::text);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT pgv_insert('vars3', 'r1', row(6 :: integer, 'str44' :: varchar), true);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('vars3', 'r2', row(6 :: integer, 'str44' :: varchar));
+ pgv_insert 
+------------
+ 
+(1 row)
+
+ROLLBACK TO sp_to_rollback;
+COMMIT;
+SELECT pgv_get('vars', 'any1',NULL::text);
+ERROR:  unrecognized variable "any1"
+SELECT pgv_get('vars', 'any2',NULL::text);
+  pgv_get   
+------------
+ text value
+(1 row)
+
+SELECT pgv_select('vars3', 'r1');
+ERROR:  unrecognized variable "r1"
+SELECT pgv_select('vars3', 'r2');
+ pgv_select 
+------------
+ (6,str44)
+(1 row)
+
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+-- CHECK ROLLBACK AFTER COMMITTING SUBTRANSACTION
+BEGIN;
+SELECT pgv_set('vars', 'any1', 'before savepoint sp1'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SAVEPOINT sp1;
+SELECT pgv_set('vars', 'any1', 'after savepoint sp1'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SAVEPOINT sp2;
+SELECT pgv_set('vars', 'any1', 'after savepoint sp2'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+RELEASE sp2;
+SELECT pgv_get('vars', 'any1',NULL::text);
+       pgv_get       
+---------------------
+ after savepoint sp2
+(1 row)
+
+ROLLBACK TO sp1;
+SELECT pgv_get('vars', 'any1',NULL::text);
+       pgv_get        
+----------------------
+ before savepoint sp1
+(1 row)
+
+COMMIT;
+BEGIN;
+SAVEPOINT sp1;
+SAVEPOINT sp2;
+SELECT pgv_set('vars2', 'any1', 'variable exists'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+RELEASE sp2;
+SELECT pgv_get('vars2', 'any1',NULL::text);
+     pgv_get     
+-----------------
+ variable exists
+(1 row)
+
+ROLLBACK TO sp1;
+COMMIT;
+SELECT pgv_get('vars2', 'any1',NULL::text);
+ERROR:  unrecognized package "vars2"
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+--CHECK TRANSACTION COMMIT
+-- Declare variables
+SELECT pgv_set('vars', 'any1', 'some value'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT pgv_set('vars', 'any2', 'some value'::text);
+ pgv_set 
+---------
+ 
+(1 row)
+
+BEGIN;
+-- Set new values
+SELECT pgv_set('vars', 'any1', 'another value'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT pgv_set('vars', 'any2', 'another value'::text);
+ pgv_set 
+---------
+ 
+(1 row)
+
+-- Check values before committing transaction
+SELECT pgv_get('vars', 'any1',NULL::text);
+    pgv_get    
+---------------
+ another value
+(1 row)
+
+SELECT pgv_get('vars', 'any2',NULL::text);
+    pgv_get    
+---------------
+ another value
+(1 row)
+
+-- Check values after committing transaction
+COMMIT;
+SELECT pgv_get('vars', 'any1',NULL::text);
+    pgv_get    
+---------------
+ another value
+(1 row)
+
+SELECT pgv_get('vars', 'any2',NULL::text);
+    pgv_get    
+---------------
+ another value
+(1 row)
+
+SELECT pgv_insert('vars3', 'r1', tab, true) FROM tab;
+ pgv_insert 
+------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT pgv_insert('vars3', 'r2', tab) FROM tab;
+ pgv_insert 
+------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+BEGIN;
+SELECT pgv_insert('vars3', 'r1', row(5 :: integer, 'str55' :: varchar),true);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('vars3', 'r2', row(5 :: integer, 'str55' :: varchar));
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('vars3', 'r1');
+ pgv_select 
+------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (5,str55)
+ (0,str00)
+(5 rows)
+
+SELECT pgv_select('vars3', 'r2');
+ pgv_select 
+------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (5,str55)
+ (0,str00)
+(5 rows)
+
+COMMIT;
+SELECT pgv_select('vars3', 'r1');
+ pgv_select 
+------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (5,str55)
+ (0,str00)
+(5 rows)
+
+SELECT pgv_select('vars3', 'r2');
+ pgv_select 
+------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (5,str55)
+ (0,str00)
+(5 rows)
+
+-- CHECK TRANSACTION ROLLBACK
+-- Variables are already declared
+BEGIN;
+-- Set new values
+SELECT pgv_set('vars', 'any1', 'one more value'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT pgv_set('vars', 'any2', 'one more value'::text);
+ pgv_set 
+---------
+ 
+(1 row)
+
+-- Check values before rollback
+SELECT pgv_get('vars', 'any1',NULL::text);
+    pgv_get     
+----------------
+ one more value
+(1 row)
+
+SELECT pgv_get('vars', 'any2',NULL::text);
+    pgv_get     
+----------------
+ one more value
+(1 row)
+
+-- Check values after rollback
+ROLLBACK;
+SELECT pgv_get('vars', 'any1',NULL::text);
+    pgv_get    
+---------------
+ another value
+(1 row)
+
+SELECT pgv_get('vars', 'any2',NULL::text);
+    pgv_get     
+----------------
+ one more value
+(1 row)
+
+-- Record variables
+BEGIN;
+SELECT pgv_delete('vars3', 'r1', 5);
+ pgv_delete 
+------------
+ t
+(1 row)
+
+SELECT pgv_delete('vars3', 'r2', 5);
+ pgv_delete 
+------------
+ t
+(1 row)
+
+SELECT pgv_select('vars3', 'r1');
+ pgv_select 
+------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (0,str00)
+(4 rows)
+
+SELECT pgv_select('vars3', 'r2');
+ pgv_select 
+------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (0,str00)
+(4 rows)
+
+ROLLBACK;
+SELECT pgv_select('vars3', 'r1');
+ pgv_select 
+------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (5,str55)
+ (0,str00)
+(5 rows)
+
+SELECT pgv_select('vars3', 'r2');
+ pgv_select 
+------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (0,str00)
+(4 rows)
+
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+-- VARIABLES DECLARED IN TRANSACTION SHOULD BE DESTROYED AFTER ROLLBACK
+BEGIN;
+SELECT pgv_set('vars', 'any1', 'text value'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT pgv_set('vars', 'any2', 'text value'::text);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT pgv_insert('vars', 'r1', row(6 :: integer, 'str44' :: varchar), true);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('vars', 'r2', row(6 :: integer, 'str44' :: varchar));
+ pgv_insert 
+------------
+ 
+(1 row)
+
+ROLLBACK;
+SELECT pgv_get('vars', 'any1',NULL::text);
+ERROR:  unrecognized variable "any1"
+SELECT pgv_get('vars', 'any2',NULL::text);
+  pgv_get   
+------------
+ text value
+(1 row)
+
+SELECT pgv_select('vars', 'r1');
+ERROR:  unrecognized variable "r1"
+SELECT pgv_select('vars', 'r2');
+ pgv_select 
+------------
+ (6,str44)
+(1 row)
+
+SELECT pgv_remove('vars');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+-- CHECK ROLLBACK AFTER COMMITTING SUBTRANSACTION
+SELECT pgv_set('vars', 'any1', 'before transaction block'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+BEGIN;
+SELECT pgv_set('vars', 'any1', 'before savepoint sp1'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SAVEPOINT sp1;
+SELECT pgv_set('vars', 'any1', 'after savepoint sp1'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SAVEPOINT sp2;
+SELECT pgv_set('vars', 'any1', 'after savepoint sp2'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+RELEASE sp2;
+SELECT pgv_get('vars', 'any1',NULL::text);
+       pgv_get       
+---------------------
+ after savepoint sp2
+(1 row)
+
+ROLLBACK TO sp1;
+SELECT pgv_get('vars', 'any1',NULL::text);
+       pgv_get        
+----------------------
+ before savepoint sp1
+(1 row)
+
+ROLLBACK;
+SELECT pgv_get('vars', 'any1',NULL::text);
+         pgv_get          
+--------------------------
+ before transaction block
+(1 row)
+
+BEGIN;
+SAVEPOINT sp1;
+SELECT pgv_set('vars2', 'any1', 'variable exists'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+RELEASE sp1;
+SELECT pgv_get('vars2', 'any1',NULL::text);
+     pgv_get     
+-----------------
+ variable exists
+(1 row)
+
+ROLLBACK;
+SELECT pgv_get('vars2', 'any1',NULL::text);
+ERROR:  unrecognized package "vars2"
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+-- Additional tests
+SELECT pgv_insert('vars3', 'r1', tab, true) FROM tab;
+ pgv_insert 
+------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+BEGIN;
+SELECT pgv_insert('vars3', 'r1', row(5 :: integer, 'before savepoint sp1' :: varchar),true);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SAVEPOINT sp1;
+SELECT pgv_update('vars3', 'r1', row(5 :: integer, 'after savepoint sp1' :: varchar));
+ pgv_update 
+------------
+ t
+(1 row)
+
+SAVEPOINT sp2;
+SELECT pgv_insert('vars3', 'r1', row(7 :: integer, 'row after sp2 to remove in sp4' :: varchar),true);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SAVEPOINT sp3;
+SAVEPOINT sp4;
+SELECT pgv_delete('vars3', 'r1', 7);
+ pgv_delete 
+------------
+ t
+(1 row)
+
+SAVEPOINT sp5;
+SELECT pgv_select('vars3', 'r1');
+        pgv_select         
+---------------------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (5,"after savepoint sp1")
+ (0,str00)
+(5 rows)
+
+ROLLBACK TO sp5;
+SELECT pgv_select('vars3', 'r1');
+        pgv_select         
+---------------------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (5,"after savepoint sp1")
+ (0,str00)
+(5 rows)
+
+RELEASE sp4;
+SELECT pgv_select('vars3', 'r1');
+        pgv_select         
+---------------------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (5,"after savepoint sp1")
+ (0,str00)
+(5 rows)
+
+ROLLBACK TO sp3;
+SELECT pgv_select('vars3', 'r1');
+              pgv_select              
+--------------------------------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (5,"after savepoint sp1")
+ (0,str00)
+ (7,"row after sp2 to remove in sp4")
+(6 rows)
+
+RELEASE sp2;
+SELECT pgv_select('vars3', 'r1');
+              pgv_select              
+--------------------------------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (5,"after savepoint sp1")
+ (0,str00)
+ (7,"row after sp2 to remove in sp4")
+(6 rows)
+
+ROLLBACK TO sp1;
+SELECT pgv_select('vars3', 'r1');
+         pgv_select         
+----------------------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (5,"before savepoint sp1")
+ (0,str00)
+(5 rows)
+
+COMMIT;
+SELECT pgv_select('vars3', 'r1');
+         pgv_select         
+----------------------------
+ (,strNULL)
+ (1,str33)
+ (2,)
+ (5,"before savepoint sp1")
+ (0,str00)
+(5 rows)
+
+SELECT pgv_set('vars', 'any1', 'outer'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+BEGIN;
+SELECT pgv_set('vars', 'any1', 'begin'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SAVEPOINT sp1;
+SELECT pgv_set('vars', 'any1', 'sp1'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SAVEPOINT sp2;
+SELECT pgv_set('vars', 'any1', 'sp2'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SAVEPOINT sp3;
+SAVEPOINT sp4;
+SELECT pgv_set('vars', 'any1', 'sp4'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SAVEPOINT sp5;
+SELECT pgv_get('vars', 'any1',NULL::text);
+ pgv_get 
+---------
+ sp4
+(1 row)
+
+ROLLBACK TO sp5;
+SELECT pgv_get('vars', 'any1',NULL::text);
+ pgv_get 
+---------
+ sp4
+(1 row)
+
+RELEASE sp4;
+SELECT pgv_get('vars', 'any1',NULL::text);
+ pgv_get 
+---------
+ sp4
+(1 row)
+
+ROLLBACK TO sp3;
+SELECT pgv_get('vars', 'any1',NULL::text);
+ pgv_get 
+---------
+ sp2
+(1 row)
+
+RELEASE sp2;
+SELECT pgv_get('vars', 'any1',NULL::text);
+ pgv_get 
+---------
+ sp2
+(1 row)
+
+ROLLBACK TO sp1;
+SELECT pgv_get('vars', 'any1',NULL::text);
+ pgv_get 
+---------
+ begin
+(1 row)
+
+ROLLBACK;
+SELECT pgv_get('vars', 'any1',NULL::text);
+ pgv_get 
+---------
+ outer
+(1 row)
+
+BEGIN;
+SELECT pgv_set('vars', 'any1', 'wrong type'::varchar, true);
+ERROR:  variable "any1" requires "text" value
+COMMIT;
+-- THE REMOVAL OF THE VARIABLE MUST BE CANCELED ON ROLLBACK
+SELECT pgv_set('vars', 'any1', 'variable exists'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+BEGIN;
+SELECT pgv_remove('vars', 'any1');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+SELECT pgv_exists('vars', 'any1');
+ pgv_exists 
+------------
+ f
+(1 row)
+
+ROLLBACK;
+SELECT pgv_exists('vars', 'any1');
+ pgv_exists 
+------------
+ t
+(1 row)
+
+SELECT pgv_get('vars', 'any1',NULL::text);
+     pgv_get     
+-----------------
+ variable exists
+(1 row)
+
+BEGIN;
+SELECT pgv_remove('vars', 'any1');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+SELECT pgv_exists('vars', 'any1');
+ pgv_exists 
+------------
+ f
+(1 row)
+
+COMMIT;
+SELECT pgv_exists('vars', 'any1');
+ pgv_exists 
+------------
+ f
+(1 row)
+
+SELECT pgv_get('vars', 'any1',NULL::text);
+ERROR:  unrecognized package "vars"
+SELECT * FROM pgv_list() ORDER BY package, name;
+ package | name | is_transactional 
+---------+------+------------------
+ vars3   | r1   | t
+(1 row)
+
+BEGIN;
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+ROLLBACK;
+SELECT * FROM pgv_list() ORDER BY package, name;
+ package | name | is_transactional 
+---------+------+------------------
+ vars3   | r1   | t
+(1 row)
+
+BEGIN;
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+COMMIT;
+SELECT * FROM pgv_list() ORDER BY package, name;
+ package | name | is_transactional 
+---------+------+------------------
+(0 rows)
+
+SELECT pgv_set('vars', 'regular', 'regular variable exists'::text);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT pgv_set('vars', 'trans1', 'trans1 variable exists'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+BEGIN;
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+SELECT pgv_free(); -- Check sequential package removal in one subtransaction
+ pgv_free 
+----------
+ 
+(1 row)
+
+SELECT * FROM pgv_list() ORDER BY package, name;
+ package | name | is_transactional 
+---------+------+------------------
+(0 rows)
+
+SELECT pgv_set('vars', 'trans2', 'trans2 variable exists'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT * FROM pgv_list() ORDER BY package, name;
+ package |  name  | is_transactional 
+---------+--------+------------------
+ vars    | trans2 | t
+(1 row)
+
+SELECT pgv_remove('vars');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+SELECT * FROM pgv_list() ORDER BY package, name;
+ package | name | is_transactional 
+---------+------+------------------
+(0 rows)
+
+ROLLBACK;
+SELECT * FROM pgv_list() ORDER BY package, name;
+ package |  name  | is_transactional 
+---------+--------+------------------
+ vars    | trans1 | t
+(1 row)
+
+BEGIN;
+SAVEPOINT sp1;
+SAVEPOINT sp2;
+SAVEPOINT sp3;
+SELECT pgv_set('vars2', 'trans2', 'trans2 variable exists'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SAVEPOINT sp4;
+SAVEPOINT sp5;
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+SELECT package FROM pgv_stats() ORDER BY package;
+ package 
+---------
+ vars
+ vars2
+(2 rows)
+
+SELECT * FROM pgv_list() ORDER BY package, name;
+ package | name | is_transactional 
+---------+------+------------------
+(0 rows)
+
+RELEASE sp5;
+SELECT package FROM pgv_stats() ORDER BY package;
+ package 
+---------
+ vars
+ vars2
+(2 rows)
+
+SELECT * FROM pgv_list() ORDER BY package, name;
+ package | name | is_transactional 
+---------+------+------------------
+(0 rows)
+
+RELEASE sp4;
+SELECT package FROM pgv_stats() ORDER BY package;
+ package 
+---------
+ vars
+ vars2
+(2 rows)
+
+SELECT * FROM pgv_list() ORDER BY package, name;
+ package | name | is_transactional 
+---------+------+------------------
+(0 rows)
+
+COMMIT;
+SELECT package FROM pgv_stats() ORDER BY package;
+ package 
+---------
+(0 rows)
+
+BEGIN;
+SELECT pgv_set('vars', 'trans1', 'package created'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT pgv_remove('vars');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+SELECT * FROM pgv_list() ORDER BY package, name;
+ package | name | is_transactional 
+---------+------+------------------
+(0 rows)
+
+SELECT pgv_set('vars', 'trans1', 'package restored'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT * FROM pgv_list() ORDER BY package, name;
+ package |  name  | is_transactional 
+---------+--------+------------------
+ vars    | trans1 | t
+(1 row)
+
+COMMIT;
+SELECT pgv_remove('vars');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+-- REMOVED TRANSACTIONAL VARIABLE SHOULD BE NOT ACCESSIBLE THROUGH LastVariable
+SELECT pgv_insert('package', 'errs',row(n), true)
+FROM generate_series(1,5) AS gs(n) WHERE 1.0/(n-3)<>0;
+ERROR:  division by zero
+SELECT pgv_insert('package', 'errs',row(1), true);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+-- Variable should not exists in case when error occurs during creation
+SELECT pgv_insert('vars4', 'r1', row('str1', 'str1'));
+ERROR:  could not identify a hash function for type unknown
+SELECT pgv_select('vars4', 'r1', 0);
+ERROR:  unrecognized package "vars4"
+-- If variable created and removed in same transaction level,
+-- it should be totally removed and should not be present
+-- in changes list and cache.
+BEGIN;
+SELECT pgv_set('vars', 'any1', 'some value'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SAVEPOINT comm;
+SELECT pgv_remove('vars', 'any1');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+RELEASE comm;
+SELECT pgv_get('vars', 'any1',NULL::text);
+ERROR:  unrecognized package "vars"
+COMMIT;
+-- Tests for PGPRO-2440
+SELECT pgv_insert('vars3', 'r3', row(1 :: integer, NULL::varchar), true);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+SELECT pgv_insert('vars3', 'r3', row(2 :: integer, NULL::varchar), true);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SAVEPOINT comm;
+SELECT pgv_insert('vars3', 'r3', row(3 :: integer, NULL::varchar), true);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+COMMIT;
+SELECT pgv_delete('vars3', 'r3', 3);
+ pgv_delete 
+------------
+ t
+(1 row)
+
+BEGIN;
+SELECT pgv_set('vars1', 't1', ''::text);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT pgv_set('vars2', 't2', ''::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SAVEPOINT sp1;
+SAVEPOINT sp2;
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+ERROR;
+ERROR:  syntax error at or near "ERROR"
+LINE 1: ERROR;
+        ^
+COMMIT;
+BEGIN;
+SELECT pgv_set('vars', 'any1', 'some value'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+SAVEPOINT sp_to_rollback;
+SELECT pgv_set('vars', 'any1', 'some value'::text, true);
+ pgv_set 
+---------
+ 
+(1 row)
+
+ROLLBACK TO sp_to_rollback;
+COMMIT;
+SELECT package FROM pgv_stats() ORDER BY package;
+ package 
+---------
+(0 rows)
+
+-- Package should exist after rollback if it contains regular variable
+BEGIN;
+SELECT pgv_set('vars', 'any1', 'some value'::text);
+ pgv_set 
+---------
+ 
+(1 row)
+
+ROLLBACK;
+SELECT package FROM pgv_stats() ORDER BY package;
+ package 
+---------
+ vars
+(1 row)
+
+-- Package should not exist if it becomes empty in rolled back transaction
+BEGIN;
+SAVEPOINT comm2;
+SELECT pgv_remove('vars');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+ROLLBACK TO comm2;
+SELECT pgv_exists('vars');
+ pgv_exists 
+------------
+ f
+(1 row)
+
+SELECT package FROM pgv_stats() ORDER BY package;
+ package 
+---------
+ vars
+(1 row)
+
+COMMIT;
+SELECT package FROM pgv_stats() ORDER BY package;
+ package 
+---------
+(0 rows)
+
+SELECT pgv_set('vars', 'any1', 'some value'::text);
+ pgv_set 
+---------
+ 
+(1 row)
+
+BEGIN;
+SELECT pgv_remove('vars');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+ROLLBACK;
+SELECT package FROM pgv_stats() ORDER BY package;
+ package 
+---------
+(0 rows)
+
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+-- Variables should be insertable after pgv_remove (variable)
+BEGIN;
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_remove('test', 'x');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'x', ROW (1::int, 3::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,3)
+(1 row)
+
+SELECT pgv_insert('test', 'x', ROW (2::int, 4::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,3)
+ (2,4)
+(2 rows)
+
+ROLLBACK;
+SELECT * FROM pgv_list() order by package, name;
+ package | name | is_transactional 
+---------+------+------------------
+(0 rows)
+
+BEGIN;
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_remove('test', 'x');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'x', ROW (1::int, 3::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,3)
+(1 row)
+
+SELECT pgv_insert('test', 'x', ROW (2::int, 4::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,3)
+ (2,4)
+(2 rows)
+
+COMMIT;
+SELECT * FROM pgv_list() order by package, name;
+ package | name | is_transactional 
+---------+------+------------------
+ test    | x    | t
+(1 row)
+
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,3)
+ (2,4)
+(2 rows)
+
+-- Variables should be insertable after pgv_remove (package)
+BEGIN;
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_remove('test');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (1::int, 3::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,3)
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (2::int, 4::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,3)
+ (2,4)
+(2 rows)
+
+ROLLBACK;
+SELECT * FROM pgv_list() order by package, name;
+ package | name | is_transactional 
+---------+------+------------------
+ test    | x    | t
+(1 row)
+
+BEGIN;
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_remove('test');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (1::int, 3::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,3)
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (2::int, 4::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,3)
+ (2,4)
+(2 rows)
+
+COMMIT;
+SELECT * FROM pgv_list() order by package, name;
+ package | name | is_transactional 
+---------+------+------------------
+ test    | y    | t
+(1 row)
+
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,3)
+ (2,4)
+(2 rows)
+
+-- Variables should be insertable after pgv_free
+BEGIN;
+SELECT pgv_insert('test', 'z', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'z');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'z', ROW (1::int, 3::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'z');
+ pgv_select 
+------------
+ (1,3)
+(1 row)
+
+SELECT pgv_insert('test', 'z', ROW (2::int, 4::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'z');
+ pgv_select 
+------------
+ (1,3)
+ (2,4)
+(2 rows)
+
+ROLLBACK;
+SELECT * FROM pgv_list() order by package, name;
+ package | name | is_transactional 
+---------+------+------------------
+ test    | y    | t
+(1 row)
+
+BEGIN;
+SELECT pgv_insert('test', 'z', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'z');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'z', ROW (1::int, 3::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'z');
+ pgv_select 
+------------
+ (1,3)
+(1 row)
+
+SELECT pgv_insert('test', 'z', ROW (2::int, 4::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'z');
+ pgv_select 
+------------
+ (1,3)
+ (2,4)
+(2 rows)
+
+COMMIT;
+SELECT * FROM pgv_list() order by package, name;
+ package | name | is_transactional 
+---------+------+------------------
+ test    | z    | t
+(1 row)
+
+SELECT pgv_select('test', 'z');
+ pgv_select 
+------------
+ (1,3)
+ (2,4)
+(2 rows)
+
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+-- Variables should be rollbackable if transactional
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'x', ROW (2::int, 3::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+BEGIN;
+SELECT pgv_remove('test', 'x');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+BEGIN;
+SELECT pgv_remove('test');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+BEGIN;
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+---
+--- Variables should not be rollbackable if not transactional
+---
+-- case 1 (remove var)
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (2::int, 3::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+BEGIN;
+SELECT pgv_remove('test', 'y');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+ROLLBACK;
+SELECT pgv_select('test', 'y');
+ERROR:  unrecognized variable "y"
+-- case 2 (remove pack)
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (2::int, 3::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+BEGIN;
+SELECT pgv_remove('test');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+ROLLBACK;
+SELECT pgv_select('test', 'y');
+ERROR:  unrecognized variable "y"
+-- case 3 (free)
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (2::int, 3::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+BEGIN;
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+ROLLBACK;
+SELECT pgv_select('test', 'y');
+ERROR:  unrecognized variable "y"
+-- clear all
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+---
+--- Cursors test #1 (remove var)
+---
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+SELECT pgv_remove('test', 'x');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'y');
+SELECT pgv_remove('test', 'y');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized variable "y"
+ROLLBACK;
+SELECT pgv_select('test', 'y');
+ERROR:  unrecognized variable "y"
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+---
+--- Cursors test #2 (remove pack)
+---
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+SELECT pgv_remove('test');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'y');
+SELECT pgv_remove('test');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test');
+ERROR:  function pgv_select(unknown) does not exist
+LINE 1: SELECT pgv_select('test');
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+---
+--- Cursors test #3 (free)
+---
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'y');
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test');
+ERROR:  function pgv_select(unknown) does not exist
+LINE 1: SELECT pgv_select('test');
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+---
+--- Cursor test #4
+---
+-- non transactional, remove var
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+SELECT pgv_remove('test', 'x');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ERROR:  unrecognized package "test"
+-- non transactional, remove pac
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+SELECT pgv_remove('test');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ERROR:  unrecognized package "test"
+-- non transactional, free
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ERROR:  unrecognized package "test"
+-- transactional, remove var
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'y');
+SELECT pgv_remove('test', 'y');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+-- transactional, remove pack
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'y');
+SELECT pgv_remove('test');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+-- transactional, free
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'y');
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'y');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+---
+--- Cursor test #5
+---
+-- non transactional
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_remove('test', 'x');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ERROR:  unrecognized package "test"
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_remove('test');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ERROR:  unrecognized package "test"
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ERROR:  unrecognized package "test"
+-- transactional
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_remove('test', 'x');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_remove('test');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized package "test"
+ROLLBACK;
+SELECT pgv_select('test', 'x');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+---
+--- Cursor test #6
+---
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_remove('test', 'x');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+COMMIT;
+---
+--- Tests for "leaked hash_seq_search scan for hash table"
+---
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'x', ROW (2::int, 3::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'x', ROW (3::int, 4::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'x') LIMIT 1;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_select('test', 'x') LIMIT 2;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+SELECT pgv_select('test', 'x') LIMIT 3;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+BEGIN;
+SELECT pgv_select('test', 'x') LIMIT 1;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_select('test', 'x') LIMIT 2;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+SELECT pgv_select('test', 'x') LIMIT 3;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+ROLLBACK;
+BEGIN;
+SELECT pgv_select('test', 'x') LIMIT 1;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_select('test', 'x') LIMIT 2;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+SELECT pgv_select('test', 'x') LIMIT 3;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+COMMIT;
+SELECT pgv_insert('test', 'y', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (2::int, 3::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'y', ROW (3::int, 4::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_select('test', 'y') LIMIT 1;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_select('test', 'y') LIMIT 2;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+SELECT pgv_select('test', 'y') LIMIT 3;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+BEGIN;
+SELECT pgv_select('test', 'y') LIMIT 1;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_select('test', 'y') LIMIT 2;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+SELECT pgv_select('test', 'y') LIMIT 3;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+ROLLBACK;
+BEGIN;
+SELECT pgv_select('test', 'y') LIMIT 1;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_select('test', 'y') LIMIT 2;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+SELECT pgv_select('test', 'y') LIMIT 3;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+COMMIT;
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+DECLARE r2_cur CURSOR FOR SELECT pgv_select('test', 'y');
+DECLARE r3_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+FETCH 1 in r2_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+FETCH 1 in r3_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+ROLLBACK;
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+DECLARE r2_cur CURSOR FOR SELECT pgv_select('test', 'y');
+DECLARE r3_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 2 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+FETCH 2 in r2_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+FETCH 2 in r3_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+ROLLBACK;
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+DECLARE r2_cur CURSOR FOR SELECT pgv_select('test', 'y');
+DECLARE r3_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 3 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+FETCH 3 in r2_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+FETCH 3 in r3_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+ROLLBACK;
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+DECLARE r2_cur CURSOR FOR SELECT pgv_select('test', 'y');
+DECLARE r3_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+FETCH 1 in r2_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+FETCH 1 in r3_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+COMMIT;
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+DECLARE r2_cur CURSOR FOR SELECT pgv_select('test', 'y');
+DECLARE r3_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 2 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+FETCH 2 in r2_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+FETCH 2 in r3_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+COMMIT;
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+DECLARE r2_cur CURSOR FOR SELECT pgv_select('test', 'y');
+DECLARE r3_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 3 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+FETCH 3 in r2_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+FETCH 3 in r3_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+COMMIT;
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+DECLARE r2_cur CURSOR FOR SELECT pgv_select('test', 'y');
+DECLARE r3_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+FETCH 2 in r2_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+FETCH 3 in r3_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+ROLLBACK;
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+DECLARE r2_cur CURSOR FOR SELECT pgv_select('test', 'y');
+DECLARE r3_cur CURSOR FOR SELECT pgv_select('test', 'x');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+FETCH 2 in r2_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+FETCH 3 in r3_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+ (3,4)
+(3 rows)
+
+COMMIT;
+---
+--- Some special cases
+---
+-- take #1
+SELECT pgv_insert('test', 'z1', ROW (2::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'z1');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (2,2)
+(1 row)
+
+SELECT pgv_remove('test', 'z1');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized variable "z1"
+ROLLBACK;
+SELECT pgv_select('test', 'z1');
+ pgv_select 
+------------
+ (2,2)
+(1 row)
+
+-- take #2
+SELECT pgv_insert('test', 'z2', ROW (2::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'z2');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (2,2)
+(1 row)
+
+SELECT pgv_remove('test', 'z2');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized variable "z2"
+ROLLBACK;
+SELECT pgv_select('test', 'z2');
+ERROR:  unrecognized variable "z2"
+SELECT pgv_insert('test', 'z2', ROW (1::int, 2::int), FALSE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+-- take #3
+SELECT pgv_insert('test', 'z3', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'z3');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_remove('test', 'z3');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+FETCH 1 in r1_cur;
+ERROR:  unrecognized variable "z3"
+ROLLBACK;
+SELECT pgv_select('test', 'z3');
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'z3');
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+SELECT pgv_remove('test', 'z3');
+ pgv_remove 
+------------
+ 
+(1 row)
+
+COMMIT;
+SELECT pgv_select('test', 'z3');
+ERROR:  unrecognized variable "z3"
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+-- take #4
+SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test', 'x', ROW (2::int, 3::int), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+SAVEPOINT sp1;
+FETCH 1 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+ROLLBACK TO SAVEPOINT sp1;
+COMMIT;
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+SAVEPOINT sp1;
+FETCH 2 in r1_cur;
+ pgv_select 
+------------
+ (1,2)
+ (2,3)
+(2 rows)
+
+ROLLBACK TO SAVEPOINT sp1;
+COMMIT;
+BEGIN;
+SAVEPOINT sp1;
+SELECT pgv_select('test', 'x') LIMIT 1;
+ pgv_select 
+------------
+ (1,2)
+(1 row)
+
+ROLLBACK TO SAVEPOINT sp1;
+COMMIT;
+---
+--- Test cases for pgv_stats
+---
+SELECT pgv_insert('test1', 'y', ROW (2::float, 1::float), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SELECT pgv_insert('test1', 'x', ROW (2::float, 1::float), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
+DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
+FETCH 1 in r1_cur;
+  pgv_stats   
+--------------
+ (test,32768)
+(1 row)
+
+FETCH 1 in r2_cur;
+  pgv_stats   
+--------------
+ (test,32768)
+(1 row)
+
+COMMIT;
+SELECT pgv_insert('test1', 'y', ROW (2::float, 1::float), TRUE);
+ERROR:  there is a record in the variable "y" with same key
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
+DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
+FETCH 1 in r1_cur;
+  pgv_stats   
+--------------
+ (test,32768)
+(1 row)
+
+FETCH 1 in r2_cur;
+  pgv_stats   
+--------------
+ (test,32768)
+(1 row)
+
+ROLLBACK;
+SELECT pgv_insert('test1', 'y', ROW (2::float, 1::float), FALSE);
+ERROR:  variable "y" already created as TRANSACTIONAL
+SELECT pgv_insert('test1', 'y', ROW (2::float, 1::float), FALSE);
+ERROR:  variable "y" already created as TRANSACTIONAL
+SELECT pgv_insert('test1', 'x', ROW (2::float, 1::float), FALSE);
+ERROR:  variable "x" already created as TRANSACTIONAL
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
+DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
+FETCH 1 in r1_cur;
+  pgv_stats   
+--------------
+ (test,32768)
+(1 row)
+
+FETCH 1 in r2_cur;
+  pgv_stats   
+--------------
+ (test,32768)
+(1 row)
+
+COMMIT;
+SELECT pgv_insert('test1', 'y', ROW (2::float, 1::float), FALSE);
+ERROR:  variable "y" already created as TRANSACTIONAL
+BEGIN;
+DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
+DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
+FETCH 1 in r1_cur;
+  pgv_stats   
+--------------
+ (test,32768)
+(1 row)
+
+FETCH 1 in r2_cur;
+  pgv_stats   
+--------------
+ (test,32768)
+(1 row)
+
+ROLLBACK;
+SELECT pgv_insert('test1', 'y', ROW (2::float, 1::float), FALSE);
+ERROR:  variable "y" already created as TRANSACTIONAL
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+
+---
+--- Some special cases
+---
+-- 1
+BEGIN;
+SAVEPOINT comm2;
+SELECT pgv_insert('test', 'x1', ROW (2::float, 1::float), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
+DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
+FETCH 1 in r1_cur;
+  pgv_stats   
+--------------
+ (test,24576)
+(1 row)
+
+FETCH 1 in r2_cur;
+  pgv_stats   
+--------------
+ (test,24576)
+(1 row)
+
+COMMIT;
+-- 2
+BEGIN;
+SELECT pgv_insert('test', 'x2', ROW (2::float, 1::float), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+SAVEPOINT comm2;
+DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
+DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
+FETCH 1 in r1_cur;
+  pgv_stats   
+--------------
+ (test,40960)
+(1 row)
+
+FETCH 1 in r2_cur;
+  pgv_stats   
+--------------
+ (test,40960)
+(1 row)
+
+COMMIT;
+-- 3
+BEGIN;
+SELECT pgv_insert('test', 'x3', ROW (2::float, 1::float), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
+DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
+SAVEPOINT comm2;
+FETCH 1 in r1_cur;
+  pgv_stats   
+--------------
+ (test,57344)
+(1 row)
+
+FETCH 1 in r2_cur;
+  pgv_stats   
+--------------
+ (test,57344)
+(1 row)
+
+COMMIT;
+-- 4
+BEGIN;
+SELECT pgv_insert('test', 'x4', ROW (2::float, 1::float), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
+DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
+FETCH 1 in r1_cur;
+  pgv_stats   
+--------------
+ (test,73728)
+(1 row)
+
+SAVEPOINT comm2;
+FETCH 1 in r2_cur;
+  pgv_stats   
+--------------
+ (test,73728)
+(1 row)
+
+COMMIT;
+-- 5
+BEGIN;
+SELECT pgv_insert('test', 'x5', ROW (2::float, 1::float), TRUE);
+ pgv_insert 
+------------
+ 
+(1 row)
+
+DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
+DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
+FETCH 1 in r1_cur;
+  pgv_stats   
+--------------
+ (test,90112)
+(1 row)
+
+FETCH 1 in r2_cur;
+  pgv_stats   
+--------------
+ (test,90112)
+(1 row)
+
+SAVEPOINT comm2;
+COMMIT;
+SELECT pgv_free();
+ pgv_free 
+----------
+ 
+(1 row)
+

From 62b0dfee3ce263c1940d988562b060c103e572f0 Mon Sep 17 00:00:00 2001
From: Maxim Orlov <m.orlov@postgrespro.ru>
Date: Tue, 3 Nov 2020 16:21:46 +0300
Subject: [PATCH 16/22] Issue #27: Fix savepoint in transaction issue, add
 expected result for 9.5.

---
 expected/pg_variables_trans_1.out | 40 +++++++++++++++----------------
 1 file changed, 20 insertions(+), 20 deletions(-)

diff --git a/expected/pg_variables_trans_1.out b/expected/pg_variables_trans_1.out
index ad84f9d..697ae93 100644
--- a/expected/pg_variables_trans_1.out
+++ b/expected/pg_variables_trans_1.out
@@ -3668,14 +3668,14 @@ SELECT pgv_insert('test', 'x1', ROW (2::float, 1::float), TRUE);
 DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
 DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
 FETCH 1 in r1_cur;
-  pgv_stats   
---------------
+ pgv_stats 
+-----------
  (test,0)
 (1 row)
 
 FETCH 1 in r2_cur;
-  pgv_stats   
---------------
+ pgv_stats 
+-----------
  (test,0)
 (1 row)
 
@@ -3692,14 +3692,14 @@ SAVEPOINT comm2;
 DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
 DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
 FETCH 1 in r1_cur;
-  pgv_stats   
---------------
+ pgv_stats 
+-----------
  (test,0)
 (1 row)
 
 FETCH 1 in r2_cur;
-  pgv_stats   
---------------
+ pgv_stats 
+-----------
  (test,0)
 (1 row)
 
@@ -3716,14 +3716,14 @@ DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
 DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
 SAVEPOINT comm2;
 FETCH 1 in r1_cur;
-  pgv_stats   
---------------
+ pgv_stats 
+-----------
  (test,0)
 (1 row)
 
 FETCH 1 in r2_cur;
-  pgv_stats   
---------------
+ pgv_stats 
+-----------
  (test,0)
 (1 row)
 
@@ -3739,15 +3739,15 @@ SELECT pgv_insert('test', 'x4', ROW (2::float, 1::float), TRUE);
 DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
 DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
 FETCH 1 in r1_cur;
-  pgv_stats   
---------------
+ pgv_stats 
+-----------
  (test,0)
 (1 row)
 
 SAVEPOINT comm2;
 FETCH 1 in r2_cur;
-  pgv_stats   
---------------
+ pgv_stats 
+-----------
  (test,0)
 (1 row)
 
@@ -3763,14 +3763,14 @@ SELECT pgv_insert('test', 'x5', ROW (2::float, 1::float), TRUE);
 DECLARE r1_cur CURSOR FOR SELECT pgv_stats();
 DECLARE r2_cur CURSOR FOR SELECT pgv_stats();
 FETCH 1 in r1_cur;
-  pgv_stats   
---------------
+ pgv_stats 
+-----------
  (test,0)
 (1 row)
 
 FETCH 1 in r2_cur;
-  pgv_stats   
---------------
+ pgv_stats 
+-----------
  (test,0)
 (1 row)
 

From 0f7307942316294efecf6ebb217abc95a4d8f651 Mon Sep 17 00:00:00 2001
From: Maxim Orlov <m.orlov@postgrespro.ru>
Date: Thu, 5 Nov 2020 11:11:59 +0300
Subject: [PATCH 17/22] Issue #27: refactoring.

+ use uniform algorithm for status lists
+ rename struct tagHtabToStat to tagVariableStatEntry
+ use context instead of bunch of args in list workaround routines
---
 pg_variables.c | 338 ++++++++++++++++++++++++-------------------------
 1 file changed, 163 insertions(+), 175 deletions(-)

diff --git a/pg_variables.c b/pg_variables.c
index 0fb344b..0db7887 100644
--- a/pg_variables.c
+++ b/pg_variables.c
@@ -147,14 +147,14 @@ static MemoryContext changesStackContext = NULL;
 static List *variables_stats = NIL;
 static List *packages_stats = NIL;
 
-typedef struct tagHtabToStat
+typedef struct tagVariableStatEntry
 {
 	HTAB			*hash;
 	HASH_SEQ_STATUS	*status;
 	Variable		*variable;
 	Package			*package;
 	int				 level;
-} HtabToStat;
+} VariableStatEntry;
 
 typedef struct tagPackageStatEntry
 {
@@ -163,142 +163,111 @@ typedef struct tagPackageStatEntry
 } PackageStatEntry;
 
 /*
- * A bunch of comp functions for HtabToStat and PackageStatEntry  members here.
+ * Compare functions for VariableStatEntry and PackageStatEntry members.
  */
 static bool
-HtabToStat_status_eq(HtabToStat *entry, void *value)
+VariableStatEntry_status_eq(void *entry, void *value)
 {
-	return entry->status == (HASH_SEQ_STATUS *) value;
+	return ((VariableStatEntry *) entry)->status == (HASH_SEQ_STATUS *) value;
 }
 
 static bool
-HtabToStat_variable_eq(HtabToStat *entry, void *value)
+VariableStatEntry_variable_eq(void *entry, void *value)
 {
-	return entry->variable == (Variable *) value;
+	return ((VariableStatEntry *) entry)->variable == (Variable *) value;
 }
 
 static bool
-HtabToStat_package_eq(HtabToStat *entry, void *value)
+VariableStatEntry_package_eq(void *entry, void *value)
 {
-	return entry->package == (Package *) value;
+	return ((VariableStatEntry *) entry)->package == (Package *) value;
 }
 
 static bool
-HtabToStat_eq_all(HtabToStat *entry, void *value)
+VariableStatEntry_eq_all(void *entry, void *value)
 {
 	return true;
 }
 
 static bool
-HtabToStat_level_eq(HtabToStat *entry, void *value)
+VariableStatEntry_level_eq(void *entry, void *value)
 {
-	return entry->level == *(int *) value;
+	return ((VariableStatEntry *) entry)->level == *(int *) value;
 }
 
 static bool
-PackageStatEntry_status_eq(PackageStatEntry *entry, void *value)
+PackageStatEntry_status_eq(void *entry, void *value)
 {
-	return entry->status == (HASH_SEQ_STATUS *) value;
+	return ((PackageStatEntry *) entry)->status == (HASH_SEQ_STATUS *) value;
 }
 
 static bool
-PackageStatEntry_level_eq(PackageStatEntry *entry, void *value)
+PackageStatEntry_level_eq(void *entry, void *value)
 {
-	return entry->level == *(int *) value;
+	return ((PackageStatEntry *) entry)->level == *(int *) value;
 }
 
 /*
- * Generic remove_if algorithm for HtabToStat.
- *
- * 'eq' - is a function pointer used to compare list entries to the 'value'.
- * 'match_first' - if true return on first match.
+ * VariableStatEntry and PackageStatEntry status member getters.
  */
-static void
-HtabToStat_remove_if(List **l, void *value,
-					 bool (*eq)(HtabToStat *, void *),
-					 bool match_first)
+static HASH_SEQ_STATUS *
+VariableStatEntry_status_ptr(void *entry)
 {
-#if (PG_VERSION_NUM < 130000)
-	ListCell	*cell, *next, *prev = NULL;
-	HtabToStat	*entry = NULL;
-
-	for (cell = list_head(*l); cell; cell = next)
-	{
-		entry = (HtabToStat *) lfirst(cell);
-		next = lnext(cell);
-
-		if (eq(entry, value))
-		{
-			*l = list_delete_cell(*l, cell, prev);
-			pfree(entry->status);
-			pfree(entry);
-
-			if (match_first)
-				return;
-		}
-		else
-		{
-			prev = cell;
-		}
-	}
-#else
-	/*
-	 * See https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=1cff1b95ab6ddae32faa3efe0d95a820dbfdc164
-	 *
-	 * Version >= 13 have different lists interface.
-	 */
-	ListCell	*cell;
-	HtabToStat	*entry = NULL;
-
-	foreach(cell, *l)
-	{
-		entry = (HtabToStat *) lfirst(cell);
-
-		if (eq(entry, value))
-		{
-			*l = foreach_delete_current(*l, cell);
-			pfree(entry->status);
-			pfree(entry);
+	return ((VariableStatEntry *) entry)->status;
+}
 
-			if (match_first)
-				return;
-		}
-	}
-#endif
+static HASH_SEQ_STATUS *
+PackageStatEntry_status_ptr(void *entry)
+{
+	return ((PackageStatEntry *) entry)->status;
 }
 
 /*
- * Generic remove_if algorithm for PackageStatEntry.
+ * Generic remove_if algorithm.
+ *
+ * For every item in the list:
+ *  1. Comapare item with value by eq function call.
+ *  2. If eq return true, then step 3, else goto 7.
+ *  3. Delete item from list.
+ *  4. If term is true, call hash_seq_term.
+ *  5. Free memory.
+ *  6. If match_first if true return.
+ *  7. Fetch next item.
  *
- * 'eq' - is a function pointer used to compare list entries to the 'value'.
- * 'match_first' - if true return on first match.
  */
+typedef struct tagRemoveIfContext
+{
+	List 				**list;					/* target list */
+	void 				*value;					/* value to compare with */
+	bool 				(*eq)(void *, void *);	/* list item eq to value func */
+	HASH_SEQ_STATUS * 	(*getter)(void *);		/* status getter */
+	bool 				match_first;			/* return on first match */
+	bool 				term;					/* hash_seq_term on match */
+} RemoveIfContext;
+
 static void
-PackageStatEntry_remove_if(List **l, void *value,
-						   bool (*eq)(PackageStatEntry *, void *),
-						   bool match_first,
-						   bool term)
+list_remove_if(RemoveIfContext ctx)
 {
 #if (PG_VERSION_NUM < 130000)
-	ListCell			*cell, *next, *prev = NULL;
-	PackageStatEntry	*entry = NULL;
+	ListCell *cell, *next, *prev = NULL;
+	void	 *entry = NULL;
 
-	for (cell = list_head(*l); cell; cell = next)
+	for (cell = list_head(*ctx.list); cell; cell = next)
 	{
-		entry = (PackageStatEntry *) lfirst(cell);
+		entry = lfirst(cell);
 		next = lnext(cell);
 
-		if (eq(entry, value))
+		if (ctx.eq(entry, ctx.value))
 		{
-			*l = list_delete_cell(*l, cell, prev);
+			*ctx.list = list_delete_cell(*ctx.list, cell, prev);
 
-			if (term)
-				hash_seq_term(entry->status);
+			if (ctx.term)
+				hash_seq_term(ctx.getter(entry));
 
-			pfree(entry->status);
+			pfree(ctx.getter(entry));
 			pfree(entry);
 
-			if (match_first)
+			if (ctx.match_first)
 				return;
 		}
 		else
@@ -312,24 +281,24 @@ PackageStatEntry_remove_if(List **l, void *value,
 	 *
 	 * Version >= 13 have different lists interface.
 	 */
-	ListCell			*cell;
-	PackageStatEntry	*entry = NULL;
+	ListCell *cell;
+	void	 *entry = NULL;
 
-	foreach(cell, *l)
+	foreach(cell, *ctx.list)
 	{
-		entry = (PackageStatEntry *) lfirst(cell);
+		entry = lfirst(cell);
 
-		if (eq(entry, value))
+		if (ctx.eq(entry, ctx.value))
 		{
-			*l = foreach_delete_current(*l, cell);
+			*ctx.list = foreach_delete_current(*ctx.list, cell);
 
-			if (term)
-				hash_seq_term(entry->status);
+			if (ctx.term)
+				hash_seq_term(ctx.getter(entry));
 
-			pfree(entry->status);
+			pfree(ctx.getter(entry));
 			pfree(entry);
 
-			if (match_first)
+			if (ctx.match_first)
 				return;
 		}
 	}
@@ -340,66 +309,130 @@ PackageStatEntry_remove_if(List **l, void *value,
  * Remove first entry for status.
  */
 static void
-remove_variables_status(List **l, HASH_SEQ_STATUS *status)
+remove_variables_status(List **list, HASH_SEQ_STATUS *status)
 {
-	HtabToStat_remove_if(l, status, HtabToStat_status_eq, true);
+	RemoveIfContext ctx =
+	{
+		.list		 = list,
+		.value		 = status,
+		.eq			 = VariableStatEntry_status_eq,
+		.getter		 = VariableStatEntry_status_ptr,
+		.match_first = true,
+		.term		 = false
+	};
+	list_remove_if(ctx);
 }
 
 /*
  * Remove first entry for variable.
  */
 static void
-remove_variables_variable(List **l, Variable *variable)
+remove_variables_variable(List **list, Variable *variable)
 {
-	HtabToStat_remove_if(l, variable, HtabToStat_variable_eq, true);
+	/*
+	 * It may be more than one item in the list for each variable in case of
+	 * cursor. So match_first is false here.
+	 */
+	RemoveIfContext ctx =
+	{
+		.list		 = list,
+		.value		 = variable,
+		.eq			 = VariableStatEntry_variable_eq,
+		.getter		 = VariableStatEntry_status_ptr,
+		.match_first = false,
+		.term		 = true
+	};
+	list_remove_if(ctx);
 }
 
 /*
  * Remove all the entries for package.
  */
 static void
-remove_variables_package(List **l, Package *package)
+remove_variables_package(List **list, Package *package)
 {
-	HtabToStat_remove_if(l, package, HtabToStat_package_eq, false);
+	RemoveIfContext ctx =
+	{
+		.list		 = list,
+		.value		 = package,
+		.eq			 = VariableStatEntry_package_eq,
+		.getter		 = VariableStatEntry_status_ptr,
+		.match_first = false,
+		.term		 = true
+	};
+	list_remove_if(ctx);
 }
 
 /*
  * Remove all the entries for level.
  */
 static void
-remove_variables_level(List **l, int level)
+remove_variables_level(List **list, int level)
 {
-	HtabToStat_remove_if(l, &level, HtabToStat_level_eq, false);
+	RemoveIfContext ctx =
+	{
+		.list		 = list,
+		.value		 = &level,
+		.eq			 = VariableStatEntry_level_eq,
+		.getter		 = VariableStatEntry_status_ptr,
+		.match_first = false,
+		.term		 = false
+	};
+	list_remove_if(ctx);
 }
 
 /*
- * Remove all.
+ * Delete variables stats list.
  */
 static void
-remove_variables_all(List **l)
+remove_variables_all(List **list)
 {
-	HtabToStat_remove_if(l, NULL, HtabToStat_eq_all, false);
-	*l = NIL;
+	RemoveIfContext ctx =
+	{
+		.list		 = list,
+		.value		 = NULL,
+		.eq			 = VariableStatEntry_eq_all,
+		.getter		 = VariableStatEntry_status_ptr,
+		.match_first = false,
+		.term		 = true
+	};
+	list_remove_if(ctx);
 }
 
 /*
- * Remove first entry for status for packages.
+ * Remove first entrie with status for packages list.
  */
 static void
-remove_packages_status(List **l, HASH_SEQ_STATUS *status)
+remove_packages_status(List **list, HASH_SEQ_STATUS *status)
 {
-	/* Match first, not term */
-	PackageStatEntry_remove_if(l, status, PackageStatEntry_status_eq, true, false);
+	RemoveIfContext ctx =
+	{
+		.list		 = list,
+		.value		 = status,
+		.eq			 = PackageStatEntry_status_eq,
+		.getter		 = PackageStatEntry_status_ptr,
+		.match_first = true,
+		.term		 = false
+	};
+	list_remove_if(ctx);
 }
 
 /*
- * Remove first entry for status for packages.
+ * Remove all the entries with level for packages list.
  */
 static void
-remove_packages_level(List **l, int level)
+remove_packages_level(List **list, int level)
 {
-	/* Match all, term */
-	PackageStatEntry_remove_if(l, &level, PackageStatEntry_level_eq, false, true);
+	RemoveIfContext ctx =
+	{
+		.list		 = list,
+		.value		 = &level,
+		.eq			 = PackageStatEntry_level_eq,
+		.getter		 = PackageStatEntry_status_ptr,
+		.match_first = false,
+		.term		 = true
+	};
+	list_remove_if(ctx);
 }
 
 static void freeStatsLists(void);
@@ -886,9 +919,9 @@ variable_select(PG_FUNCTION_ARGS)
 
 	if (SRF_IS_FIRSTCALL())
 	{
-		MemoryContext	 oldcontext;
-		RecordVar		*record;
-		HtabToStat		*htab_to_stat;
+		MemoryContext	 	 oldcontext;
+		RecordVar			*record;
+		VariableStatEntry	*entry;
 
 		record = &(GetActualValue(variable).record);
 		funcctx = SRF_FIRSTCALL_INIT();
@@ -901,13 +934,13 @@ variable_select(PG_FUNCTION_ARGS)
 		hash_seq_init(rstat, record->rhash);
 		funcctx->user_fctx = rstat;
 
-		htab_to_stat = palloc0(sizeof(HtabToStat));
-		htab_to_stat->hash = record->rhash;
-		htab_to_stat->status = rstat;
-		htab_to_stat->variable = variable;
-		htab_to_stat->package = package;
-		htab_to_stat->level = GetCurrentTransactionNestLevel();
-		variables_stats = lcons((void *)htab_to_stat, variables_stats);
+		entry = palloc0(sizeof(VariableStatEntry));
+		entry->hash = record->rhash;
+		entry->status = rstat;
+		entry->variable = variable;
+		entry->package = package;
+		entry->level = GetCurrentTransactionNestLevel();
+		variables_stats = lcons((void *)entry, variables_stats);
 
 		MemoryContextSwitchTo(oldcontext);
 		PG_FREE_IF_COPY(package_name, 0);
@@ -2469,49 +2502,6 @@ pgvSubTransCallback(SubXactEvent event, SubTransactionId mySubid,
 
 	remove_variables_level(&variables_stats, GetCurrentTransactionNestLevel());
 	remove_packages_level(&packages_stats, GetCurrentTransactionNestLevel());
-
-/*#if (PG_VERSION_NUM < 130000)
-	{
-		ListCell			*cell, *next, *prev = NULL;
-		PackageStatEntry	*entry = NULL;
-
-		for (cell = list_head(packages_stats); cell; cell = next)
-		{
-			entry = (PackageStatEntry *) lfirst(cell);
-			next = lnext(cell);
-
-			if (entry->level == GetCurrentTransactionNestLevel())
-			{
-				packages_stats = list_delete_cell(packages_stats, cell, prev);
-				hash_seq_term(entry->status);
-				pfree(entry->status);
-				pfree(entry);
-			}
-			else
-			{
-				prev = cell;
-			}
-		}
-	}
-#else
-	{
-		ListCell			*cell;
-		PackageStatEntry	*entry = NULL;
-
-		foreach(cell, packages_stats)
-		{
-			entry = (PackageStatEntry *) lfirst(cell);
-
-			if (entry->level == GetCurrentTransactionNestLevel())
-			{
-				packages_stats = foreach_delete_current(packages_stats, cell);
-				hash_seq_term(entry->status);
-				pfree(entry->status);
-				pfree(entry);
-			}
-		}
-	}
-#endif*/
 }
 
 /*
@@ -2567,20 +2557,18 @@ static void
 freeStatsLists(void)
 {
 	ListCell		*cell;
-	HtabToStat		*htab_to_stat;
-	PackageStatEntry *entry;
 
 	foreach(cell, variables_stats)
 	{
-		htab_to_stat = (HtabToStat *) lfirst(cell);
-		hash_seq_term(htab_to_stat->status);
+		VariableStatEntry *entry = (VariableStatEntry *) lfirst(cell);
+		hash_seq_term(entry->status);
 	}
 
 	variables_stats = NIL;
 
 	foreach(cell, packages_stats)
 	{
-		entry = (PackageStatEntry *) lfirst(cell);
+		PackageStatEntry *entry = (PackageStatEntry *) lfirst(cell);
 		hash_seq_term(entry->status);
 	}
 

From 88633c3bec33abbb2fd68a41c5c2cbe324aaec20 Mon Sep 17 00:00:00 2001
From: Maxim Orlov <m.orlov@postgrespro.ru>
Date: Thu, 5 Nov 2020 16:26:14 +0300
Subject: [PATCH 18/22] Issue #27: Make sanitizer happy.

At the moment pg_variables can't handle cursors in a "proper" way.
It's possible to remove pg_variables while have active cursor open,
which seem to be erratic and couse sanitizer to be unpappy about it.
---
 expected/pg_variables_trans.out   | 15 +++++++--------
 expected/pg_variables_trans_1.out | 15 +++++++--------
 expected/pg_variables_trans_2.out | 15 +++++++--------
 sql/pg_variables_trans.sql        |  8 +++++---
 4 files changed, 26 insertions(+), 27 deletions(-)

diff --git a/expected/pg_variables_trans.out b/expected/pg_variables_trans.out
index bee104d..944a8a4 100644
--- a/expected/pg_variables_trans.out
+++ b/expected/pg_variables_trans.out
@@ -2850,14 +2850,13 @@ FETCH 1 in r1_cur;
  (1,2)
 (1 row)
 
+CLOSE r1_cur;
 SELECT pgv_remove('test', 'x');
  pgv_remove 
 ------------
  
 (1 row)
 
-FETCH 1 in r1_cur;
-ERROR:  unrecognized package "test"
 ROLLBACK;
 SELECT pgv_select('test', 'x');
 ERROR:  unrecognized package "test"
@@ -2875,14 +2874,13 @@ FETCH 1 in r1_cur;
  (1,2)
 (1 row)
 
+CLOSE r1_cur;
 SELECT pgv_remove('test');
  pgv_remove 
 ------------
  
 (1 row)
 
-FETCH 1 in r1_cur;
-ERROR:  unrecognized package "test"
 ROLLBACK;
 SELECT pgv_select('test', 'x');
 ERROR:  unrecognized package "test"
@@ -2900,14 +2898,13 @@ FETCH 1 in r1_cur;
  (1,2)
 (1 row)
 
+CLOSE r1_cur;
 SELECT pgv_free();
  pgv_free 
 ----------
  
 (1 row)
 
-FETCH 1 in r1_cur;
-ERROR:  unrecognized package "test"
 ROLLBACK;
 SELECT pgv_select('test', 'x');
 ERROR:  unrecognized package "test"
@@ -3440,6 +3437,7 @@ FETCH 1 in r1_cur;
  (2,2)
 (1 row)
 
+CLOSE r1_cur;
 SELECT pgv_remove('test', 'z2');
  pgv_remove 
 ------------
@@ -3447,7 +3445,7 @@ SELECT pgv_remove('test', 'z2');
 (1 row)
 
 FETCH 1 in r1_cur;
-ERROR:  unrecognized variable "z2"
+ERROR:  cursor "r1_cur" does not exist
 ROLLBACK;
 SELECT pgv_select('test', 'z2');
 ERROR:  unrecognized variable "z2"
@@ -3472,6 +3470,7 @@ FETCH 1 in r1_cur;
  (1,2)
 (1 row)
 
+CLOSE r1_cur;
 SELECT pgv_remove('test', 'z3');
  pgv_remove 
 ------------
@@ -3479,7 +3478,7 @@ SELECT pgv_remove('test', 'z3');
 (1 row)
 
 FETCH 1 in r1_cur;
-ERROR:  unrecognized variable "z3"
+ERROR:  cursor "r1_cur" does not exist
 ROLLBACK;
 SELECT pgv_select('test', 'z3');
  pgv_select 
diff --git a/expected/pg_variables_trans_1.out b/expected/pg_variables_trans_1.out
index 697ae93..278272a 100644
--- a/expected/pg_variables_trans_1.out
+++ b/expected/pg_variables_trans_1.out
@@ -2850,14 +2850,13 @@ FETCH 1 in r1_cur;
  (1,2)
 (1 row)
 
+CLOSE r1_cur;
 SELECT pgv_remove('test', 'x');
  pgv_remove 
 ------------
  
 (1 row)
 
-FETCH 1 in r1_cur;
-ERROR:  unrecognized package "test"
 ROLLBACK;
 SELECT pgv_select('test', 'x');
 ERROR:  unrecognized package "test"
@@ -2875,14 +2874,13 @@ FETCH 1 in r1_cur;
  (1,2)
 (1 row)
 
+CLOSE r1_cur;
 SELECT pgv_remove('test');
  pgv_remove 
 ------------
  
 (1 row)
 
-FETCH 1 in r1_cur;
-ERROR:  unrecognized package "test"
 ROLLBACK;
 SELECT pgv_select('test', 'x');
 ERROR:  unrecognized package "test"
@@ -2900,14 +2898,13 @@ FETCH 1 in r1_cur;
  (1,2)
 (1 row)
 
+CLOSE r1_cur;
 SELECT pgv_free();
  pgv_free 
 ----------
  
 (1 row)
 
-FETCH 1 in r1_cur;
-ERROR:  unrecognized package "test"
 ROLLBACK;
 SELECT pgv_select('test', 'x');
 ERROR:  unrecognized package "test"
@@ -3440,6 +3437,7 @@ FETCH 1 in r1_cur;
  (2,2)
 (1 row)
 
+CLOSE r1_cur;
 SELECT pgv_remove('test', 'z2');
  pgv_remove 
 ------------
@@ -3447,7 +3445,7 @@ SELECT pgv_remove('test', 'z2');
 (1 row)
 
 FETCH 1 in r1_cur;
-ERROR:  unrecognized variable "z2"
+ERROR:  cursor "r1_cur" does not exist
 ROLLBACK;
 SELECT pgv_select('test', 'z2');
 ERROR:  unrecognized variable "z2"
@@ -3472,6 +3470,7 @@ FETCH 1 in r1_cur;
  (1,2)
 (1 row)
 
+CLOSE r1_cur;
 SELECT pgv_remove('test', 'z3');
  pgv_remove 
 ------------
@@ -3479,7 +3478,7 @@ SELECT pgv_remove('test', 'z3');
 (1 row)
 
 FETCH 1 in r1_cur;
-ERROR:  unrecognized variable "z3"
+ERROR:  cursor "r1_cur" does not exist
 ROLLBACK;
 SELECT pgv_select('test', 'z3');
  pgv_select 
diff --git a/expected/pg_variables_trans_2.out b/expected/pg_variables_trans_2.out
index 102d550..df11ca6 100644
--- a/expected/pg_variables_trans_2.out
+++ b/expected/pg_variables_trans_2.out
@@ -2850,14 +2850,13 @@ FETCH 1 in r1_cur;
  (1,2)
 (1 row)
 
+CLOSE r1_cur;
 SELECT pgv_remove('test', 'x');
  pgv_remove 
 ------------
  
 (1 row)
 
-FETCH 1 in r1_cur;
-ERROR:  unrecognized package "test"
 ROLLBACK;
 SELECT pgv_select('test', 'x');
 ERROR:  unrecognized package "test"
@@ -2875,14 +2874,13 @@ FETCH 1 in r1_cur;
  (1,2)
 (1 row)
 
+CLOSE r1_cur;
 SELECT pgv_remove('test');
  pgv_remove 
 ------------
  
 (1 row)
 
-FETCH 1 in r1_cur;
-ERROR:  unrecognized package "test"
 ROLLBACK;
 SELECT pgv_select('test', 'x');
 ERROR:  unrecognized package "test"
@@ -2900,14 +2898,13 @@ FETCH 1 in r1_cur;
  (1,2)
 (1 row)
 
+CLOSE r1_cur;
 SELECT pgv_free();
  pgv_free 
 ----------
  
 (1 row)
 
-FETCH 1 in r1_cur;
-ERROR:  unrecognized package "test"
 ROLLBACK;
 SELECT pgv_select('test', 'x');
 ERROR:  unrecognized package "test"
@@ -3440,6 +3437,7 @@ FETCH 1 in r1_cur;
  (2,2)
 (1 row)
 
+CLOSE r1_cur;
 SELECT pgv_remove('test', 'z2');
  pgv_remove 
 ------------
@@ -3447,7 +3445,7 @@ SELECT pgv_remove('test', 'z2');
 (1 row)
 
 FETCH 1 in r1_cur;
-ERROR:  unrecognized variable "z2"
+ERROR:  cursor "r1_cur" does not exist
 ROLLBACK;
 SELECT pgv_select('test', 'z2');
 ERROR:  unrecognized variable "z2"
@@ -3472,6 +3470,7 @@ FETCH 1 in r1_cur;
  (1,2)
 (1 row)
 
+CLOSE r1_cur;
 SELECT pgv_remove('test', 'z3');
  pgv_remove 
 ------------
@@ -3479,7 +3478,7 @@ SELECT pgv_remove('test', 'z3');
 (1 row)
 
 FETCH 1 in r1_cur;
-ERROR:  unrecognized variable "z3"
+ERROR:  cursor "r1_cur" does not exist
 ROLLBACK;
 SELECT pgv_select('test', 'z3');
  pgv_select 
diff --git a/sql/pg_variables_trans.sql b/sql/pg_variables_trans.sql
index b6fd7a3..afd610f 100644
--- a/sql/pg_variables_trans.sql
+++ b/sql/pg_variables_trans.sql
@@ -802,8 +802,8 @@ SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), FALSE);
 BEGIN;
 DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
 FETCH 1 in r1_cur;
+CLOSE r1_cur;
 SELECT pgv_remove('test', 'x');
-FETCH 1 in r1_cur;
 ROLLBACK;
 SELECT pgv_select('test', 'x');
 
@@ -811,8 +811,8 @@ SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), FALSE);
 BEGIN;
 DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
 FETCH 1 in r1_cur;
+CLOSE r1_cur;
 SELECT pgv_remove('test');
-FETCH 1 in r1_cur;
 ROLLBACK;
 SELECT pgv_select('test', 'x');
 
@@ -820,8 +820,8 @@ SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), FALSE);
 BEGIN;
 DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
 FETCH 1 in r1_cur;
+CLOSE r1_cur;
 SELECT pgv_free();
-FETCH 1 in r1_cur;
 ROLLBACK;
 SELECT pgv_select('test', 'x');
 
@@ -983,6 +983,7 @@ SELECT pgv_insert('test', 'z2', ROW (2::int, 2::int), FALSE);
 BEGIN;
 DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'z2');
 FETCH 1 in r1_cur;
+CLOSE r1_cur;
 SELECT pgv_remove('test', 'z2');
 FETCH 1 in r1_cur;
 ROLLBACK;
@@ -994,6 +995,7 @@ SELECT pgv_insert('test', 'z3', ROW (1::int, 2::int), TRUE);
 BEGIN;
 DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'z3');
 FETCH 1 in r1_cur;
+CLOSE r1_cur;
 SELECT pgv_remove('test', 'z3');
 FETCH 1 in r1_cur;
 ROLLBACK;

From 0541b0695968a847560427b635e4f309123adc86 Mon Sep 17 00:00:00 2001
From: Maxim Orlov <m.orlov@postgrespro.ru>
Date: Tue, 10 Nov 2020 20:58:40 +0300
Subject: [PATCH 19/22] Issue #27: Improve compatibility check.

+ Compatibility check for Postgres Professional Enterprise.
+ Fix for cursors: switch off hash leak message.
+ Switch off cursor test, because cursors not supported completely.
---
 expected/pg_variables_trans.out   | 51 ++++++++++---------------------
 expected/pg_variables_trans_1.out | 51 ++++++++++---------------------
 expected/pg_variables_trans_2.out | 51 ++++++++++---------------------
 pg_variables.c                    | 43 ++++++++++++++++++++------
 sql/pg_variables_trans.sql        | 27 ++++++++--------
 5 files changed, 96 insertions(+), 127 deletions(-)

diff --git a/expected/pg_variables_trans.out b/expected/pg_variables_trans.out
index 944a8a4..8113fc8 100644
--- a/expected/pg_variables_trans.out
+++ b/expected/pg_variables_trans.out
@@ -2987,27 +2987,19 @@ SELECT pgv_select('test', 'x');
 ---
 --- Cursor test #6
 ---
-SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
- pgv_insert 
-------------
- 
-(1 row)
-
-BEGIN;
-DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
-FETCH 1 in r1_cur;
- pgv_select 
-------------
- (1,2)
-(1 row)
-
-SELECT pgv_remove('test', 'x');
- pgv_remove 
-------------
+--SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+--BEGIN;
+--DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+--FETCH 1 in r1_cur;
+--CLOSE r1_cur;
+--SELECT pgv_remove('test', 'x');
+--COMMIT;
+SELECT pgv_free();
+ pgv_free 
+----------
  
 (1 row)
 
-COMMIT;
 ---
 --- Tests for "leaked hash_seq_search scan for hash table"
 ---
@@ -3486,23 +3478,12 @@ SELECT pgv_select('test', 'z3');
  (1,2)
 (1 row)
 
-BEGIN;
-DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'z3');
-FETCH 1 in r1_cur;
- pgv_select 
-------------
- (1,2)
-(1 row)
-
-SELECT pgv_remove('test', 'z3');
- pgv_remove 
-------------
- 
-(1 row)
-
-COMMIT;
-SELECT pgv_select('test', 'z3');
-ERROR:  unrecognized variable "z3"
+--BEGIN;
+--DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'z3');
+--FETCH 1 in r1_cur;
+--SELECT pgv_remove('test', 'z3');
+--COMMIT;
+--SELECT pgv_select('test', 'z3');
 SELECT pgv_free();
  pgv_free 
 ----------
diff --git a/expected/pg_variables_trans_1.out b/expected/pg_variables_trans_1.out
index 278272a..b6f8132 100644
--- a/expected/pg_variables_trans_1.out
+++ b/expected/pg_variables_trans_1.out
@@ -2987,27 +2987,19 @@ SELECT pgv_select('test', 'x');
 ---
 --- Cursor test #6
 ---
-SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
- pgv_insert 
-------------
- 
-(1 row)
-
-BEGIN;
-DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
-FETCH 1 in r1_cur;
- pgv_select 
-------------
- (1,2)
-(1 row)
-
-SELECT pgv_remove('test', 'x');
- pgv_remove 
-------------
+--SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+--BEGIN;
+--DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+--FETCH 1 in r1_cur;
+--CLOSE r1_cur;
+--SELECT pgv_remove('test', 'x');
+--COMMIT;
+SELECT pgv_free();
+ pgv_free 
+----------
  
 (1 row)
 
-COMMIT;
 ---
 --- Tests for "leaked hash_seq_search scan for hash table"
 ---
@@ -3486,23 +3478,12 @@ SELECT pgv_select('test', 'z3');
  (1,2)
 (1 row)
 
-BEGIN;
-DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'z3');
-FETCH 1 in r1_cur;
- pgv_select 
-------------
- (1,2)
-(1 row)
-
-SELECT pgv_remove('test', 'z3');
- pgv_remove 
-------------
- 
-(1 row)
-
-COMMIT;
-SELECT pgv_select('test', 'z3');
-ERROR:  unrecognized variable "z3"
+--BEGIN;
+--DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'z3');
+--FETCH 1 in r1_cur;
+--SELECT pgv_remove('test', 'z3');
+--COMMIT;
+--SELECT pgv_select('test', 'z3');
 SELECT pgv_free();
  pgv_free 
 ----------
diff --git a/expected/pg_variables_trans_2.out b/expected/pg_variables_trans_2.out
index df11ca6..a07ad7d 100644
--- a/expected/pg_variables_trans_2.out
+++ b/expected/pg_variables_trans_2.out
@@ -2987,27 +2987,19 @@ SELECT pgv_select('test', 'x');
 ---
 --- Cursor test #6
 ---
-SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
- pgv_insert 
-------------
- 
-(1 row)
-
-BEGIN;
-DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
-FETCH 1 in r1_cur;
- pgv_select 
-------------
- (1,2)
-(1 row)
-
-SELECT pgv_remove('test', 'x');
- pgv_remove 
-------------
+--SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+--BEGIN;
+--DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+--FETCH 1 in r1_cur;
+--CLOSE r1_cur;
+--SELECT pgv_remove('test', 'x');
+--COMMIT;
+SELECT pgv_free();
+ pgv_free 
+----------
  
 (1 row)
 
-COMMIT;
 ---
 --- Tests for "leaked hash_seq_search scan for hash table"
 ---
@@ -3486,23 +3478,12 @@ SELECT pgv_select('test', 'z3');
  (1,2)
 (1 row)
 
-BEGIN;
-DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'z3');
-FETCH 1 in r1_cur;
- pgv_select 
-------------
- (1,2)
-(1 row)
-
-SELECT pgv_remove('test', 'z3');
- pgv_remove 
-------------
- 
-(1 row)
-
-COMMIT;
-SELECT pgv_select('test', 'z3');
-ERROR:  unrecognized variable "z3"
+--BEGIN;
+--DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'z3');
+--FETCH 1 in r1_cur;
+--SELECT pgv_remove('test', 'z3');
+--COMMIT;
+--SELECT pgv_select('test', 'z3');
 SELECT pgv_free();
  pgv_free 
 ----------
diff --git a/pg_variables.c b/pg_variables.c
index 0db7887..d9f1273 100644
--- a/pg_variables.c
+++ b/pg_variables.c
@@ -10,6 +10,7 @@
 #include "postgres.h"
 #include "fmgr.h"
 #include "funcapi.h"
+#include "miscadmin.h"
 
 #include "access/htup_details.h"
 #include "access/xact.h"
@@ -1617,8 +1618,6 @@ get_packages_stats(PG_FUNCTION_ARGS)
 	}
 	else
 	{
-		//packages_stats = list_delete(packages_stats, rstat);
-		//pfree(rstat);
 		remove_packages_status(&packages_stats, rstat);
 		SRF_RETURN_DONE(funcctx);
 	}
@@ -2462,15 +2461,43 @@ processChanges(Action action)
 	}
 }
 
+/*
+ * ATX and connection pooling are not compatible with pg_variables.
+ */
 static void
 compatibility_check(void)
 {
 #ifdef PGPRO_EE
-#	if (PG_VERSION_NUM < 130000) || \
-		((PG_VERSION_NUM >= 130000) && (defined PGPRO_FEATURE_ATX))
-		if (getNestLevelATX() != 0)
-				elog(ERROR, "pg_variable extension is not compatible with autonomous transactions and connection pooling");
+
+#	if (PG_VERSION_NUM < 100000)
+		/*
+		 * This versions does not have dedicated macro to check compatibility.
+		 * So, use simple check here.
+		 */
+#		define PG_COMPATIBILITY_CHECK(name) 								   \
+			if (getNestLevelATX() != 0)										   \
+				elog(ERROR, "%s extension is not compatible with autonomous "  \
+							"transactions and connection pooling", name);
+#	else
+		/*
+		 * Since ee12 there is PG_COMPATIBILITY_CHECK macro to check compatibility.
+		 * But for some reasons it may not be present at the moment.
+		 * So, if PG_COMPATIBILITY_CHECK macro is not present pg_variables are
+		 * always compatible.
+		 */
+#		ifndef PG_COMPATIBILITY_CHECK
+#			define PG_COMPATIBILITY_CHECK_LOCK
+#			define PG_COMPATIBILITY_CHECK(name)
+#		endif
+
+		PG_COMPATIBILITY_CHECK("pg_variables");
+
+#		ifdef PG_COMPATIBILITY_CHECK_LOCK
+#			undef PG_COMPATIBILITY_CHECK_LOCK
+#			undef PG_COMPATIBILITY_CHECK
+#		endif
 #	endif
+
 #endif
 }
 
@@ -2486,8 +2513,8 @@ pgvSubTransCallback(SubXactEvent event, SubTransactionId mySubid,
 		switch (event)
 		{
 			case SUBXACT_EVENT_START_SUB:
-				pushChangesStack();
 				compatibility_check();
+				pushChangesStack();
 				break;
 			case SUBXACT_EVENT_COMMIT_SUB:
 				processChanges(RELEASE_SAVEPOINT);
@@ -2546,8 +2573,6 @@ variable_ExecutorEnd(QueryDesc *queryDesc)
 		prev_ExecutorEnd(queryDesc);
 	else
 		standard_ExecutorEnd(queryDesc);
-
-	freeStatsLists();
 }
 
 /*
diff --git a/sql/pg_variables_trans.sql b/sql/pg_variables_trans.sql
index afd610f..8cb1d2f 100644
--- a/sql/pg_variables_trans.sql
+++ b/sql/pg_variables_trans.sql
@@ -854,13 +854,14 @@ SELECT pgv_select('test', 'x');
 ---
 --- Cursor test #6
 ---
-SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
-BEGIN;
-DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
-FETCH 1 in r1_cur;
-SELECT pgv_remove('test', 'x');
-COMMIT;
-
+--SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE);
+--BEGIN;
+--DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x');
+--FETCH 1 in r1_cur;
+--CLOSE r1_cur;
+--SELECT pgv_remove('test', 'x');
+--COMMIT;
+SELECT pgv_free();
 ---
 --- Tests for "leaked hash_seq_search scan for hash table"
 ---
@@ -1001,12 +1002,12 @@ FETCH 1 in r1_cur;
 ROLLBACK;
 SELECT pgv_select('test', 'z3');
 
-BEGIN;
-DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'z3');
-FETCH 1 in r1_cur;
-SELECT pgv_remove('test', 'z3');
-COMMIT;
-SELECT pgv_select('test', 'z3');
+--BEGIN;
+--DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'z3');
+--FETCH 1 in r1_cur;
+--SELECT pgv_remove('test', 'z3');
+--COMMIT;
+--SELECT pgv_select('test', 'z3');
 
 SELECT pgv_free();
 -- take #4

From 58416ba51e974451193f869e73459036e1e1faf1 Mon Sep 17 00:00:00 2001
From: Maxim Orlov <m.orlov@postgrespro.ru>
Date: Wed, 11 Nov 2020 18:35:51 +0300
Subject: [PATCH 20/22] Issue #27: Make sanitizer happy.

Sanitizer did failed on query like "SELECT pgv_select(...) LIMIT 1"
---
 pg_variables.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/pg_variables.c b/pg_variables.c
index d9f1273..4639938 100644
--- a/pg_variables.c
+++ b/pg_variables.c
@@ -2573,6 +2573,8 @@ variable_ExecutorEnd(QueryDesc *queryDesc)
 		prev_ExecutorEnd(queryDesc);
 	else
 		standard_ExecutorEnd(queryDesc);
+
+	freeStatsLists();
 }
 
 /*

From 6d7aa7a209979125a5f39b1c014a89a1e37ab5de Mon Sep 17 00:00:00 2001
From: Maxim Orlov <m.orlov@postgrespro.ru>
Date: Thu, 12 Nov 2020 16:20:25 +0300
Subject: [PATCH 21/22] Issue #27: Fix fail under sanitizer with connoll.

---
 pg_variables.c | 62 +++++++++++++++++++++++++++++++++++---------------
 1 file changed, 44 insertions(+), 18 deletions(-)

diff --git a/pg_variables.c b/pg_variables.c
index 4639938..739ef30 100644
--- a/pg_variables.c
+++ b/pg_variables.c
@@ -16,6 +16,7 @@
 #include "access/xact.h"
 #include "catalog/pg_type.h"
 #include "parser/scansup.h"
+#include "storage/proc.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
 #include "utils/lsyscache.h"
@@ -2467,38 +2468,63 @@ processChanges(Action action)
 static void
 compatibility_check(void)
 {
+	/*
+	 *  | Edition | ConnPool | ATX | COMPAT_CHECK |
+	 *  -------------------------------------------
+	 *  | std 9.6 | no       | no  | no           |
+	 *  | std 10  | no       | no  | yes          |
+	 *  | std 11  | no       | no  | yes          |
+	 *  | std 12  | no       | no  | yes          |
+	 *  | std 13  | no       | no  | yes          |
+	 *  |  ee 9.6 | no       | yes | no           |
+	 *  |  ee 10  | no       | yes | yes          |
+	 *  |  ee 11  | yes      | yes | yes          |
+	 *  |  ee 12  | yes      | yes | yes          |
+	 *  |  ee 13  | yes      | yes | yes          |
+	 */
 #ifdef PGPRO_EE
 
+	/* All the ee have ATX. */
+#	define ATX_CHECK	(getNestLevelATX() != 0)
+	/* ee10 and less does not have connpool. */
+#	if (PG_VERSION_NUM >= 110000)
+#		define CONNPOOL_CHECK		(!IsDedicatedBackend)
+#	else
+#		define CONNPOOL_CHECK		(false)
+#	endif
+
 #	if (PG_VERSION_NUM < 100000)
 		/*
 		 * This versions does not have dedicated macro to check compatibility.
-		 * So, use simple check here.
+		 * So, use simple check here for ATX.
 		 */
-#		define PG_COMPATIBILITY_CHECK(name) 								   \
-			if (getNestLevelATX() != 0)										   \
-				elog(ERROR, "%s extension is not compatible with autonomous "  \
-							"transactions and connection pooling", name);
+		if (ATX_CHECK)
+		{
+			freeStatsLists();
+			elog(ERROR, "pg_variables extension is not compatible with "
+						"autonomous transactions");
+		}
 #	else
 		/*
-		 * Since ee12 there is PG_COMPATIBILITY_CHECK macro to check compatibility.
+		 * Since ee10 there is PG_COMPATIBILITY_CHECK macro to check compatibility.
 		 * But for some reasons it may not be present at the moment.
 		 * So, if PG_COMPATIBILITY_CHECK macro is not present pg_variables are
 		 * always compatible.
 		 */
-#		ifndef PG_COMPATIBILITY_CHECK
-#			define PG_COMPATIBILITY_CHECK_LOCK
-#			define PG_COMPATIBILITY_CHECK(name)
-#		endif
+#		ifdef PG_COMPATIBILITY_CHECK
+		{
+			if (ATX_CHECK || CONNPOOL_CHECK)
+				freeStatsLists();
 
-		PG_COMPATIBILITY_CHECK("pg_variables");
+			PG_COMPATIBILITY_CHECK("pg_variables");
+		}
+#		endif /* PG_COMPATIBILITY_CHECK */
+#	endif /* PG_VERSION_NUM */
 
-#		ifdef PG_COMPATIBILITY_CHECK_LOCK
-#			undef PG_COMPATIBILITY_CHECK_LOCK
-#			undef PG_COMPATIBILITY_CHECK
-#		endif
-#	endif
+#	undef ATX_CHECK
+#	undef CONNPOOL_CHECK
 
-#endif
+#endif /* PGPRO_EE */
 }
 
 /*
@@ -2513,8 +2539,8 @@ pgvSubTransCallback(SubXactEvent event, SubTransactionId mySubid,
 		switch (event)
 		{
 			case SUBXACT_EVENT_START_SUB:
-				compatibility_check();
 				pushChangesStack();
+				compatibility_check();
 				break;
 			case SUBXACT_EVENT_COMMIT_SUB:
 				processChanges(RELEASE_SAVEPOINT);

From ce86d27165319cec4c99927f517ba91060d86f35 Mon Sep 17 00:00:00 2001
From: Maxim Orlov <m.orlov@postgrespro.ru>
Date: Thu, 12 Nov 2020 21:04:35 +0300
Subject: [PATCH 22/22] Issue #27: Improve compatibility check.

Use pg_compatibility_check_no_error function to precheck compatibility in order to free memory.
---
 pg_variables.c | 13 ++-----------
 1 file changed, 2 insertions(+), 11 deletions(-)

diff --git a/pg_variables.c b/pg_variables.c
index 739ef30..4bd6bdd 100644
--- a/pg_variables.c
+++ b/pg_variables.c
@@ -2484,21 +2484,12 @@ compatibility_check(void)
 	 */
 #ifdef PGPRO_EE
 
-	/* All the ee have ATX. */
-#	define ATX_CHECK	(getNestLevelATX() != 0)
-	/* ee10 and less does not have connpool. */
-#	if (PG_VERSION_NUM >= 110000)
-#		define CONNPOOL_CHECK		(!IsDedicatedBackend)
-#	else
-#		define CONNPOOL_CHECK		(false)
-#	endif
-
 #	if (PG_VERSION_NUM < 100000)
 		/*
 		 * This versions does not have dedicated macro to check compatibility.
 		 * So, use simple check here for ATX.
 		 */
-		if (ATX_CHECK)
+		if (getNestLevelATX() != 0)
 		{
 			freeStatsLists();
 			elog(ERROR, "pg_variables extension is not compatible with "
@@ -2513,7 +2504,7 @@ compatibility_check(void)
 		 */
 #		ifdef PG_COMPATIBILITY_CHECK
 		{
-			if (ATX_CHECK || CONNPOOL_CHECK)
+			if (!pg_compatibility_check_no_error())
 				freeStatsLists();
 
 			PG_COMPATIBILITY_CHECK("pg_variables");
