diff --git a/.travis.yml b/.travis.yml index 7edda6c..80d5de7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,6 @@ -sudo: required +os: linux + +dist: focal language: c @@ -18,13 +20,31 @@ notifications: on_failure: always env: + - PG_VERSION=16 LEVEL=nightmare + - PG_VERSION=16 LEVEL=hardcore + - PG_VERSION=16 + - PG_VERSION=15 LEVEL=nightmare + - PG_VERSION=15 LEVEL=hardcore + - PG_VERSION=15 + - PG_VERSION=14 LEVEL=nightmare + - PG_VERSION=14 LEVEL=hardcore + - PG_VERSION=14 + - PG_VERSION=13 LEVEL=nightmare + - PG_VERSION=13 LEVEL=hardcore + - PG_VERSION=13 + - PG_VERSION=12 LEVEL=nightmare + - PG_VERSION=12 LEVEL=hardcore + - PG_VERSION=12 - PG_VERSION=11 LEVEL=nightmare - PG_VERSION=11 LEVEL=hardcore - PG_VERSION=11 - - PG_VERSION=10 - - PG_VERSION=9.6 - - PG_VERSION=9.5 +# XXX: consider fixing nightmare mode matrix: allow_failures: - env: PG_VERSION=11 LEVEL=nightmare + - env: PG_VERSION=12 LEVEL=nightmare + - env: PG_VERSION=13 LEVEL=nightmare + - env: PG_VERSION=14 LEVEL=nightmare + - env: PG_VERSION=15 LEVEL=nightmare + - env: PG_VERSION=16 LEVEL=nightmare diff --git a/Dockerfile.tmpl b/Dockerfile.tmpl index 1760377..2792b6e 100644 --- a/Dockerfile.tmpl +++ b/Dockerfile.tmpl @@ -5,8 +5,8 @@ RUN apk add --no-cache \ openssl curl \ perl perl-ipc-run \ make musl-dev gcc bison flex coreutils \ - zlib-dev libedit-dev \ - clang clang-analyzer; + zlib-dev libedit-dev linux-headers \ + pkgconf icu-dev clang clang15 clang-analyzer; # Install fresh valgrind RUN apk add valgrind \ diff --git a/LICENSE b/LICENSE index f340ae8..9c8fe9d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ pg_variables is released under the PostgreSQL License, a liberal Open Source license, similar to the BSD or MIT licenses. -Copyright (c) 2016-2018, Postgres Professional +Copyright (c) 2016-2022, Postgres Professional Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group Portions Copyright (c) 1994, The Regents of the University of California diff --git a/Makefile b/Makefile index 7e262ff..7253e93 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,8 @@ DATA_built = $(EXTENSION)--$(EXTVERSION).sql PGFILEDESC = "pg_variables - sessional variables" -REGRESS = pg_variables pg_variables_any pg_variables_trans +REGRESS = pg_variables pg_variables_any pg_variables_trans pg_variables_atx \ + pg_variables_atx_pkg ifdef USE_PGXS PG_CONFIG = pg_config diff --git a/README.md b/README.md index 01be495..294ed37 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # pg_variables - session variables with various types -[![Build Status](https://travis-ci.org/postgrespro/pg_variables.svg?branch=master)](https://travis-ci.org/postgrespro/pg_variables) +[![Build Status](https://travis-ci.com/postgrespro/pg_variables.svg?branch=master)](https://travis-ci.com/postgrespro/pg_variables) [![codecov](https://codecov.io/gh/postgrespro/pg_variables/branch/master/graph/badge.svg)](https://codecov.io/gh/postgrespro/pg_variables) [![GitHub license](https://img.shields.io/badge/license-PostgreSQL-blue.svg)](https://raw.githubusercontent.com/postgrespro/pg_variables/master/README.md) diff --git a/expected/pg_variables.out b/expected/pg_variables.out index ba0b64a..1e5bee6 100644 --- a/expected/pg_variables.out +++ b/expected/pg_variables.out @@ -563,11 +563,13 @@ ERROR: variable "j1" requires "jsonb" value SELECT pgv_insert('vars3', 'r1', tab) FROM tab; ERROR: there is a record in the variable "r1" with same key SELECT pgv_insert('vars3', 'r1', row(1, 'str1', 'str2')); -ERROR: new record structure differs from variable "r1" structure +ERROR: new record structure have 3 attributes, but variable "r1" structure have 2. SELECT pgv_insert('vars3', 'r1', row(1, 1)); -ERROR: new record structure differs from variable "r1" structure +ERROR: new record attribute type for attribute number 2 differs from variable "r1" structure. +HINT: You may need explicit type casts. SELECT pgv_insert('vars3', 'r1', row('str1', 'str1')); -ERROR: new record structure differs from variable "r1" structure +ERROR: new record attribute type for attribute number 1 differs from variable "r1" structure. +HINT: You may need explicit type casts. SELECT pgv_select('vars3', 'r1', ARRAY[[1,2]]); -- fail ERROR: searching for elements in multidimensional arrays is not supported -- Test variables caching @@ -771,8 +773,8 @@ CLOSE r1_cur; COMMIT; -- warning RESET client_min_messages; -- Clean memory after unsuccessful creation of a variable -SELECT pgv_insert('vars4', 'r1', row('str1', 'str1')); -- fail -ERROR: could not identify a hash function for type unknown +SELECT pgv_insert('vars4', 'r1', row (('str1'::text, 'str1'::text))); -- fail +ERROR: could not identify a hash function for type record SELECT package FROM pgv_stats() WHERE package = 'vars4'; package --------- @@ -929,3 +931,98 @@ SELECT * FROM pgv_list() order by package, name; ---------+------+------------------ (0 rows) +-- Check insert of record with various amount of fields +CREATE TEMP TABLE foo(id int, t text); +INSERT INTO foo VALUES (0, 'str00'); +SELECT pgv_insert('vars', 'r1', row(1, 'str1'::text, 'str2'::text)); + pgv_insert +------------ + +(1 row) + +SELECT pgv_select('vars', 'r1'); + pgv_select +--------------- + (1,str1,str2) +(1 row) + +SELECT pgv_insert('vars', 'r1', foo) FROM foo; +ERROR: new record structure have 2 attributes, but variable "r1" structure have 3. +SELECT pgv_select('vars', 'r1'); + pgv_select +--------------- + (1,str1,str2) +(1 row) + +SELECT pgv_insert('vars', 'r2', row(1, 'str1')); -- ok, UNKNOWNOID of 'str1' converts to TEXTOID + pgv_insert +------------ + +(1 row) + +SELECT pgv_insert('vars', 'r2', foo) FROM foo; -- ok + pgv_insert +------------ + +(1 row) + +SELECT pgv_select('vars', 'r2'); + pgv_select +------------ + (1,str1) + (0,str00) +(2 rows) + +SELECT pgv_insert('vars', 'r3', row(1, 'str1'::text)); + pgv_insert +------------ + +(1 row) + +SELECT pgv_insert('vars', 'r3', foo) FROM foo; -- ok, no conversions + pgv_insert +------------ + +(1 row) + +SELECT pgv_select('vars', 'r3'); + pgv_select +------------ + (1,str1) + (0,str00) +(2 rows) + +SELECT pgv_insert('vars', 'r4', row(1, 2::int)); + pgv_insert +------------ + +(1 row) + +SELECT pgv_insert('vars', 'r4', row(0, 'str1')); -- fail, UNKNOWNOID of 'str1' can't be converted to int +ERROR: new record attribute type for attribute number 2 differs from variable "r4" structure. +HINT: You may need explicit type casts. +SELECT pgv_select('vars', 'r4'); + pgv_select +------------ + (1,2) +(1 row) + +SELECT pgv_insert('vars', 'r5', foo) FROM foo; -- types: int, text + pgv_insert +------------ + +(1 row) + +SELECT pgv_insert('vars', 'r5', row(1, 'str1')); -- ok, UNKNOWNOID of 'str1' converts to TEXTOID + pgv_insert +------------ + +(1 row) + +SELECT pgv_select('vars', 'r5'); + pgv_select +------------ + (1,str1) + (0,str00) +(2 rows) + diff --git a/expected/pg_variables_atx.out b/expected/pg_variables_atx.out new file mode 100644 index 0000000..3322c37 --- /dev/null +++ b/expected/pg_variables_atx.out @@ -0,0 +1,571 @@ +select pgv_free(); + pgv_free +---------- + +(1 row) + +------------------------------ +-- Non-transactional variables +------------------------------ +select pgv_set('vars', 'int1', 101); + pgv_set +--------- + +(1 row) + +begin; + select pgv_set('vars', 'int2', 102); + pgv_set +--------- + +(1 row) + + begin autonomous; + select pgv_set('vars', 'int3', 103); + pgv_set +--------- + +(1 row) + +-- 101, 102, 103: + select pgv_get('vars', 'int1', null::int), pgv_get('vars', 'int2', null::int), pgv_get('vars', 'int3', null::int); + pgv_get | pgv_get | pgv_get +---------+---------+--------- + 101 | 102 | 103 +(1 row) + + select pgv_set('vars', 'int1', 1001); + pgv_set +--------- + +(1 row) + + begin autonomous; +-- 1001, 102, 103: + select pgv_get('vars', 'int1', null::int), pgv_get('vars', 'int2', null::int), pgv_get('vars', 'int3', null::int); + pgv_get | pgv_get | pgv_get +---------+---------+--------- + 1001 | 102 | 103 +(1 row) + + select pgv_set('vars', 'int2', 1002); + pgv_set +--------- + +(1 row) + + commit; + commit; +-- 1001, 1002, 103: + select pgv_get('vars', 'int1', null::int), pgv_get('vars', 'int2', null::int), pgv_get('vars', 'int3', null::int); + pgv_get | pgv_get | pgv_get +---------+---------+--------- + 1001 | 1002 | 103 +(1 row) + + select pgv_set('vars', 'int3', 1003); + pgv_set +--------- + +(1 row) + +rollback; +-- 1001, 1002, 1003: +select pgv_get('vars', 'int1', null::int), pgv_get('vars', 'int2', null::int), pgv_get('vars', 'int3', null::int); + pgv_get | pgv_get | pgv_get +---------+---------+--------- + 1001 | 1002 | 1003 +(1 row) + +-- vars:int1, vars:int2, vars:int3: +select * from pgv_list() order by package, name; + package | name | is_transactional +---------+------+------------------ + vars | int1 | f + vars | int2 | f + vars | int3 | f +(3 rows) + +select pgv_free(); + pgv_free +---------- + +(1 row) + +-------------------------- +-- Transactional variables +-------------------------- +select pgv_set('vars', 'int1', 101, true); + pgv_set +--------- + +(1 row) + +begin; + select pgv_set('vars', 'int2', 102, true); + pgv_set +--------- + +(1 row) + + begin autonomous; + select pgv_set('vars', 'int3', 103, true); + pgv_set +--------- + +(1 row) + +-- 103: + select pgv_get('vars', 'int3', null::int); + pgv_get +--------- + 103 +(1 row) + + begin autonomous; + select pgv_set('vars', 'int2', 1002, true); + pgv_set +--------- + +(1 row) + +-- 1002: + select pgv_get('vars', 'int2', null::int); + pgv_get +--------- + 1002 +(1 row) + + commit; +-- 103: + select pgv_get('vars', 'int3', null::int); + pgv_get +--------- + 103 +(1 row) + + commit; + select pgv_set('vars', 'int1', 1001, true); + pgv_set +--------- + +(1 row) + +-- 1001: + select pgv_get('vars', 'int1', null::int); + pgv_get +--------- + 1001 +(1 row) + +-- 102: + select pgv_get('vars', 'int2', null::int); + pgv_get +--------- + 102 +(1 row) + +rollback; +-- 101: +select pgv_get('vars', 'int1', null::int); + pgv_get +--------- + 101 +(1 row) + +-- vars:int1: +select * from pgv_list() order by package, name; + package | name | is_transactional +---------+------+------------------ + vars | int1 | t +(1 row) + +select pgv_free(); + pgv_free +---------- + +(1 row) + +---------- +-- Cursors +---------- +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_insert('test', 'y', row (10::int, 20::int), true); + pgv_insert +------------ + +(1 row) + +select pgv_insert('test', 'y', row (20::int, 30::int), true); + pgv_insert +------------ + +(1 row) + +select pgv_insert('test', 'y', row (30::int, 40::int), true); + pgv_insert +------------ + +(1 row) + +begin; + declare r1_cur cursor for select pgv_select('test', 'x'); + begin autonomous; + begin autonomous; + begin autonomous; + begin autonomous; + begin autonomous; + select pgv_insert('test', 'z', row (11::int, 22::int), false); + pgv_insert +------------ + +(1 row) + + select pgv_insert('test', 'z', row (22::int, 33::int), false); + pgv_insert +------------ + +(1 row) + + select pgv_insert('test', 'z', row (33::int, 44::int), false); + pgv_insert +------------ + +(1 row) + + declare r11_cur cursor for select pgv_select('test', 'x'); +-- (1,2),(2,3): + fetch 2 in r11_cur; + pgv_select +------------ + (1,2) + (2,3) +(2 rows) + + declare r2_cur cursor for select pgv_select('test', 'y'); +-- correct error: unrecognized variable "y" + fetch 2 in r2_cur; +ERROR: unrecognized variable "y" + rollback; + rollback; + rollback; + rollback; + rollback; + declare r2_cur cursor for select pgv_select('test', 'y'); + declare r3_cur cursor for select pgv_select('test', 'z'); +-- (1,2),(2,3): + fetch 2 in r1_cur; + pgv_select +------------ + (1,2) + (2,3) +(2 rows) + +-- (10,20),(20,30): + fetch 2 in r2_cur; + pgv_select +------------ + (10,20) + (20,30) +(2 rows) + +-- (11,22),(22,33): + fetch 2 in r3_cur; + pgv_select +------------ + (11,22) + (22,33) +(2 rows) + +rollback; +select pgv_free(); + pgv_free +---------- + +(1 row) + +------------------------------------------ +-- Savepoint: rollback in main transaction +------------------------------------------ +begin; + select pgv_set('vars', 'trans_int', 101, true); + pgv_set +--------- + +(1 row) + +-- 101: + select pgv_get('vars', 'trans_int', null::int); + pgv_get +--------- + 101 +(1 row) + + savepoint sp1; + select pgv_set('vars', 'trans_int', 102, true); + pgv_set +--------- + +(1 row) + +-- 102: + select pgv_get('vars', 'trans_int', null::int); + pgv_get +--------- + 102 +(1 row) + + begin autonomous; + select pgv_set('vars', 'trans_int', 103, true); + pgv_set +--------- + +(1 row) + +-- 103: + select pgv_get('vars', 'trans_int', null::int); + pgv_get +--------- + 103 +(1 row) + + commit; +-- 102: + select pgv_get('vars', 'trans_int', null::int); + pgv_get +--------- + 102 +(1 row) + + rollback to sp1; +commit; +-- 101: +select pgv_get('vars', 'trans_int', null::int); + pgv_get +--------- + 101 +(1 row) + +select pgv_free(); + pgv_free +---------- + +(1 row) + +------------------------------------------------ +-- Savepoint: rollback in autonomous transaction +------------------------------------------------ +begin; + select pgv_set('vars', 'trans_int', 1, true); + pgv_set +--------- + +(1 row) + + savepoint sp1; + select pgv_set('vars', 'trans_int', 100, true); + pgv_set +--------- + +(1 row) + + begin autonomous; + begin autonomous; + select pgv_set('vars1', 'int1', 2); + pgv_set +--------- + +(1 row) + + select pgv_set('vars1', 'trans_int1', 3, true); + pgv_set +--------- + +(1 row) + + savepoint sp2; + select pgv_set('vars1', 'trans_int1', 4, true); + pgv_set +--------- + +(1 row) + +-- 2 + select pgv_get('vars1', 'int1', null::int); + pgv_get +--------- + 2 +(1 row) + +-- 4 + select pgv_get('vars1', 'trans_int1', null::int); + pgv_get +--------- + 4 +(1 row) + + rollback to sp2; +-- 3 + select pgv_get('vars1', 'trans_int1', null::int); + pgv_get +--------- + 3 +(1 row) + +-- vars1:int1, vars1:trans_int1: + select * from pgv_list() order by package, name; + package | name | is_transactional +---------+------------+------------------ + vars1 | int1 | f + vars1 | trans_int1 | t +(2 rows) + + select pgv_set('vars1', 'trans_int2', 4, true); + pgv_set +--------- + +(1 row) + + select pgv_set('vars1', 'trans_int3', 5, true); + pgv_set +--------- + +(1 row) + + select pgv_set('vars1', 'int2', 3); + pgv_set +--------- + +(1 row) + + rollback; + commit; + rollback to sp1; +-- 1 + select pgv_get('vars', 'trans_int', null::int); + pgv_get +--------- + 1 +(1 row) + +-- 2 + select pgv_get('vars1', 'int1', null::int); + pgv_get +--------- + 2 +(1 row) + +-- 3 + select pgv_get('vars1', 'int2', null::int); + pgv_get +--------- + 3 +(1 row) + +-- vars:trans_int, vars1:int1, vars1:int2: + select * from pgv_list() order by package, name; + package | name | is_transactional +---------+-----------+------------------ + vars | trans_int | t + vars1 | int1 | f + vars1 | int2 | f +(3 rows) + +commit; +select pgv_free(); + pgv_free +---------- + +(1 row) + +------------------------------------------------------------ +-- Sample with (subxact inside ATX) == (subxact outside ATX) +------------------------------------------------------------ +select pgv_set('vars1', 'int1', 0); + pgv_set +--------- + +(1 row) + +select pgv_set('vars1', 'trans_int1', 0, true); + pgv_set +--------- + +(1 row) + +begin; + begin autonomous; + select pgv_set('vars1', 'int1', 1); + pgv_set +--------- + +(1 row) + + select pgv_set('vars1', 'trans_int1', 2, true); + pgv_set +--------- + +(1 row) + + savepoint sp2; + select pgv_set('vars1', 'trans_int1', 3, true); + pgv_set +--------- + +(1 row) + + rollback to sp2; +-- 2 + select pgv_get('vars1', 'trans_int1', null::int); + pgv_get +--------- + 2 +(1 row) + + commit; +rollback; +-- vars1:int1, vars1:trans_int1 +select * from pgv_list() order by package, name; + package | name | is_transactional +---------+------------+------------------ + vars1 | int1 | f + vars1 | trans_int1 | t +(2 rows) + +-- 1 +select pgv_get('vars1', 'int1', null::int); + pgv_get +--------- + 1 +(1 row) + +-- 0 +select pgv_get('vars1', 'trans_int1', null::int); + pgv_get +--------- + 0 +(1 row) + +select pgv_free(); + pgv_free +---------- + +(1 row) + diff --git a/expected/pg_variables_atx_0.out b/expected/pg_variables_atx_0.out new file mode 100644 index 0000000..3ffdc86 --- /dev/null +++ b/expected/pg_variables_atx_0.out @@ -0,0 +1,465 @@ +select pgv_free(); + pgv_free +---------- + +(1 row) + +------------------------------ +-- Non-transactional variables +------------------------------ +select pgv_set('vars', 'int1', 101); + pgv_set +--------- + +(1 row) + +begin; + select pgv_set('vars', 'int2', 102); + pgv_set +--------- + +(1 row) + + begin autonomous; +ERROR: syntax error at or near "autonomous" +LINE 1: begin autonomous; + ^ + select pgv_set('vars', 'int3', 103); +ERROR: current transaction is aborted, commands ignored until end of transaction block +-- 101, 102, 103: + select pgv_get('vars', 'int1', null::int), pgv_get('vars', 'int2', null::int), pgv_get('vars', 'int3', null::int); +ERROR: current transaction is aborted, commands ignored until end of transaction block + select pgv_set('vars', 'int1', 1001); +ERROR: current transaction is aborted, commands ignored until end of transaction block + begin autonomous; +ERROR: syntax error at or near "autonomous" +LINE 1: begin autonomous; + ^ +-- 1001, 102, 103: + select pgv_get('vars', 'int1', null::int), pgv_get('vars', 'int2', null::int), pgv_get('vars', 'int3', null::int); +ERROR: current transaction is aborted, commands ignored until end of transaction block + select pgv_set('vars', 'int2', 1002); +ERROR: current transaction is aborted, commands ignored until end of transaction block + commit; + commit; +WARNING: there is no transaction in progress +-- 1001, 1002, 103: + select pgv_get('vars', 'int1', null::int), pgv_get('vars', 'int2', null::int), pgv_get('vars', 'int3', null::int); +ERROR: unrecognized variable "int3" + select pgv_set('vars', 'int3', 1003); + pgv_set +--------- + +(1 row) + +rollback; +WARNING: there is no transaction in progress +-- 1001, 1002, 1003: +select pgv_get('vars', 'int1', null::int), pgv_get('vars', 'int2', null::int), pgv_get('vars', 'int3', null::int); + pgv_get | pgv_get | pgv_get +---------+---------+--------- + 101 | 102 | 1003 +(1 row) + +-- vars:int1, vars:int2, vars:int3: +select * from pgv_list() order by package, name; + package | name | is_transactional +---------+------+------------------ + vars | int1 | f + vars | int2 | f + vars | int3 | f +(3 rows) + +select pgv_free(); + pgv_free +---------- + +(1 row) + +-------------------------- +-- Transactional variables +-------------------------- +select pgv_set('vars', 'int1', 101, true); + pgv_set +--------- + +(1 row) + +begin; + select pgv_set('vars', 'int2', 102, true); + pgv_set +--------- + +(1 row) + + begin autonomous; +ERROR: syntax error at or near "autonomous" +LINE 1: begin autonomous; + ^ + select pgv_set('vars', 'int3', 103, true); +ERROR: current transaction is aborted, commands ignored until end of transaction block +-- 103: + select pgv_get('vars', 'int3', null::int); +ERROR: current transaction is aborted, commands ignored until end of transaction block + begin autonomous; +ERROR: syntax error at or near "autonomous" +LINE 1: begin autonomous; + ^ + select pgv_set('vars', 'int2', 1002, true); +ERROR: current transaction is aborted, commands ignored until end of transaction block +-- 1002: + select pgv_get('vars', 'int2', null::int); +ERROR: current transaction is aborted, commands ignored until end of transaction block + commit; +-- 103: + select pgv_get('vars', 'int3', null::int); +ERROR: unrecognized variable "int3" + commit; +WARNING: there is no transaction in progress + select pgv_set('vars', 'int1', 1001, true); + pgv_set +--------- + +(1 row) + +-- 1001: + select pgv_get('vars', 'int1', null::int); + pgv_get +--------- + 1001 +(1 row) + +-- 102: + select pgv_get('vars', 'int2', null::int); +ERROR: unrecognized variable "int2" +rollback; +WARNING: there is no transaction in progress +-- 101: +select pgv_get('vars', 'int1', null::int); + pgv_get +--------- + 1001 +(1 row) + +-- vars:int1: +select * from pgv_list() order by package, name; + package | name | is_transactional +---------+------+------------------ + vars | int1 | t +(1 row) + +select pgv_free(); + pgv_free +---------- + +(1 row) + +---------- +-- Cursors +---------- +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_insert('test', 'y', row (10::int, 20::int), true); + pgv_insert +------------ + +(1 row) + +select pgv_insert('test', 'y', row (20::int, 30::int), true); + pgv_insert +------------ + +(1 row) + +select pgv_insert('test', 'y', row (30::int, 40::int), true); + pgv_insert +------------ + +(1 row) + +begin; + declare r1_cur cursor for select pgv_select('test', 'x'); + begin autonomous; +ERROR: syntax error at or near "autonomous" +LINE 1: begin autonomous; + ^ + begin autonomous; +ERROR: syntax error at or near "autonomous" +LINE 1: begin autonomous; + ^ + begin autonomous; +ERROR: syntax error at or near "autonomous" +LINE 1: begin autonomous; + ^ + begin autonomous; +ERROR: syntax error at or near "autonomous" +LINE 1: begin autonomous; + ^ + begin autonomous; +ERROR: syntax error at or near "autonomous" +LINE 1: begin autonomous; + ^ + select pgv_insert('test', 'z', row (11::int, 22::int), false); +ERROR: current transaction is aborted, commands ignored until end of transaction block + select pgv_insert('test', 'z', row (22::int, 33::int), false); +ERROR: current transaction is aborted, commands ignored until end of transaction block + select pgv_insert('test', 'z', row (33::int, 44::int), false); +ERROR: current transaction is aborted, commands ignored until end of transaction block + declare r11_cur cursor for select pgv_select('test', 'x'); +ERROR: current transaction is aborted, commands ignored until end of transaction block +-- (1,2),(2,3): + fetch 2 in r11_cur; +ERROR: current transaction is aborted, commands ignored until end of transaction block + declare r2_cur cursor for select pgv_select('test', 'y'); +ERROR: current transaction is aborted, commands ignored until end of transaction block +-- correct error: unrecognized variable "y" + fetch 2 in r2_cur; +ERROR: current transaction is aborted, commands ignored until end of transaction block + rollback; + rollback; +WARNING: there is no transaction in progress + rollback; +WARNING: there is no transaction in progress + rollback; +WARNING: there is no transaction in progress + rollback; +WARNING: there is no transaction in progress + declare r2_cur cursor for select pgv_select('test', 'y'); +ERROR: DECLARE CURSOR can only be used in transaction blocks + declare r3_cur cursor for select pgv_select('test', 'z'); +ERROR: DECLARE CURSOR can only be used in transaction blocks +-- (1,2),(2,3): + fetch 2 in r1_cur; +ERROR: cursor "r1_cur" does not exist +-- (10,20),(20,30): + fetch 2 in r2_cur; +ERROR: cursor "r2_cur" does not exist +-- (11,22),(22,33): + fetch 2 in r3_cur; +ERROR: cursor "r3_cur" does not exist +rollback; +WARNING: there is no transaction in progress +select pgv_free(); + pgv_free +---------- + +(1 row) + +------------------------------------------ +-- Savepoint: rollback in main transaction +------------------------------------------ +begin; + select pgv_set('vars', 'trans_int', 101, true); + pgv_set +--------- + +(1 row) + +-- 101: + select pgv_get('vars', 'trans_int', null::int); + pgv_get +--------- + 101 +(1 row) + + savepoint sp1; + select pgv_set('vars', 'trans_int', 102, true); + pgv_set +--------- + +(1 row) + +-- 102: + select pgv_get('vars', 'trans_int', null::int); + pgv_get +--------- + 102 +(1 row) + + begin autonomous; +ERROR: syntax error at or near "autonomous" +LINE 1: begin autonomous; + ^ + select pgv_set('vars', 'trans_int', 103, true); +ERROR: current transaction is aborted, commands ignored until end of transaction block +-- 103: + select pgv_get('vars', 'trans_int', null::int); +ERROR: current transaction is aborted, commands ignored until end of transaction block + commit; +-- 102: + select pgv_get('vars', 'trans_int', null::int); +ERROR: unrecognized package "vars" + rollback to sp1; +ERROR: ROLLBACK TO SAVEPOINT can only be used in transaction blocks +commit; +WARNING: there is no transaction in progress +-- 101: +select pgv_get('vars', 'trans_int', null::int); +ERROR: unrecognized package "vars" +select pgv_free(); + pgv_free +---------- + +(1 row) + +------------------------------------------------ +-- Savepoint: rollback in autonomous transaction +------------------------------------------------ +begin; + select pgv_set('vars', 'trans_int', 1, true); + pgv_set +--------- + +(1 row) + + savepoint sp1; + select pgv_set('vars', 'trans_int', 100, true); + pgv_set +--------- + +(1 row) + + begin autonomous; +ERROR: syntax error at or near "autonomous" +LINE 1: begin autonomous; + ^ + begin autonomous; +ERROR: syntax error at or near "autonomous" +LINE 1: begin autonomous; + ^ + select pgv_set('vars1', 'int1', 2); +ERROR: current transaction is aborted, commands ignored until end of transaction block + select pgv_set('vars1', 'trans_int1', 3, true); +ERROR: current transaction is aborted, commands ignored until end of transaction block + savepoint sp2; +ERROR: current transaction is aborted, commands ignored until end of transaction block + select pgv_set('vars1', 'trans_int1', 4, true); +ERROR: current transaction is aborted, commands ignored until end of transaction block +-- 2 + select pgv_get('vars1', 'int1', null::int); +ERROR: current transaction is aborted, commands ignored until end of transaction block +-- 4 + select pgv_get('vars1', 'trans_int1', null::int); +ERROR: current transaction is aborted, commands ignored until end of transaction block + rollback to sp2; +ERROR: savepoint "sp2" does not exist +-- 3 + select pgv_get('vars1', 'trans_int1', null::int); +ERROR: current transaction is aborted, commands ignored until end of transaction block +-- vars1:int1, vars1:trans_int1: + select * from pgv_list() order by package, name; +ERROR: current transaction is aborted, commands ignored until end of transaction block + select pgv_set('vars1', 'trans_int2', 4, true); +ERROR: current transaction is aborted, commands ignored until end of transaction block + select pgv_set('vars1', 'trans_int3', 5, true); +ERROR: current transaction is aborted, commands ignored until end of transaction block + select pgv_set('vars1', 'int2', 3); +ERROR: current transaction is aborted, commands ignored until end of transaction block + rollback; + commit; +WARNING: there is no transaction in progress + rollback to sp1; +ERROR: ROLLBACK TO SAVEPOINT can only be used in transaction blocks +-- 1 + select pgv_get('vars', 'trans_int', null::int); +ERROR: unrecognized package "vars" +-- 2 + select pgv_get('vars1', 'int1', null::int); +ERROR: unrecognized package "vars1" +-- 3 + select pgv_get('vars1', 'int2', null::int); +ERROR: unrecognized package "vars1" +-- vars:trans_int, vars1:int1, vars1:int2: + select * from pgv_list() order by package, name; + package | name | is_transactional +---------+------+------------------ +(0 rows) + +commit; +WARNING: there is no transaction in progress +select pgv_free(); + pgv_free +---------- + +(1 row) + +------------------------------------------------------------ +-- Sample with (subxact inside ATX) == (subxact outside ATX) +------------------------------------------------------------ +select pgv_set('vars1', 'int1', 0); + pgv_set +--------- + +(1 row) + +select pgv_set('vars1', 'trans_int1', 0, true); + pgv_set +--------- + +(1 row) + +begin; + begin autonomous; +ERROR: syntax error at or near "autonomous" +LINE 1: begin autonomous; + ^ + select pgv_set('vars1', 'int1', 1); +ERROR: current transaction is aborted, commands ignored until end of transaction block + select pgv_set('vars1', 'trans_int1', 2, true); +ERROR: current transaction is aborted, commands ignored until end of transaction block + savepoint sp2; +ERROR: current transaction is aborted, commands ignored until end of transaction block + select pgv_set('vars1', 'trans_int1', 3, true); +ERROR: current transaction is aborted, commands ignored until end of transaction block + rollback to sp2; +ERROR: savepoint "sp2" does not exist +-- 2 + select pgv_get('vars1', 'trans_int1', null::int); +ERROR: current transaction is aborted, commands ignored until end of transaction block + commit; +rollback; +WARNING: there is no transaction in progress +-- vars1:int1, vars1:trans_int1 +select * from pgv_list() order by package, name; + package | name | is_transactional +---------+------------+------------------ + vars1 | int1 | f + vars1 | trans_int1 | t +(2 rows) + +-- 1 +select pgv_get('vars1', 'int1', null::int); + pgv_get +--------- + 0 +(1 row) + +-- 0 +select pgv_get('vars1', 'trans_int1', null::int); + pgv_get +--------- + 0 +(1 row) + +select pgv_free(); + pgv_free +---------- + +(1 row) + diff --git a/expected/pg_variables_atx_pkg.out b/expected/pg_variables_atx_pkg.out new file mode 100644 index 0000000..e9c8412 --- /dev/null +++ b/expected/pg_variables_atx_pkg.out @@ -0,0 +1,473 @@ +-- +-- PGPRO-7614: function pgv_free() inside autonomous transaction +-- +SELECT pgv_free(); + pgv_free +---------- + +(1 row) + +-- +-- +-- Functions pgv_free() + pgv_get() inside autonomous transaction; package +-- with regular variable; autonomous transaction with commit. +-- +BEGIN; + SELECT pgv_set('vars', 'int1', 1); + pgv_set +--------- + +(1 row) + + BEGIN AUTONOMOUS; + SELECT pgv_get('vars', 'int1', null::int); + pgv_get +--------- + 1 +(1 row) + + SELECT pgv_free(); + pgv_free +---------- + +(1 row) + +-- ERROR: unrecognized package "vars" + SELECT pgv_get('vars', 'int1', null::int); +ERROR: unrecognized package "vars" + COMMIT; +-- ERROR: unrecognized package "vars" + SELECT pgv_get('vars', 'int1', null::int); +ERROR: unrecognized package "vars" +ROLLBACK; +-- +-- +-- Function pgv_free() inside autonomous transaction; package with +-- regular variable; autonomous transaction with commit. +-- +BEGIN; + SELECT pgv_set('vars', 'int1', 1); + pgv_set +--------- + +(1 row) + + BEGIN AUTONOMOUS; + SELECT pgv_free(); + pgv_free +---------- + +(1 row) + + COMMIT; +-- ERROR: unrecognized package "vars" + SELECT pgv_get('vars', 'int1', null::int); +ERROR: unrecognized package "vars" +ROLLBACK; +-- +-- +-- Function pgv_free() inside autonomous transaction; package with +-- regular variable; autonomous transaction with rollback. +-- +BEGIN; + SELECT pgv_set('vars', 'int1', 1); + pgv_set +--------- + +(1 row) + + BEGIN AUTONOMOUS; + SELECT pgv_free(); + pgv_free +---------- + +(1 row) + + ROLLBACK; +-- ERROR: unrecognized package "vars" + SELECT pgv_get('vars', 'int1', null::int); +ERROR: unrecognized package "vars" +ROLLBACK; +-- +-- +-- Function pgv_free() inside autonomous transaction; package with +-- transactional variable; autonomous transaction with rollback. +-- +BEGIN; + SELECT pgv_set('vars', 'int1', 1, true); + pgv_set +--------- + +(1 row) + + BEGIN AUTONOMOUS; + SELECT pgv_free(); + pgv_free +---------- + +(1 row) + + ROLLBACK; + SELECT pgv_get('vars', 'int1', null::int); + pgv_get +--------- + 1 +(1 row) + +ROLLBACK; +-- +-- +-- Function pgv_free() inside autonomous transaction; package with +-- transactional variable; autonomous transaction with commit. +-- +BEGIN; + SELECT pgv_set('vars', 'int1', 1, true); + pgv_set +--------- + +(1 row) + + BEGIN AUTONOMOUS; + SELECT pgv_free(); + pgv_free +---------- + +(1 row) + + COMMIT; +-- ERROR: unrecognized package "vars" + SELECT pgv_get('vars', 'int1', null::int); +ERROR: unrecognized package "vars" +ROLLBACK; +-- +-- +-- Function pgv_free() inside recursive autonomous transactions. +-- +BEGIN; + BEGIN AUTONOMOUS; + SELECT pgv_set('vars', 'int1', 1); + pgv_set +--------- + +(1 row) + + BEGIN AUTONOMOUS; + BEGIN AUTONOMOUS; + SELECT pgv_free(); + pgv_free +---------- + +(1 row) + + COMMIT; +-- ERROR: unrecognized package "vars" + SELECT pgv_get('vars', 'int1', null::int); +ERROR: unrecognized package "vars" + COMMIT; +-- ERROR: unrecognized package "vars" + SELECT pgv_get('vars', 'int1', null::int); +ERROR: unrecognized package "vars" + COMMIT; +ROLLBACK; +-- +-- +-- Function pgv_free() inside recursive autonomous transactions; +-- recreating the package after deletion with using regular +-- variable. +-- +BEGIN; + SELECT pgv_set('vars', 'int1', 1); + pgv_set +--------- + +(1 row) + + BEGIN AUTONOMOUS; + BEGIN AUTONOMOUS; + SELECT pgv_get('vars', 'int1', null::int); + pgv_get +--------- + 1 +(1 row) + + BEGIN AUTONOMOUS; + SELECT pgv_free(); + pgv_free +---------- + +(1 row) + + COMMIT; +-- ERROR: unrecognized package "vars" + SELECT pgv_get('vars', 'int1', null::int); +ERROR: unrecognized package "vars" + COMMIT; + SELECT pgv_set('vars', 'int1', 2); + pgv_set +--------- + +(1 row) + + COMMIT; + SELECT pgv_get('vars', 'int1', null::int); + pgv_get +--------- + 2 +(1 row) + +ROLLBACK; +-- +-- +-- Function pgv_free() inside recursive autonomous transactions; +-- recreating the package after deletion with using transactional +-- variable. +-- +BEGIN; + SELECT pgv_set('vars', 'int1', 1); + pgv_set +--------- + +(1 row) + + BEGIN AUTONOMOUS; + BEGIN AUTONOMOUS; + SELECT pgv_get('vars', 'int1', null::int); + pgv_get +--------- + 1 +(1 row) + + BEGIN AUTONOMOUS; + SELECT pgv_free(); + pgv_free +---------- + +(1 row) + + COMMIT; +-- ERROR: unrecognized package "vars" + SELECT pgv_get('vars', 'int1', null::int); +ERROR: unrecognized package "vars" + COMMIT; + SELECT pgv_set('vars', 'int1', 2, true); + pgv_set +--------- + +(1 row) + + SELECT pgv_list(); + pgv_list +--------------- + (vars,int1,t) +(1 row) + + COMMIT; +-- ERROR: unrecognized package "vars" + SELECT pgv_get('vars', 'int1', null::int); +ERROR: unrecognized package "vars" +ROLLBACK; +-- +-- +-- Test for case: do not free hash_seq_search scans of parent transaction +-- at end of the autonomous transaction. +-- +BEGIN; + SELECT pgv_insert('test', 'x', row (1::int, 2::int), false); + pgv_insert +------------ + +(1 row) + + SELECT pgv_insert('test', 'x', row (3::int, 4::int), false); + pgv_insert +------------ + +(1 row) + + DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x'); +-- (1,2) + FETCH 1 IN r1_cur; + pgv_select +------------ + (1,2) +(1 row) + + BEGIN AUTONOMOUS; + ROLLBACK; +-- (3,4) + FETCH 1 IN r1_cur; + pgv_select +------------ + (3,4) +(1 row) + + SELECT pgv_remove('test', 'x'); + pgv_remove +------------ + +(1 row) + +-- ERROR: unrecognized package "test" + FETCH 1 IN r1_cur; +ERROR: unrecognized package "test" +ROLLBACK; +-- +-- +-- Test for case: pgv_free() should free hash_seq_search scans of all +-- (current ATX + parent) transactions. +-- +BEGIN; + SELECT pgv_insert('test', 'x', row (1::int, 2::int), false); + pgv_insert +------------ + +(1 row) + + DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x'); +-- (1,2) + FETCH 1 IN r1_cur; + pgv_select +------------ + (1,2) +(1 row) + + BEGIN AUTONOMOUS; + SELECT pgv_free(); + pgv_free +---------- + +(1 row) + + ROLLBACK; +-- ERROR: unrecognized package "test" + FETCH 1 IN r1_cur; +ERROR: unrecognized package "test" +ROLLBACK; +-- +-- +-- Test for case: pgv_set() created a regular variable; rollback +-- removes package state and creates a new state to make package valid. +-- Commit of next autonomous transaction should not replace this new +-- state (this is not allowed for autonomous transaction). +-- +BEGIN; + BEGIN AUTONOMOUS; + SELECT pgv_set('vars', 'int1', 1); + pgv_set +--------- + +(1 row) + + ROLLBACK; + BEGIN AUTONOMOUS; + SELECT pgv_set('vars', 'int1', 2); + pgv_set +--------- + +(1 row) + + COMMIT; +ROLLBACK; +SELECT pgv_remove('vars', 'int1'); + pgv_remove +------------ + +(1 row) + +-- +-- +-- Test for case: pgv_set() created a regular variable and package with +-- (atxlevel=1, level=1). COMMIT changes this level to (atxlevel=1, level=0). +-- In the next autonomous transaction (atxlevel=1, level=1) we erroneously +-- detect that the package changed in upper transaction and remove the +-- package state (this is not allowed for autonomous transaction). +-- +BEGIN; + BEGIN AUTONOMOUS; + SELECT pgv_set('vars', 'int1', 2); + pgv_set +--------- + +(1 row) + + COMMIT; + BEGIN AUTONOMOUS; + SELECT pgv_free(); + pgv_free +---------- + +(1 row) + + SELECT pgv_set('vars', 'int1', 2, true); + pgv_set +--------- + +(1 row) + + COMMIT; +ROLLBACK; +-- +-- +-- Test for case: pgv_set() created a regular variable and package with +-- (atxlevel=1, level=1). ROLLBACK changes this level to (atxlevel=0, level=0). +-- But ROLLBACK shouldn't change atxlevel in case rollback of sub-transaction. +-- +BEGIN; + BEGIN AUTONOMOUS; + SAVEPOINT sp1; + SELECT pgv_set('vars1', 'int1', 0); + pgv_set +--------- + +(1 row) + + ROLLBACK TO sp1; + COMMIT; +ROLLBACK; +SELECT pgv_remove('vars1', 'int1'); + pgv_remove +------------ + +(1 row) + +SELECT pgv_free(); + pgv_free +---------- + +(1 row) + +-- +-- +-- PGPRO-7856 +-- Test for case: we don't remove the package object without any variables at +-- the end of autonomous transaction but need to move the state of this object +-- to upper level. +-- +BEGIN; + BEGIN AUTONOMOUS; + SAVEPOINT sp1; + SELECT pgv_set('vars2', 'any1', 'variable exists'::text, true); + pgv_set +--------- + +(1 row) + + SELECT pgv_free(); + pgv_free +---------- + +(1 row) + + RELEASE sp1; + ROLLBACK; + BEGIN AUTONOMOUS; + SAVEPOINT sp2; + SAVEPOINT sp3; + SELECT pgv_free(); + pgv_free +---------- + +(1 row) + + COMMIT; +ROLLBACK; diff --git a/expected/pg_variables_atx_pkg_1.out b/expected/pg_variables_atx_pkg_1.out new file mode 100644 index 0000000..8e36d0a --- /dev/null +++ b/expected/pg_variables_atx_pkg_1.out @@ -0,0 +1,515 @@ +-- +-- PGPRO-7614: function pgv_free() inside autonomous transaction +-- +SELECT pgv_free(); + pgv_free +---------- + +(1 row) + +-- +-- +-- Functions pgv_free() + pgv_get() inside autonomous transaction; package +-- with regular variable; autonomous transaction with commit. +-- +BEGIN; + SELECT pgv_set('vars', 'int1', 1); + pgv_set +--------- + +(1 row) + + BEGIN AUTONOMOUS; +ERROR: syntax error at or near "AUTONOMOUS" +LINE 1: BEGIN AUTONOMOUS; + ^ + SELECT pgv_get('vars', 'int1', null::int); +ERROR: current transaction is aborted, commands ignored until end of transaction block + SELECT pgv_free(); +ERROR: current transaction is aborted, commands ignored until end of transaction block +-- ERROR: unrecognized package "vars" + SELECT pgv_get('vars', 'int1', null::int); +ERROR: current transaction is aborted, commands ignored until end of transaction block + COMMIT; +-- ERROR: unrecognized package "vars" + SELECT pgv_get('vars', 'int1', null::int); + pgv_get +--------- + 1 +(1 row) + +ROLLBACK; +WARNING: there is no transaction in progress +-- +-- +-- Function pgv_free() inside autonomous transaction; package with +-- regular variable; autonomous transaction with commit. +-- +BEGIN; + SELECT pgv_set('vars', 'int1', 1); + pgv_set +--------- + +(1 row) + + BEGIN AUTONOMOUS; +ERROR: syntax error at or near "AUTONOMOUS" +LINE 1: BEGIN AUTONOMOUS; + ^ + SELECT pgv_free(); +ERROR: current transaction is aborted, commands ignored until end of transaction block + COMMIT; +-- ERROR: unrecognized package "vars" + SELECT pgv_get('vars', 'int1', null::int); + pgv_get +--------- + 1 +(1 row) + +ROLLBACK; +WARNING: there is no transaction in progress +-- +-- +-- Function pgv_free() inside autonomous transaction; package with +-- regular variable; autonomous transaction with rollback. +-- +BEGIN; + SELECT pgv_set('vars', 'int1', 1); + pgv_set +--------- + +(1 row) + + BEGIN AUTONOMOUS; +ERROR: syntax error at or near "AUTONOMOUS" +LINE 1: BEGIN AUTONOMOUS; + ^ + SELECT pgv_free(); +ERROR: current transaction is aborted, commands ignored until end of transaction block + ROLLBACK; +-- ERROR: unrecognized package "vars" + SELECT pgv_get('vars', 'int1', null::int); + pgv_get +--------- + 1 +(1 row) + +ROLLBACK; +WARNING: there is no transaction in progress +-- +-- +-- Function pgv_free() inside autonomous transaction; package with +-- transactional variable; autonomous transaction with rollback. +-- +BEGIN; + SELECT pgv_set('vars', 'int1', 1, true); +ERROR: variable "int1" already created as NOT TRANSACTIONAL + BEGIN AUTONOMOUS; +ERROR: syntax error at or near "AUTONOMOUS" +LINE 1: BEGIN AUTONOMOUS; + ^ + SELECT pgv_free(); +ERROR: current transaction is aborted, commands ignored until end of transaction block + ROLLBACK; + SELECT pgv_get('vars', 'int1', null::int); + pgv_get +--------- + 1 +(1 row) + +ROLLBACK; +WARNING: there is no transaction in progress +-- +-- +-- Function pgv_free() inside autonomous transaction; package with +-- transactional variable; autonomous transaction with commit. +-- +BEGIN; + SELECT pgv_set('vars', 'int1', 1, true); +ERROR: variable "int1" already created as NOT TRANSACTIONAL + BEGIN AUTONOMOUS; +ERROR: syntax error at or near "AUTONOMOUS" +LINE 1: BEGIN AUTONOMOUS; + ^ + SELECT pgv_free(); +ERROR: current transaction is aborted, commands ignored until end of transaction block + COMMIT; +-- ERROR: unrecognized package "vars" + SELECT pgv_get('vars', 'int1', null::int); + pgv_get +--------- + 1 +(1 row) + +ROLLBACK; +WARNING: there is no transaction in progress +-- +-- +-- Function pgv_free() inside recursive autonomous transactions. +-- +BEGIN; + BEGIN AUTONOMOUS; +ERROR: syntax error at or near "AUTONOMOUS" +LINE 1: BEGIN AUTONOMOUS; + ^ + SELECT pgv_set('vars', 'int1', 1); +ERROR: current transaction is aborted, commands ignored until end of transaction block + BEGIN AUTONOMOUS; +ERROR: syntax error at or near "AUTONOMOUS" +LINE 1: BEGIN AUTONOMOUS; + ^ + BEGIN AUTONOMOUS; +ERROR: syntax error at or near "AUTONOMOUS" +LINE 1: BEGIN AUTONOMOUS; + ^ + SELECT pgv_free(); +ERROR: current transaction is aborted, commands ignored until end of transaction block + COMMIT; +-- ERROR: unrecognized package "vars" + SELECT pgv_get('vars', 'int1', null::int); + pgv_get +--------- + 1 +(1 row) + + COMMIT; +WARNING: there is no transaction in progress +-- ERROR: unrecognized package "vars" + SELECT pgv_get('vars', 'int1', null::int); + pgv_get +--------- + 1 +(1 row) + + COMMIT; +WARNING: there is no transaction in progress +ROLLBACK; +WARNING: there is no transaction in progress +-- +-- +-- Function pgv_free() inside recursive autonomous transactions; +-- recreating the package after deletion with using regular +-- variable. +-- +BEGIN; + SELECT pgv_set('vars', 'int1', 1); + pgv_set +--------- + +(1 row) + + BEGIN AUTONOMOUS; +ERROR: syntax error at or near "AUTONOMOUS" +LINE 1: BEGIN AUTONOMOUS; + ^ + BEGIN AUTONOMOUS; +ERROR: syntax error at or near "AUTONOMOUS" +LINE 1: BEGIN AUTONOMOUS; + ^ + SELECT pgv_get('vars', 'int1', null::int); +ERROR: current transaction is aborted, commands ignored until end of transaction block + BEGIN AUTONOMOUS; +ERROR: syntax error at or near "AUTONOMOUS" +LINE 1: BEGIN AUTONOMOUS; + ^ + SELECT pgv_free(); +ERROR: current transaction is aborted, commands ignored until end of transaction block + COMMIT; +-- ERROR: unrecognized package "vars" + SELECT pgv_get('vars', 'int1', null::int); + pgv_get +--------- + 1 +(1 row) + + COMMIT; +WARNING: there is no transaction in progress + SELECT pgv_set('vars', 'int1', 2); + pgv_set +--------- + +(1 row) + + COMMIT; +WARNING: there is no transaction in progress + SELECT pgv_get('vars', 'int1', null::int); + pgv_get +--------- + 2 +(1 row) + +ROLLBACK; +WARNING: there is no transaction in progress +-- +-- +-- Function pgv_free() inside recursive autonomous transactions; +-- recreating the package after deletion with using transactional +-- variable. +-- +BEGIN; + SELECT pgv_set('vars', 'int1', 1); + pgv_set +--------- + +(1 row) + + BEGIN AUTONOMOUS; +ERROR: syntax error at or near "AUTONOMOUS" +LINE 1: BEGIN AUTONOMOUS; + ^ + BEGIN AUTONOMOUS; +ERROR: syntax error at or near "AUTONOMOUS" +LINE 1: BEGIN AUTONOMOUS; + ^ + SELECT pgv_get('vars', 'int1', null::int); +ERROR: current transaction is aborted, commands ignored until end of transaction block + BEGIN AUTONOMOUS; +ERROR: syntax error at or near "AUTONOMOUS" +LINE 1: BEGIN AUTONOMOUS; + ^ + SELECT pgv_free(); +ERROR: current transaction is aborted, commands ignored until end of transaction block + COMMIT; +-- ERROR: unrecognized package "vars" + SELECT pgv_get('vars', 'int1', null::int); + pgv_get +--------- + 1 +(1 row) + + COMMIT; +WARNING: there is no transaction in progress + SELECT pgv_set('vars', 'int1', 2, true); +ERROR: variable "int1" already created as NOT TRANSACTIONAL + SELECT pgv_list(); + pgv_list +--------------- + (vars,int1,f) +(1 row) + + COMMIT; +WARNING: there is no transaction in progress +-- ERROR: unrecognized package "vars" + SELECT pgv_get('vars', 'int1', null::int); + pgv_get +--------- + 1 +(1 row) + +ROLLBACK; +WARNING: there is no transaction in progress +-- +-- +-- Test for case: do not free hash_seq_search scans of parent transaction +-- at end of the autonomous transaction. +-- +BEGIN; + SELECT pgv_insert('test', 'x', row (1::int, 2::int), false); + pgv_insert +------------ + +(1 row) + + SELECT pgv_insert('test', 'x', row (3::int, 4::int), false); + pgv_insert +------------ + +(1 row) + + DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x'); +-- (1,2) + FETCH 1 IN r1_cur; + pgv_select +------------ + (1,2) +(1 row) + + BEGIN AUTONOMOUS; +ERROR: syntax error at or near "AUTONOMOUS" +LINE 1: BEGIN AUTONOMOUS; + ^ + ROLLBACK; +-- (3,4) + FETCH 1 IN r1_cur; +ERROR: cursor "r1_cur" does not exist + SELECT pgv_remove('test', 'x'); + pgv_remove +------------ + +(1 row) + +-- ERROR: unrecognized package "test" + FETCH 1 IN r1_cur; +ERROR: cursor "r1_cur" does not exist +ROLLBACK; +WARNING: there is no transaction in progress +-- +-- +-- Test for case: pgv_free() should free hash_seq_search scans of all +-- (current ATX + parent) transactions. +-- +BEGIN; + SELECT pgv_insert('test', 'x', row (1::int, 2::int), false); + pgv_insert +------------ + +(1 row) + + DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x'); +-- (1,2) + FETCH 1 IN r1_cur; + pgv_select +------------ + (1,2) +(1 row) + + BEGIN AUTONOMOUS; +ERROR: syntax error at or near "AUTONOMOUS" +LINE 1: BEGIN AUTONOMOUS; + ^ + SELECT pgv_free(); +ERROR: current transaction is aborted, commands ignored until end of transaction block + ROLLBACK; +-- ERROR: unrecognized package "test" + FETCH 1 IN r1_cur; +ERROR: cursor "r1_cur" does not exist +ROLLBACK; +WARNING: there is no transaction in progress +-- +-- +-- Test for case: pgv_set() created a regular variable; rollback +-- removes package state and creates a new state to make package valid. +-- Commit of next autonomous transaction should not replace this new +-- state (this is not allowed for autonomous transaction). +-- +BEGIN; + BEGIN AUTONOMOUS; +ERROR: syntax error at or near "AUTONOMOUS" +LINE 1: BEGIN AUTONOMOUS; + ^ + SELECT pgv_set('vars', 'int1', 1); +ERROR: current transaction is aborted, commands ignored until end of transaction block + ROLLBACK; + BEGIN AUTONOMOUS; +ERROR: syntax error at or near "AUTONOMOUS" +LINE 1: BEGIN AUTONOMOUS; + ^ + SELECT pgv_set('vars', 'int1', 2); + pgv_set +--------- + +(1 row) + + COMMIT; +WARNING: there is no transaction in progress +ROLLBACK; +WARNING: there is no transaction in progress +SELECT pgv_remove('vars', 'int1'); + pgv_remove +------------ + +(1 row) + +-- +-- +-- Test for case: pgv_set() created a regular variable and package with +-- (atxlevel=1, level=1). COMMIT changes this level to (atxlevel=1, level=0). +-- In the next autonomous transaction (atxlevel=1, level=1) we erroneously +-- detect that the package changed in upper transaction and remove the +-- package state (this is not allowed for autonomous transaction). +-- +BEGIN; + BEGIN AUTONOMOUS; +ERROR: syntax error at or near "AUTONOMOUS" +LINE 1: BEGIN AUTONOMOUS; + ^ + SELECT pgv_set('vars', 'int1', 2); +ERROR: current transaction is aborted, commands ignored until end of transaction block + COMMIT; + BEGIN AUTONOMOUS; +ERROR: syntax error at or near "AUTONOMOUS" +LINE 1: BEGIN AUTONOMOUS; + ^ + SELECT pgv_free(); + pgv_free +---------- + +(1 row) + + SELECT pgv_set('vars', 'int1', 2, true); + pgv_set +--------- + +(1 row) + + COMMIT; +WARNING: there is no transaction in progress +ROLLBACK; +WARNING: there is no transaction in progress +-- +-- +-- Test for case: pgv_set() created a regular variable and package with +-- (atxlevel=1, level=1). ROLLBACK changes this level to (atxlevel=0, level=0). +-- But ROLLBACK shouldn't change atxlevel in case rollback of sub-transaction. +-- +BEGIN; + BEGIN AUTONOMOUS; +ERROR: syntax error at or near "AUTONOMOUS" +LINE 1: BEGIN AUTONOMOUS; + ^ + SAVEPOINT sp1; +ERROR: current transaction is aborted, commands ignored until end of transaction block + SELECT pgv_set('vars1', 'int1', 0); +ERROR: current transaction is aborted, commands ignored until end of transaction block + ROLLBACK TO sp1; +ERROR: savepoint "sp1" does not exist + COMMIT; +ROLLBACK; +WARNING: there is no transaction in progress +SELECT pgv_remove('vars1', 'int1'); +ERROR: unrecognized package "vars1" +SELECT pgv_free(); + pgv_free +---------- + +(1 row) + +-- +-- +-- PGPRO-7856 +-- Test for case: we don't remove the package object without any variables at +-- the end of autonomous transaction but need to move the state of this object +-- to upper level. +-- +BEGIN; + BEGIN AUTONOMOUS; +ERROR: syntax error at or near "AUTONOMOUS" +LINE 1: BEGIN AUTONOMOUS; + ^ + SAVEPOINT sp1; +ERROR: current transaction is aborted, commands ignored until end of transaction block + SELECT pgv_set('vars2', 'any1', 'variable exists'::text, true); +ERROR: current transaction is aborted, commands ignored until end of transaction block + SELECT pgv_free(); +ERROR: current transaction is aborted, commands ignored until end of transaction block + RELEASE sp1; +ERROR: current transaction is aborted, commands ignored until end of transaction block + ROLLBACK; + BEGIN AUTONOMOUS; +ERROR: syntax error at or near "AUTONOMOUS" +LINE 1: BEGIN AUTONOMOUS; + ^ + SAVEPOINT sp2; +ERROR: SAVEPOINT can only be used in transaction blocks + SAVEPOINT sp3; +ERROR: SAVEPOINT can only be used in transaction blocks + SELECT pgv_free(); + pgv_free +---------- + +(1 row) + + COMMIT; +WARNING: there is no transaction in progress +ROLLBACK; +WARNING: there is no transaction in progress diff --git a/expected/pg_variables_trans.out b/expected/pg_variables_trans.out index 025cc77..4370646 100644 --- a/expected/pg_variables_trans.out +++ b/expected/pg_variables_trans.out @@ -1858,8 +1858,8 @@ SELECT pgv_insert('package', 'errs',row(1), true); (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_insert('vars4', 'r1', row (('str1'::text, 'str1'::text))); +ERROR: could not identify a hash function for type record SELECT pgv_select('vars4', 'r1', 0); ERROR: unrecognized package "vars4" -- If variable created and removed in same transaction level, @@ -2032,3 +2032,1853 @@ SELECT 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) + +CLOSE r1_cur; +SELECT pgv_remove('test', 'x'); + pgv_remove +------------ + +(1 row) + +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) + +CLOSE r1_cur; +SELECT pgv_remove('test'); + pgv_remove +------------ + +(1 row) + +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) + +CLOSE r1_cur; +SELECT pgv_free(); + pgv_free +---------- + +(1 row) + +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); +--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) + +--- +--- 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) + +CLOSE r1_cur; +SELECT pgv_remove('test', 'z2'); + pgv_remove +------------ + +(1 row) + +FETCH 1 in r1_cur; +ERROR: cursor "r1_cur" does not exist +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) + +CLOSE r1_cur; +SELECT pgv_remove('test', 'z3'); + pgv_remove +------------ + +(1 row) + +FETCH 1 in r1_cur; +ERROR: cursor "r1_cur" does not exist +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; +--SELECT pgv_remove('test', 'z3'); +--COMMIT; +--SELECT pgv_select('test', '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 +--- +--- Amount of allocated memory may vary from version to version, as well as from +--- platform to platform. Moreover, postgres versions less than 90600 always +--- show zero allocated memory. So, it's much easier to check if allocated +--- memory size is multiple of 8k since we use ALLOCSET_DEFAULT_INITSIZE +--- (see memutils.h), insted of creating multiple outputs files. +--- +CREATE TEMP VIEW pgv_stats_view(pack, mem_mult) AS + SELECT package, allocated_memory % 8192 as allocated_multiple_8192 + FROM pgv_stats() + ORDER BY 1; +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 pack, mem_mult FROM pgv_stats_view; +DECLARE r2_cur CURSOR FOR SELECT pack, mem_mult FROM pgv_stats_view; +FETCH 1 in r1_cur; + pack | mem_mult +------+---------- + test | 0 +(1 row) + +FETCH 1 in r2_cur; + pack | mem_mult +------+---------- + 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 pack, mem_mult FROM pgv_stats_view; +DECLARE r2_cur CURSOR FOR SELECT pack, mem_mult FROM pgv_stats_view; +FETCH 1 in r1_cur; + pack | mem_mult +------+---------- + test | 0 +(1 row) + +FETCH 1 in r2_cur; + pack | mem_mult +------+---------- + 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 pack, mem_mult FROM pgv_stats_view; +DECLARE r2_cur CURSOR FOR SELECT pack, mem_mult FROM pgv_stats_view; +FETCH 1 in r1_cur; + pack | mem_mult +------+---------- + test | 0 +(1 row) + +FETCH 1 in r2_cur; + pack | mem_mult +------+---------- + 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 pack, mem_mult FROM pgv_stats_view; +DECLARE r2_cur CURSOR FOR SELECT pack, mem_mult FROM pgv_stats_view; +FETCH 1 in r1_cur; + pack | mem_mult +------+---------- + test | 0 +(1 row) + +FETCH 1 in r2_cur; + pack | mem_mult +------+---------- + 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) + +--- +--- 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 pack, mem_mult FROM pgv_stats_view; +DECLARE r2_cur CURSOR FOR SELECT pack, mem_mult FROM pgv_stats_view; +FETCH 1 in r1_cur; + pack | mem_mult +------+---------- + test | 0 +(1 row) + +FETCH 1 in r2_cur; + pack | mem_mult +------+---------- + 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 pack, mem_mult FROM pgv_stats_view; +DECLARE r2_cur CURSOR FOR SELECT pack, mem_mult FROM pgv_stats_view; +FETCH 1 in r1_cur; + pack | mem_mult +------+---------- + test | 0 +(1 row) + +FETCH 1 in r2_cur; + pack | mem_mult +------+---------- + 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 pack, mem_mult FROM pgv_stats_view; +DECLARE r2_cur CURSOR FOR SELECT pack, mem_mult FROM pgv_stats_view; +SAVEPOINT comm2; +FETCH 1 in r1_cur; + pack | mem_mult +------+---------- + test | 0 +(1 row) + +FETCH 1 in r2_cur; + pack | mem_mult +------+---------- + 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 pack, mem_mult FROM pgv_stats_view; +DECLARE r2_cur CURSOR FOR SELECT pack, mem_mult FROM pgv_stats_view; +FETCH 1 in r1_cur; + pack | mem_mult +------+---------- + test | 0 +(1 row) + +SAVEPOINT comm2; +FETCH 1 in r2_cur; + pack | mem_mult +------+---------- + 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 pack, mem_mult FROM pgv_stats_view; +DECLARE r2_cur CURSOR FOR SELECT pack, mem_mult FROM pgv_stats_view; +FETCH 1 in r1_cur; + pack | mem_mult +------+---------- + test | 0 +(1 row) + +FETCH 1 in r2_cur; + pack | mem_mult +------+---------- + test | 0 +(1 row) + +SAVEPOINT comm2; +COMMIT; +DROP VIEW pgv_stats_view; +SELECT pgv_free(); + pgv_free +---------- + +(1 row) + +--- +--- Test case for issue #32 [PGPRO-4456] +--- +CREATE TEMP TABLE tab (id int, t varchar); +INSERT INTO tab VALUES (0, 'str00'); +SELECT pgv_insert('vars', 'r1', row(1, 'str1', 'str2')); + pgv_insert +------------ + +(1 row) + +SELECT pgv_insert('vars', 'a', tab) FROM tab; + pgv_insert +------------ + +(1 row) + +SELECT pgv_insert('vars', 'r1', tab) FROM tab; +ERROR: new record structure have 2 attributes, but variable "r1" structure have 3. +SELECT pgv_select('vars', 'r1'); + pgv_select +--------------- + (1,str1,str2) +(1 row) + +SELECT pgv_insert('vars', 'r2', row(1, 'str1'::varchar)); + pgv_insert +------------ + +(1 row) + +SELECT pgv_insert('vars', 'b', tab) FROM tab; + pgv_insert +------------ + +(1 row) + +SELECT pgv_insert('vars', 'r2', tab) FROM tab; + pgv_insert +------------ + +(1 row) + +SELECT pgv_select('vars', 'r2'); + pgv_select +------------ + (1,str1) + (0,str00) +(2 rows) + +SELECT pgv_free(); + pgv_free +---------- + +(1 row) + +-- +-- Test case for issue #38 [PGPRO-4676] +-- +SELECT pgv_insert('test', 'x5', ROW ((2::int, 1::int)), TRUE); +ERROR: could not identify a hash function for type record +-- +-- Test case for PGPRO-7614: crash by using cursor after rollback of cursor +-- creation. +-- +BEGIN; + SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), true); + pgv_insert +------------ + +(1 row) + + 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; + FETCH 1 in r1_cur; + pgv_select +------------ +(0 rows) + +ROLLBACK; +BEGIN; + SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE); + pgv_insert +------------ + +(1 row) + + DECLARE r1_cur CURSOR FOR SELECT pgv_stats(); + SAVEPOINT sp1; + FETCH 1 in r1_cur; + pgv_stats +-------------- + (test,32768) +(1 row) + + ROLLBACK TO SAVEPOINT sp1; + FETCH 1 in r1_cur; + pgv_stats +----------- +(0 rows) + +ROLLBACK; diff --git a/expected/pg_variables_trans_0.out b/expected/pg_variables_trans_0.out new file mode 100644 index 0000000..8a3c54c --- /dev/null +++ b/expected/pg_variables_trans_0.out @@ -0,0 +1,3888 @@ +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) + +CLOSE r1_cur; +SELECT pgv_remove('test', 'x'); + pgv_remove +------------ + +(1 row) + +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) + +CLOSE r1_cur; +SELECT pgv_remove('test'); + pgv_remove +------------ + +(1 row) + +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) + +CLOSE r1_cur; +SELECT pgv_free(); + pgv_free +---------- + +(1 row) + +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); +--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) + +--- +--- 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) + +CLOSE r1_cur; +SELECT pgv_remove('test', 'z2'); + pgv_remove +------------ + +(1 row) + +FETCH 1 in r1_cur; +ERROR: cursor "r1_cur" does not exist +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) + +CLOSE r1_cur; +SELECT pgv_remove('test', 'z3'); + pgv_remove +------------ + +(1 row) + +FETCH 1 in r1_cur; +ERROR: cursor "r1_cur" does not exist +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; +--SELECT pgv_remove('test', 'z3'); +--COMMIT; +--SELECT pgv_select('test', '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 +--- +--- Amount of allocated memory may vary from version to version, as well as from +--- platform to platform. Moreover, postgres versions less than 90600 always +--- show zero allocated memory. So, it's much easier to check if allocated +--- memory size is multiple of 8k since we use ALLOCSET_DEFAULT_INITSIZE +--- (see memutils.h), insted of creating multiple outputs files. +--- +CREATE TEMP VIEW pgv_stats_view(pack, mem_mult) AS + SELECT package, allocated_memory % 8192 as allocated_multiple_8192 + FROM pgv_stats() + ORDER BY 1; +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 pack, mem_mult FROM pgv_stats_view; +DECLARE r2_cur CURSOR FOR SELECT pack, mem_mult FROM pgv_stats_view; +FETCH 1 in r1_cur; + pack | mem_mult +------+---------- + test | 0 +(1 row) + +FETCH 1 in r2_cur; + pack | mem_mult +------+---------- + 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 pack, mem_mult FROM pgv_stats_view; +DECLARE r2_cur CURSOR FOR SELECT pack, mem_mult FROM pgv_stats_view; +FETCH 1 in r1_cur; + pack | mem_mult +------+---------- + test | 0 +(1 row) + +FETCH 1 in r2_cur; + pack | mem_mult +------+---------- + 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 pack, mem_mult FROM pgv_stats_view; +DECLARE r2_cur CURSOR FOR SELECT pack, mem_mult FROM pgv_stats_view; +FETCH 1 in r1_cur; + pack | mem_mult +------+---------- + test | 0 +(1 row) + +FETCH 1 in r2_cur; + pack | mem_mult +------+---------- + 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 pack, mem_mult FROM pgv_stats_view; +DECLARE r2_cur CURSOR FOR SELECT pack, mem_mult FROM pgv_stats_view; +FETCH 1 in r1_cur; + pack | mem_mult +------+---------- + test | 0 +(1 row) + +FETCH 1 in r2_cur; + pack | mem_mult +------+---------- + 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) + +--- +--- 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 pack, mem_mult FROM pgv_stats_view; +DECLARE r2_cur CURSOR FOR SELECT pack, mem_mult FROM pgv_stats_view; +FETCH 1 in r1_cur; + pack | mem_mult +------+---------- + test | 0 +(1 row) + +FETCH 1 in r2_cur; + pack | mem_mult +------+---------- + 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 pack, mem_mult FROM pgv_stats_view; +DECLARE r2_cur CURSOR FOR SELECT pack, mem_mult FROM pgv_stats_view; +FETCH 1 in r1_cur; + pack | mem_mult +------+---------- + test | 0 +(1 row) + +FETCH 1 in r2_cur; + pack | mem_mult +------+---------- + 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 pack, mem_mult FROM pgv_stats_view; +DECLARE r2_cur CURSOR FOR SELECT pack, mem_mult FROM pgv_stats_view; +SAVEPOINT comm2; +FETCH 1 in r1_cur; + pack | mem_mult +------+---------- + test | 0 +(1 row) + +FETCH 1 in r2_cur; + pack | mem_mult +------+---------- + 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 pack, mem_mult FROM pgv_stats_view; +DECLARE r2_cur CURSOR FOR SELECT pack, mem_mult FROM pgv_stats_view; +FETCH 1 in r1_cur; + pack | mem_mult +------+---------- + test | 0 +(1 row) + +SAVEPOINT comm2; +FETCH 1 in r2_cur; + pack | mem_mult +------+---------- + 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 pack, mem_mult FROM pgv_stats_view; +DECLARE r2_cur CURSOR FOR SELECT pack, mem_mult FROM pgv_stats_view; +FETCH 1 in r1_cur; + pack | mem_mult +------+---------- + test | 0 +(1 row) + +FETCH 1 in r2_cur; + pack | mem_mult +------+---------- + test | 0 +(1 row) + +SAVEPOINT comm2; +COMMIT; +DROP VIEW pgv_stats_view; +SELECT pgv_free(); + pgv_free +---------- + +(1 row) + +--- +--- Test case for issue #32 [PGPRO-4456] +--- +CREATE TEMP TABLE tab (id int, t varchar); +INSERT INTO tab VALUES (0, 'str00'); +SELECT pgv_insert('vars', 'r1', row(1, 'str1', 'str2')); + pgv_insert +------------ + +(1 row) + +SELECT pgv_insert('vars', 'a', tab) FROM tab; + pgv_insert +------------ + +(1 row) + +SELECT pgv_insert('vars', 'r1', tab) FROM tab; +ERROR: new record structure have 2 attributes, but variable "r1" structure have 3. +SELECT pgv_select('vars', 'r1'); + pgv_select +--------------- + (1,str1,str2) +(1 row) + +SELECT pgv_insert('vars', 'r2', row(1, 'str1'::varchar)); + pgv_insert +------------ + +(1 row) + +SELECT pgv_insert('vars', 'b', tab) FROM tab; + pgv_insert +------------ + +(1 row) + +SELECT pgv_insert('vars', 'r2', tab) FROM tab; + pgv_insert +------------ + +(1 row) + +SELECT pgv_select('vars', 'r2'); + pgv_select +------------ + (1,str1) + (0,str00) +(2 rows) + +SELECT pgv_free(); + pgv_free +---------- + +(1 row) + +-- +-- Test case for issue #38 [PGPRO-4676] +-- +SELECT pgv_insert('test', 'x5', ROW ((2::int, 1::int)), TRUE); + pgv_insert +------------ + +(1 row) + +-- +-- Test case for PGPRO-7614: crash by using cursor after rollback of cursor +-- creation. +-- +BEGIN; + SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), true); + pgv_insert +------------ + +(1 row) + + 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; + FETCH 1 in r1_cur; + pgv_select +------------ +(0 rows) + +ROLLBACK; +BEGIN; + SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE); + pgv_insert +------------ + +(1 row) + + DECLARE r1_cur CURSOR FOR SELECT pgv_stats(); + SAVEPOINT sp1; + FETCH 1 in r1_cur; + pgv_stats +-------------- + (test,32768) +(1 row) + + ROLLBACK TO SAVEPOINT sp1; + FETCH 1 in r1_cur; + pgv_stats +----------- +(0 rows) + +ROLLBACK; diff --git a/pg_variables.c b/pg_variables.c old mode 100755 new mode 100644 index 781b460..c8ee939 --- a/pg_variables.c +++ b/pg_variables.c @@ -3,18 +3,20 @@ * pg_variables.c * Functions, which get or set variables values * - * Copyright (c) 2015-2016, Postgres Professional + * Copyright (c) 2015-2022, Postgres Professional * *------------------------------------------------------------------------- */ #include "postgres.h" #include "fmgr.h" #include "funcapi.h" +#include "miscadmin.h" #include "access/htup_details.h" #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" @@ -45,7 +47,9 @@ PG_FUNCTION_INFO_V1(get_packages_and_variables); PG_FUNCTION_INFO_V1(get_packages_stats); extern void _PG_init(void); +#if PG_VERSION_NUM < 150000 extern void _PG_fini(void); +#endif static void ensurePackagesHashExists(void); static void getKeyFromName(text *name, char *key); @@ -56,12 +60,12 @@ 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); -static void releaseSavepoint(TransObject *object, TransObjectType type); -static void rollbackSavepoint(TransObject *object, TransObjectType type); +static void releaseSavepoint(TransObject *object, TransObjectType type, bool sub); +static void rollbackSavepoint(TransObject *object, TransObjectType type, bool sub); static void copyValue(VarState *src, VarState *dest, Variable *destVar); static void freeValue(VarState *varstate, bool is_record); @@ -75,12 +79,17 @@ static void addToChangesStackUpperLevel(TransObject *object, TransObjectType type); static void pushChangesStack(void); -static int numOfRegVars(Package *package); +static int numOfRegVars(Package *package); + +#ifdef PGPRO_EE +static void pgvSaveContext(void); +static void pgvRestoreContext(void); +#endif /* Constructors */ static void makePackHTAB(Package *package, bool is_trans); -static inline ChangedObject *makeChangedObject(TransObject *object, - MemoryContext ctx); +static inline ChangedObject * makeChangedObject(TransObject *object, + MemoryContext ctx); static void initObjectHistory(TransObject *object, TransObjectType type); /* Hook functions */ @@ -98,7 +107,7 @@ do { \ (errcode(ERRCODE_INVALID_PARAMETER_VALUE), \ errmsg("variable name can not be NULL"))); \ } while(0) -#else /* PG_VERSION_NUM < 120000 */ +#else /* PG_VERSION_NUM < 120000 */ #define CHECK_ARGS_FOR_NULL() \ do { \ if (fcinfo->argnull[0]) \ @@ -110,17 +119,20 @@ do { \ (errcode(ERRCODE_INVALID_PARAMETER_VALUE), \ errmsg("variable name can not be NULL"))); \ } while(0) -#endif /* PG_VERSION_NUM */ +#endif /* PG_VERSION_NUM */ + +/* User controlled GUCs */ +bool convert_unknownoid_guc; +bool convert_unknownoid; static HTAB *packagesHash = NULL; static MemoryContext ModuleContext = NULL; /* Recent package */ static Package *LastPackage = NULL; + /* Recent variable */ static Variable *LastVariable = NULL; -/* Recent row type id */ -static Oid LastTypeId = InvalidOid; /* Saved hook values for recall */ static ExecutorEnd_hook_type prev_ExecutorEnd = NULL; @@ -129,6 +141,421 @@ 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, 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 + * 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, because it would not be reset by the + * time pgvTransCallback is called. + */ +static List *variables_stats = NIL; +static List *packages_stats = NIL; + +typedef struct tagVariableStatEntry +{ + HTAB *hash; + HASH_SEQ_STATUS *status; + Variable *variable; + Package *package; + Levels levels; + void **user_fctx; /* pointer to funcctx->user_fctx */ +} VariableStatEntry; + +typedef struct tagPackageStatEntry +{ + HASH_SEQ_STATUS *status; + Levels levels; + void **user_fctx; /* pointer to funcctx->user_fctx */ +} PackageStatEntry; + +#ifdef PGPRO_EE +/* + * Context for storing/restoring parameters when switching autonomous + * transactions + */ +typedef struct PgvContextStruct +{ + dlist_head *changesStack; + MemoryContext changesStackContext; + struct PgvContextStruct *next; +} PgvContextStruct; + +static PgvContextStruct *pgv_context = NULL; + +#endif /* PGPRO_EE */ + +/* + * Compare functions for VariableStatEntry and PackageStatEntry members. + */ +static bool +VariableStatEntry_status_eq(void *entry, void *value) +{ + return ((VariableStatEntry *) entry)->status == (HASH_SEQ_STATUS *) value; +} + +static bool +VariableStatEntry_variable_eq(void *entry, void *value) +{ + return ((VariableStatEntry *) entry)->variable == (Variable *) value; +} + +static bool +VariableStatEntry_package_eq(void *entry, void *value) +{ + return ((VariableStatEntry *) entry)->package == (Package *) value; +} + +static bool +VariableStatEntry_eq_all(void *entry, void *value) +{ + return true; +} + +static bool +VariableStatEntry_level_eq(void *entry, void *value) +{ + return +#ifdef PGPRO_EE + /* Compare ATX level */ + ((VariableStatEntry *) entry)->levels.atxlevel == ((Levels *) value)->atxlevel && +#endif + ((VariableStatEntry *) entry)->levels.level == ((Levels *) value)->level; +} + +static bool +PackageStatEntry_status_eq(void *entry, void *value) +{ + return ((PackageStatEntry *) entry)->status == (HASH_SEQ_STATUS *) value; +} + +static bool +PackageStatEntry_level_eq(void *entry, void *value) +{ + return +#ifdef PGPRO_EE + /* Compare ATX level */ + ((PackageStatEntry *) entry)->levels.atxlevel == ((Levels *) value)->atxlevel && +#endif + ((PackageStatEntry *) entry)->levels.level == ((Levels *) value)->level; +} + +#ifdef PGPRO_EE +static bool +VariableStatEntry_is_transactional(void *entry, void *value) +{ + return ((VariableStatEntry *) entry)->variable->is_transactional; +} +#endif + +/* + * VariableStatEntry and PackageStatEntry status member getters. + */ +static HASH_SEQ_STATUS * +VariableStatEntry_status_ptr(void *entry) +{ + return ((VariableStatEntry *) entry)->status; +} + +static HASH_SEQ_STATUS * +PackageStatEntry_status_ptr(void *entry) +{ + return ((PackageStatEntry *) entry)->status; +} + +/* + * VariableStatEntry and PackageStatEntry functions for clear function context. + */ +static void +VariableStatEntry_clear_fctx(void *entry) +{ + VariableStatEntry *e = (VariableStatEntry *) entry; + if (e->user_fctx) + *e->user_fctx = NULL; +} + +static void +PackageStatEntry_clear_fctx(void *entry) +{ + PackageStatEntry *e = (PackageStatEntry *) entry; + if (e->user_fctx) + *e->user_fctx = NULL; +} + +/* + * 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. + * + */ +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 */ + void (*clear_fctx) (void *); /* clear function context */ +} RemoveIfContext; + +static void +list_remove_if(RemoveIfContext ctx) +{ +#if (PG_VERSION_NUM < 130000) + ListCell *cell, + *next, + *prev = NULL; + void *entry = NULL; + + for (cell = list_head(*ctx.list); cell; cell = next) + { + entry = lfirst(cell); + next = lnext(cell); + + if (ctx.eq(entry, ctx.value)) + { + *ctx.list = list_delete_cell(*ctx.list, cell, prev); + + if (ctx.term) +#ifdef PGPRO_EE + hash_seq_term_all_levels(ctx.getter(entry)); +#else + hash_seq_term(ctx.getter(entry)); +#endif + + ctx.clear_fctx(entry); + + pfree(ctx.getter(entry)); + pfree(entry); + + if (ctx.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; + void *entry = NULL; + + foreach(cell, *ctx.list) + { + entry = lfirst(cell); + + if (ctx.eq(entry, ctx.value)) + { + *ctx.list = foreach_delete_current(*ctx.list, cell); + + if (ctx.term) +#ifdef PGPRO_EE + hash_seq_term_all_levels(ctx.getter(entry)); +#else + hash_seq_term(ctx.getter(entry)); +#endif + + ctx.clear_fctx(entry); + + pfree(ctx.getter(entry)); + pfree(entry); + + if (ctx.match_first) + return; + } + } +#endif +} + +/* + * Remove first entry for status. + */ +static void +remove_variables_status(List **list, HASH_SEQ_STATUS *status) +{ + RemoveIfContext ctx = + { + .list = list, + .value = status, + .eq = VariableStatEntry_status_eq, + .getter = VariableStatEntry_status_ptr, + .match_first = true, + .term = false, + .clear_fctx = VariableStatEntry_clear_fctx + }; + + list_remove_if(ctx); +} + +/* + * Remove first entry for variable. + */ +static void +remove_variables_variable(List **list, Variable *variable) +{ + /* + * 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, + .clear_fctx = VariableStatEntry_clear_fctx + }; + + list_remove_if(ctx); +} + +/* + * Remove all the entries for package. + */ +static void +remove_variables_package(List **list, Package *package) +{ + RemoveIfContext ctx = + { + .list = list, + .value = package, + .eq = VariableStatEntry_package_eq, + .getter = VariableStatEntry_status_ptr, + .match_first = false, + .term = true, + .clear_fctx = VariableStatEntry_clear_fctx + }; + + list_remove_if(ctx); +} + +/* + * Remove all the entries for level. + */ +static void +remove_variables_level(List **list, Levels *levels) +{ + RemoveIfContext ctx = + { + .list = list, + .value = levels, + .eq = VariableStatEntry_level_eq, + .getter = VariableStatEntry_status_ptr, + .match_first = false, + .term = false, + .clear_fctx = VariableStatEntry_clear_fctx + }; + + list_remove_if(ctx); +} + +/* + * Delete variables stats list. + */ +static void +remove_variables_all(List **list) +{ + RemoveIfContext ctx = + { + .list = list, + .value = NULL, + .eq = VariableStatEntry_eq_all, + .getter = VariableStatEntry_status_ptr, + .match_first = false, + .term = true, + .clear_fctx = VariableStatEntry_clear_fctx + }; + + list_remove_if(ctx); +} + +/* + * Remove first entrie with status for packages list. + */ +static void +remove_packages_status(List **list, HASH_SEQ_STATUS *status) +{ + RemoveIfContext ctx = + { + .list = list, + .value = status, + .eq = PackageStatEntry_status_eq, + .getter = PackageStatEntry_status_ptr, + .match_first = true, + .term = false, + .clear_fctx = PackageStatEntry_clear_fctx + }; + + list_remove_if(ctx); +} + +/* + * Remove all the entries with level for packages list. + */ +static void +remove_packages_level(List **list, Levels *levels) +{ + RemoveIfContext ctx = + { + .list = list, + .value = levels, + .eq = PackageStatEntry_level_eq, + .getter = PackageStatEntry_status_ptr, + .match_first = false, + .term = true, + .clear_fctx = PackageStatEntry_clear_fctx + }; + + list_remove_if(ctx); +} + +#ifdef PGPRO_EE +/* + * Remove all transactional entries. + */ +static void +remove_variables_transactional(List **list) +{ + RemoveIfContext ctx = + { + .list = list, + .value = NULL, + .eq = VariableStatEntry_is_transactional, + .getter = VariableStatEntry_status_ptr, + .match_first = false, + .term = true, + .clear_fctx = VariableStatEntry_clear_fctx + }; + + list_remove_if(ctx); +} +#endif + +static void freeStatsLists(void); + /* Returns a lists of packages and variables changed at current subxact level */ #define get_actual_changes_list() \ ( \ @@ -164,7 +591,7 @@ static void variable_set(text *package_name, text *var_name, Oid typid, Datum value, bool is_null, bool is_transactional) { - Package *package; + Package *package; Variable *variable; ScalarVar *scalar; @@ -195,7 +622,7 @@ static Datum variable_get(text *package_name, text *var_name, Oid typid, bool *is_null, bool strict) { - Package *package; + Package *package; Variable *variable; ScalarVar *scalar; @@ -309,7 +736,7 @@ variable_insert(PG_FUNCTION_ARGS) text *package_name; text *var_name; HeapTupleHeader rec; - Package *package; + Package *package; Variable *variable; bool is_transactional; @@ -387,26 +814,35 @@ variable_insert(PG_FUNCTION_ARGS) tupTypmod = HeapTupleHeaderGetTypMod(rec); record = &(GetActualValue(variable).record); - if (!record->tupdesc) + tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); + + if (!record->tupdesc || variable->is_deleted) { /* * This is the first record for the var_name. Initialize record. */ - tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); + /* Convert UNKNOWNOID to TEXTOID if needed + * tupdesc may be changed + */ + if (convert_unknownoid) + { + coerce_unknown_first_record(&tupdesc, &rec); + } + init_record(record, tupdesc, variable); + variable->is_deleted = false; } - else if (LastTypeId == RECORDOID || !OidIsValid(LastTypeId) || - LastTypeId != tupType) + else { /* * We need to check attributes of the new row if this is a transient * record type or if last record has different id. + * Also we convert UNKNOWNOID to TEXTOID if needed. + * tupdesc may be changed */ - tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); - check_attributes(variable, tupdesc); - } + check_attributes(variable, &rec, tupdesc); - LastTypeId = tupType; + } insert_record(variable, rec); @@ -426,12 +862,13 @@ variable_update(PG_FUNCTION_ARGS) text *package_name; text *var_name; HeapTupleHeader rec; - Package *package; + Package *package; Variable *variable; TransObject *transObject; bool res; Oid tupType; int32 tupTypmod; + TupleDesc tupdesc = NULL; /* Checks */ CHECK_ARGS_FOR_NULL(); @@ -484,17 +921,13 @@ variable_update(PG_FUNCTION_ARGS) tupType = HeapTupleHeaderGetTypeId(rec); tupTypmod = HeapTupleHeaderGetTypMod(rec); - if (LastTypeId == RECORDOID || !OidIsValid(LastTypeId) || - LastTypeId != tupType) - { - TupleDesc tupdesc = NULL; - - tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); - check_attributes(variable, tupdesc); - ReleaseTupleDesc(tupdesc); - } - - LastTypeId = tupType; + tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); + /* + * Convert UNKNOWNOID to TEXTOID if needed + * tupdesc may be changed + */ + check_attributes(variable, &rec, tupdesc); + ReleaseTupleDesc(tupdesc); res = update_record(variable, rec); @@ -513,7 +946,7 @@ variable_delete(PG_FUNCTION_ARGS) Oid value_type; Datum value; bool value_is_null = PG_ARGISNULL(2); - Package *package; + Package *package; Variable *variable; TransObject *transObject; bool res; @@ -587,30 +1020,31 @@ 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); + VariableStatEntry *entry; record = &(GetActualValue(variable).record); - funcctx = SRF_FIRSTCALL_INIT(); - oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + oldcontext = MemoryContextSwitchTo(TopTransactionContext); funcctx->tuple_desc = record->tupdesc; @@ -618,6 +1052,18 @@ variable_select(PG_FUNCTION_ARGS) hash_seq_init(rstat, record->rhash); funcctx->user_fctx = rstat; + entry = palloc0(sizeof(VariableStatEntry)); + entry->hash = record->rhash; + entry->status = rstat; + entry->variable = variable; + entry->package = package; + entry->levels.level = GetCurrentTransactionNestLevel(); +#ifdef PGPRO_EE + entry->levels.atxlevel = getNestLevelATX(); +#endif + entry->user_fctx = &funcctx->user_fctx; + variables_stats = lcons((void *) entry, variables_stats); + MemoryContextSwitchTo(oldcontext); PG_FREE_IF_COPY(package_name, 0); PG_FREE_IF_COPY(var_name, 1); @@ -625,19 +1071,28 @@ variable_select(PG_FUNCTION_ARGS) funcctx = SRF_PERCALL_SETUP(); + if (funcctx->user_fctx == NULL) + { + /* + * VariableStatEntry was removed. For example, after call + * 'ROLLBACK TO SAVEPOINT ...' + */ + SRF_RETURN_DONE(funcctx); + } + /* Get next hash record */ rstat = (HASH_SEQ_STATUS *) funcctx->user_fctx; item = (HashRecordEntry *) hash_seq_search(rstat); if (item != NULL) { Assert(!HeapTupleHeaderHasExternal( - (HeapTupleHeader) DatumGetPointer(item->tuple))); + (HeapTupleHeader) DatumGetPointer(item->tuple))); SRF_RETURN_NEXT(funcctx, item->tuple); } else { - pfree(rstat); + remove_variables_status(&variables_stats, rstat); SRF_RETURN_DONE(funcctx); } } @@ -650,7 +1105,7 @@ variable_select_by_value(PG_FUNCTION_ARGS) Oid value_type; Datum value; bool value_is_null = PG_ARGISNULL(2); - Package *package; + Package *package; Variable *variable; HashRecordEntry *item; @@ -698,7 +1153,7 @@ variable_select_by_value(PG_FUNCTION_ARGS) if (found) { Assert(!HeapTupleHeaderHasExternal( - (HeapTupleHeader) DatumGetPointer(item->tuple))); + (HeapTupleHeader) DatumGetPointer(item->tuple))); PG_RETURN_DATUM(item->tuple); } @@ -727,7 +1182,7 @@ variable_select_by_values(PG_FUNCTION_ARGS) text *package_name; text *var_name; ArrayType *values; - Package *package; + Package *package; Variable *variable; MemoryContext oldcontext; @@ -792,7 +1247,7 @@ variable_select_by_values(PG_FUNCTION_ARGS) if (found) { Assert(!HeapTupleHeaderHasExternal( - (HeapTupleHeader) DatumGetPointer(item->tuple))); + (HeapTupleHeader) DatumGetPointer(item->tuple))); SRF_RETURN_NEXT(funcctx, item->tuple); } } @@ -810,7 +1265,7 @@ variable_exists(PG_FUNCTION_ARGS) { text *package_name; text *var_name; - Package *package; + Package *package; Variable *variable = NULL; char key[NAMEDATALEN]; bool found = false; @@ -872,11 +1327,11 @@ package_exists(PG_FUNCTION_ARGS) Datum remove_variable(PG_FUNCTION_ARGS) { - text *package_name; - text *var_name; - Package *package; - Variable *variable; - TransObject *transObject; + text *package_name; + text *var_name; + Package *package; + Variable *variable; + TransObject *transObject; CHECK_ARGS_FOR_NULL(); @@ -901,6 +1356,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 +1365,7 @@ remove_variable(PG_FUNCTION_ARGS) else removeObject(&variable->transObject, TRANS_VARIABLE); - resetVariablesCache(false); + resetVariablesCache(); PG_FREE_IF_COPY(package_name, 0); PG_FREE_IF_COPY(var_name, 1); @@ -923,7 +1379,7 @@ remove_variable(PG_FUNCTION_ARGS) Datum remove_package(PG_FUNCTION_ARGS) { - Package *package; + Package *package; text *package_name; if (PG_ARGISNULL(0)) @@ -934,9 +1390,17 @@ remove_package(PG_FUNCTION_ARGS) package_name = PG_GETARG_TEXT_PP(0); package = getPackage(package_name, true); + /* + * Need to remove variables before removing package because + * remove_variables_package() calls hash_seq_term() which uses + * "entry->status->hashp->frozen" but memory context of "hashp" + * for regular variables can be deleted in removePackageInternal(). + */ + remove_variables_package(&variables_stats, package); + removePackageInternal(package); - resetVariablesCache(true); + resetVariablesCache(); PG_FREE_IF_COPY(package_name, 0); PG_RETURN_VOID(); @@ -946,6 +1410,26 @@ static void removePackageInternal(Package *package) { TransObject *transObject; + Variable *variable; + HTAB *htab; + HASH_SEQ_STATUS vstat; + int i; + + /* Mark all the valid variables from package as 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) @@ -970,7 +1454,7 @@ removePackageInternal(Package *package) static bool isPackageEmpty(Package *package) { - int var_num = GetPackState(package)->trans_var_num; + int var_num = GetPackState(package)->trans_var_num; if (package->varHashRegular) var_num += hash_get_num_entries(package->varHashRegular); @@ -983,13 +1467,11 @@ 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; } /* @@ -999,13 +1481,21 @@ resetVariablesCache(bool with_package) Datum remove_packages(PG_FUNCTION_ARGS) { - Package *package; + Package *package; HASH_SEQ_STATUS pstat; /* There is no any packages and variables */ if (packagesHash == NULL) PG_RETURN_VOID(); + /* + * Need to remove variables before removing packages because + * remove_variables_all() calls hash_seq_term() which uses + * "entry->status->hashp->frozen" but memory context of "hashp" + * for regular variables can be deleted in removePackageInternal(). + */ + remove_variables_all(&variables_stats); + /* Get packages list */ hash_seq_init(&pstat, packagesHash); while ((package = (Package *) hash_seq_search(&pstat)) != NULL) @@ -1013,7 +1503,7 @@ remove_packages(PG_FUNCTION_ARGS) removePackageInternal(package); } - resetVariablesCache(true); + resetVariablesCache(); PG_RETURN_VOID(); } @@ -1060,7 +1550,7 @@ get_packages_and_variables(PG_FUNCTION_ARGS) */ if (packagesHash) { - Package *package; + Package *package; HASH_SEQ_STATUS pstat; int mRecs = NUMVARIABLES, nRecs = 0; @@ -1082,7 +1572,8 @@ get_packages_and_variables(PG_FUNCTION_ARGS) /* Get variables list for package */ for (i = 0; i < 2; i++) { - HTAB *htab = pack_htab(package, i); + HTAB *htab = pack_htab(package, i); + if (!htab) continue; hash_seq_init(&vstat, htab); @@ -1156,11 +1647,13 @@ getMemoryTotalSpace(MemoryContext context, int level, Size *totalspace) MemoryContext child; MemoryContextCounters totals; - AssertArg(MemoryContextIsValid(context)); + Assert(MemoryContextIsValid(context)); /* Examine the context itself */ memset(&totals, 0, sizeof(totals)); -#if PG_VERSION_NUM >= 110000 +#if PG_VERSION_NUM >= 140000 + (*context->methods->stats) (context, NULL, NULL, &totals, true); +#elif PG_VERSION_NUM >= 110000 (*context->methods->stats) (context, NULL, NULL, &totals); #else (*context->methods->stats) (context, level, false, &totals); @@ -1185,8 +1678,8 @@ get_packages_stats(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; MemoryContext oldcontext; - HASH_SEQ_STATUS *pstat; - Package *package; + HASH_SEQ_STATUS *rstat; + Package *package; if (SRF_IS_FIRSTCALL()) { @@ -1210,11 +1703,24 @@ get_packages_stats(PG_FUNCTION_ARGS) */ if (packagesHash) { - pstat = (HASH_SEQ_STATUS *) palloc0(sizeof(HASH_SEQ_STATUS)); + MemoryContext ctx; + PackageStatEntry *entry; + + 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; + entry = palloc0(sizeof(PackageStatEntry)); + entry->status = rstat; + entry->levels.level = GetCurrentTransactionNestLevel(); +#ifdef PGPRO_EE + entry->levels.atxlevel = getNestLevelATX(); +#endif + entry->user_fctx = &funcctx->user_fctx; + packages_stats = lcons((void *) entry, packages_stats); + MemoryContextSwitchTo(ctx); } else funcctx->user_fctx = NULL; @@ -1227,9 +1733,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]; @@ -1261,7 +1767,7 @@ get_packages_stats(PG_FUNCTION_ARGS) } else { - pfree(pstat); + remove_packages_status(&packages_stats, rstat); SRF_RETURN_DONE(funcctx); } } @@ -1303,7 +1809,11 @@ ensurePackagesHashExists(void) packagesHash = hash_create("Packages hash", NUMPACKAGES, &ctl, - HASH_ELEM | HASH_CONTEXT); + HASH_ELEM | +#if PG_VERSION_NUM >= 140000 + HASH_STRINGS | +#endif + HASH_CONTEXT); } /* @@ -1312,10 +1822,10 @@ ensurePackagesHashExists(void) static void makePackHTAB(Package *package, bool is_trans) { - HASHCTL ctl; - char hash_name[BUFSIZ]; - HTAB **htab; - MemoryContext *context; + HASHCTL ctl; + char hash_name[BUFSIZ]; + HTAB **htab; + MemoryContext *context; htab = is_trans ? &package->varHashTransact : &package->varHashRegular; context = is_trans ? &package->hctxTransact : &package->hctxRegular; @@ -1330,7 +1840,11 @@ makePackHTAB(Package *package, bool is_trans) ctl.hcxt = *context; *htab = hash_create(hash_name, NUMVARIABLES, &ctl, - HASH_ELEM | HASH_CONTEXT); + HASH_ELEM | +#if PG_VERSION_NUM >= 140000 + HASH_STRINGS | +#endif + HASH_CONTEXT); } static void @@ -1348,13 +1862,14 @@ initObjectHistory(TransObject *object, TransObjectType type) /* Initialize state */ state->is_valid = true; if (type == TRANS_PACKAGE) - ((PackState *)state)->trans_var_num = 0; + ((PackState *) state)->trans_var_num = 0; else { - Variable *variable = (Variable *) object; + Variable *variable = (Variable *) object; + if (!variable->is_record) { - VarState * varState = (VarState *) state; + VarState *varState = (VarState *) state; ScalarVar *scalar = &(varState->value.scalar); get_typlenbyval(variable->typid, &scalar->typlen, @@ -1367,7 +1882,7 @@ initObjectHistory(TransObject *object, TransObjectType type) static Package * getPackage(text *name, bool strict) { - Package *package; + Package *package; char key[NAMEDATALEN]; bool found; @@ -1380,8 +1895,8 @@ getPackage(text *name, bool strict) if (found && GetActualState(package)->is_valid) { - Assert (GetPackState(package)->trans_var_num + - numOfRegVars(package) > 0); + Assert(GetPackState(package)->trans_var_num + + numOfRegVars(package) > 0); return package; } } @@ -1389,7 +1904,7 @@ getPackage(text *name, bool strict) if (strict) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("unrecognized package \"%s\"", key))); + errmsg("unrecognized package \"%s\"", key))); return NULL; } @@ -1397,7 +1912,7 @@ getPackage(text *name, bool strict) static Package * createPackage(text *name, bool is_trans) { - Package *package; + Package *package; char key[NAMEDATALEN]; bool found; @@ -1446,6 +1961,9 @@ createPackage(text *name, bool is_trans) package->varHashTransact = NULL; package->hctxRegular = NULL; package->hctxTransact = NULL; +#ifdef PGPRO_EE + package->context = NULL; +#endif initObjectHistory(&package->transObject, TRANS_PACKAGE); } @@ -1489,19 +2007,19 @@ getVariableInternal(Package *package, text *name, Oid typid, bool is_record, if (variable->typid != typid) { char *var_type = DatumGetCString( - DirectFunctionCall1(regtypeout, - ObjectIdGetDatum(variable->typid))); + DirectFunctionCall1(regtypeout, + ObjectIdGetDatum(variable->typid))); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("variable \"%s\" requires \"%s\" value", + errmsg("variable \"%s\" requires \"%s\" value", key, var_type))); } if (variable->is_record != is_record) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("\"%s\" isn't a %s variable", + errmsg("\"%s\" isn't a %s variable", key, is_record ? "record" : "scalar"))); } if (!GetActualState(variable)->is_valid && strict) @@ -1548,7 +2066,7 @@ createVariableInternal(Package *package, text *name, Oid typid, bool is_record, if (found) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("variable \"%s\" already created as %sTRANSACTIONAL", + errmsg("variable \"%s\" already created as %sTRANSACTIONAL", key, is_transactional ? "NOT " : ""))); } @@ -1575,7 +2093,7 @@ createVariableInternal(Package *package, text *name, Oid typid, bool is_record, ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("\"%s\" isn't a %s variable", - key, is_record ? "record" : "scalar"))); + key, is_record ? "record" : "scalar"))); /* * Savepoint must be created when variable changed in current @@ -1596,6 +2114,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)) @@ -1606,8 +2125,8 @@ createVariableInternal(Package *package, text *name, Oid typid, bool is_record, } /* - * If the variable has been created or has just become valid, - * increment the counter of valid transactional variables. + * If the variable has been created or has just become valid, increment + * the counter of valid transactional variables. */ if (is_transactional && (!found || !GetActualState(variable)->is_valid)) @@ -1687,15 +2206,32 @@ removeState(TransObject *object, TransObjectType type, TransState *stateToDelete } /* Remove package or variable (either transactional or regular) */ -void +bool removeObject(TransObject *object, TransObjectType type) { bool found; HTAB *hash; - Package *package = NULL; + Package *package = NULL; if (type == TRANS_PACKAGE) { +#ifdef PGPRO_EE + PackageContext *context, + *next; + + /* + * Do not delete package inside autonomous transaction: it could be + * used in parent transaction. But we can delete package without any + * states: this means that the package was created in the current + * transaction. + */ + if (getNestLevelATX() > 0 && !dlist_is_empty(&object->states)) + { + GetActualState(object)->is_valid = false; + return false; + } +#endif + package = (Package *) object; /* Regular variables had already removed */ @@ -1703,24 +2239,48 @@ removeObject(TransObject *object, TransObjectType type) MemoryContextDelete(package->hctxRegular); if (package->hctxTransact) MemoryContextDelete(package->hctxTransact); +#ifdef PGPRO_EE + + /* + * Remove contexts with transactional part (stored when switching to + * ATX transaction) + */ + context = package->context; + while (context) + { + next = context->next; + if (context->hctxTransact) + MemoryContextDelete(context->hctxTransact); + pfree(context); + context = next; + } +#endif hash = packagesHash; } else { - Variable *var = (Variable *) object; + Variable *var = (Variable *) object; + package = var->package; hash = var->is_transactional ? - var->package->varHashTransact : - var->package->varHashRegular; + var->package->varHashTransact : + var->package->varHashRegular; } + /* + * Need to remove variables before removing state because + * remove_variables_variable() calls hash_seq_term() which uses + * "entry->status->hashp->frozen" but memory context of "hashp" + * for regular variables can be deleted in removeState() in freeValue(). + */ + /* Remove object from hash table */ + hash_search(hash, object->name, HASH_REMOVE, &found); + remove_variables_variable(&variables_stats, (Variable*)object); + /* Remove all object's states */ while (!dlist_is_empty(&object->states)) removeState(object, type, GetActualState(object)); - /* Remove object from hash table */ - hash_search(hash, object->name, HASH_REMOVE, &found); - /* Remove package if it became empty */ if (type == TRANS_VARIABLE && isPackageEmpty(package)) { @@ -1728,7 +2288,9 @@ removeObject(TransObject *object, TransObjectType type) GetActualState(&package->transObject)->is_valid = false; } - resetVariablesCache(true); + resetVariablesCache(); + + return true; } /* @@ -1745,7 +2307,7 @@ createSavepoint(TransObject *object, TransObjectType type) { newState = (TransState *) MemoryContextAllocZero(ModuleContext, sizeof(PackState)); - ((PackState *)newState)->trans_var_num = ((PackState *)prevState)->trans_var_num; + ((PackState *) newState)->trans_var_num = ((PackState *) prevState)->trans_var_num; } else { @@ -1772,10 +2334,17 @@ numOfRegVars(Package *package) * Rollback object to its previous state */ static void -rollbackSavepoint(TransObject *object, TransObjectType type) +rollbackSavepoint(TransObject *object, TransObjectType type, bool sub) { TransState *state; + /* Nothing to do here if trans object was removed already. */ + if (dlist_is_empty(&object->states)) + { + removeObject(object, type); + return; + } + state = GetActualState(object); removeState(object, type, state); @@ -1785,11 +2354,21 @@ rollbackSavepoint(TransObject *object, TransObjectType type) if (dlist_is_empty(&object->states)) { /* ...but object is a package and has some regular variables... */ - if (numOfRegVars((Package *)object) > 0) + if (numOfRegVars((Package *) object) > 0) { /* ...create a new state to make package valid. */ initObjectHistory(object, type); - GetActualState(object)->level = GetCurrentTransactionNestLevel() - 1; +#ifdef PGPRO_EE + /* + * Package inside autonomous transaction should not be detected + * as 'object has been changed in upper level' because in this + * case we will remove state in releaseSavepoint() but this + * state may be used in pgvRestoreContext(). So atxlevel should + * be 0 in case of rollback of autonomous transaction. + */ + GetActualState(object)->levels.atxlevel = sub ? getNestLevelATX() : 0; +#endif + GetActualState(object)->levels.level = GetCurrentTransactionNestLevel() - 1; if (!dlist_is_empty(changesStack)) addToChangesStackUpperLevel(object, type); } @@ -1797,11 +2376,12 @@ rollbackSavepoint(TransObject *object, TransObjectType type) /* ...or remove an object if it is no longer needed. */ removeObject(object, type); } + /* - * But if a package has more states, but hasn't valid variables, - * mark it as not valid or remove at top level transaction. - */ - else if (isPackageEmpty((Package *)object)) + * But if a package has more states, but hasn't valid variables, mark + * it as not valid or remove at top level transaction. + */ + else if (isPackageEmpty((Package *) object)) { if (dlist_is_empty(changesStack)) { @@ -1813,7 +2393,10 @@ rollbackSavepoint(TransObject *object, TransObjectType type) { createSavepoint(object, type); addToChangesStackUpperLevel(object, type); - GetActualState(object)->level = GetCurrentTransactionNestLevel() - 1; +#ifdef PGPRO_EE + GetActualState(object)->levels.atxlevel = getNestLevelATX(); +#endif + GetActualState(object)->levels.level = GetCurrentTransactionNestLevel() - 1; } GetActualState(object)->is_valid = false; } @@ -1830,41 +2413,66 @@ rollbackSavepoint(TransObject *object, TransObjectType type) * Remove previous state of object */ static void -releaseSavepoint(TransObject *object, TransObjectType type) +releaseSavepoint(TransObject *object, TransObjectType type, bool sub) { dlist_head *states = &object->states; - Assert(GetActualState(object)->level == GetCurrentTransactionNestLevel()); + + Assert(GetActualState(object)->levels.level == GetCurrentTransactionNestLevel()); +#ifdef PGPRO_EE + Assert(GetActualState(object)->levels.atxlevel == getNestLevelATX()); +#endif /* - * If the object is not valid and does not exist at a higher level - * (or if we complete the transaction) - remove object. + * If the object is not valid and does not exist at a higher level (or if + * we complete the transaction) - remove object. */ if (!GetActualState(object)->is_valid && - (!dlist_has_next(states, dlist_head_node(states)) || - dlist_is_empty(changesStack)) + (!dlist_has_next(states, dlist_head_node(states)) || + dlist_is_empty(changesStack)) ) { - removeObject(object, type); - return; + if (removeObject(object, type)) + return; } - /* If object has been changed in upper level - - * replace state of that level with the current one. */ + /* + * If object has been changed in upper level - replace state of that level + * with the current one. + */ if (isObjectChangedInUpperTrans(object)) { TransState *stateToDelete; dlist_node *nodeToDelete; + nodeToDelete = dlist_next_node(states, dlist_head_node(states)); stateToDelete = dlist_container(TransState, node, nodeToDelete); +#ifdef PGPRO_EE + /* + * We can not delete package state inside autonomous transaction + * because the state can be used in pgvRestoreContext(). + * Exception: the state was created within this autonomous transaction. + */ + Assert(type != TRANS_PACKAGE || getNestLevelATX() == 0 || + stateToDelete->levels.atxlevel == getNestLevelATX()); +#endif removeState(object, type, stateToDelete); } - /* If the object does not yet have a record in previous level changesStack, - * create it. */ + + /* + * If the object does not yet have a record in previous level + * changesStack, create it. + */ else if (!dlist_is_empty(changesStack)) addToChangesStackUpperLevel(object, type); /* Change subxact level due to release */ - GetActualState(object)->level--; + GetActualState(object)->levels.level--; + +#ifdef PGPRO_EE + /* Change ATX level due to finish autonomous transaction */ + if (!sub && getNestLevelATX() > 0) + GetActualState(object)->levels.atxlevel = 0; +#endif } static void @@ -1872,15 +2480,16 @@ addToChangesStackUpperLevel(TransObject *object, TransObjectType type) { ChangedObject *co_new; ChangesStackNode *csn; + /* - * Impossible to push in upper list existing node - * because it was created in another context + * Impossible to push in upper list existing node because it was created + * in another context */ csn = dlist_head_element(ChangesStackNode, node, changesStack); co_new = makeChangedObject(object, csn->ctx); dlist_push_head(type == TRANS_PACKAGE ? csn->changedPacksList : - csn->changedVarsList, - &co_new->node); + csn->changedVarsList, + &co_new->node); } /* @@ -1895,7 +2504,16 @@ isObjectChangedInCurrentTrans(TransObject *object) return false; state = GetActualState(object); - return state->level == GetCurrentTransactionNestLevel(); + return +#ifdef PGPRO_EE + + /* + * We should separate states with equal subxacts but with different ATX + * level + */ + state->levels.atxlevel == getNestLevelATX() && +#endif + state->levels.level == GetCurrentTransactionNestLevel(); } /* @@ -1909,13 +2527,34 @@ isObjectChangedInUpperTrans(TransObject *object) cur_state = GetActualState(object); if (dlist_has_next(&object->states, &cur_state->node) && - cur_state->level == GetCurrentTransactionNestLevel()) +#ifdef PGPRO_EE + cur_state->levels.atxlevel == getNestLevelATX() && +#endif + cur_state->levels.level == GetCurrentTransactionNestLevel()) { prev_state = dlist_container(TransState, node, cur_state->node.next); - return prev_state->level == GetCurrentTransactionNestLevel() - 1; + return +#ifdef PGPRO_EE + + /* + * We should separate states with equal subxacts but with different + * ATX level + */ + prev_state->levels.atxlevel == getNestLevelATX() && +#endif + prev_state->levels.level == GetCurrentTransactionNestLevel() - 1; } else - return cur_state->level == GetCurrentTransactionNestLevel() - 1; + return +#ifdef PGPRO_EE + + /* + * We should separate states with equal subxacts but with different + * ATX level + */ + cur_state->levels.atxlevel == getNestLevelATX() && +#endif + cur_state->levels.level == GetCurrentTransactionNestLevel() - 1; } /* @@ -2010,7 +2649,10 @@ addToChangesStack(TransObject *object, TransObjectType type) csn->changedVarsList, &co->node); /* Give this object current subxact level */ - GetActualState(object)->level = GetCurrentTransactionNestLevel(); + GetActualState(object)->levels.level = GetCurrentTransactionNestLevel(); +#ifdef PGPRO_EE + GetActualState(object)->levels.atxlevel = getNestLevelATX(); +#endif } } @@ -2024,63 +2666,62 @@ typedef enum Action ROLLBACK_TO_SAVEPOINT } Action; +/* + * Apply savepoint actions on list of variables or packages. + */ +static void +applyAction(Action action, TransObjectType type, dlist_head *list, bool sub) +{ + dlist_iter iter; + + dlist_foreach(iter, list) + { + ChangedObject *co = dlist_container(ChangedObject, node, iter.cur); + TransObject *object = co->object; + + switch (action) + { + case ROLLBACK_TO_SAVEPOINT: + rollbackSavepoint(object, type, sub); + break; + case RELEASE_SAVEPOINT: + + /* + * If package was removed in current transaction level mark + * var as removed. We do not check pack_state->level, because + * var cannot get in list of changes until pack is removed. + */ + if (type == TRANS_VARIABLE) + { + Variable *variable = (Variable *) object; + Package *package = variable->package; + + if (!GetActualState(package)->is_valid) + GetActualState(variable)->is_valid = false; + } + + releaseSavepoint(object, type, sub); + break; + } + } +} + /* * Iterate variables and packages from list of changes and * apply corresponding action on them */ static void -processChanges(Action action) +processChanges(Action action, bool sub) { ChangesStackNode *bottom_list; - int i; Assert(changesStack && changesStackContext); /* List removed from stack but we still can use it */ bottom_list = dlist_container(ChangesStackNode, node, dlist_pop_head_node(changesStack)); - /* - * i: - * 1 - manage variables - * 0 - manage packages - */ - for (i = 1; i > -1; i--) - { - dlist_iter iter; - - dlist_foreach(iter, i ? bottom_list->changedVarsList : - bottom_list->changedPacksList) - { - ChangedObject *co = dlist_container(ChangedObject, node, iter.cur); - TransObject *object = co->object; - - switch (action) - { - case ROLLBACK_TO_SAVEPOINT: - rollbackSavepoint(object, i ? TRANS_VARIABLE : TRANS_PACKAGE); - break; - case RELEASE_SAVEPOINT: - - /* - * If package was removed in current transaction level - * mark var as removed. We do not check pack_state->level, - * because var cannot get in list of changes until pack is - * removed. - */ - if (i) - { - Variable *variable = (Variable *) object; - Package *package = variable->package; - - if (!GetActualState(package)->is_valid) - GetActualState(variable)->is_valid = false; - } - - releaseSavepoint(object, i ? TRANS_VARIABLE : TRANS_PACKAGE); - break; - } - } - } + applyAction(action, TRANS_VARIABLE, bottom_list->changedVarsList, sub); + applyAction(action, TRANS_PACKAGE, bottom_list->changedPacksList, sub); /* Remove changes list of current level */ MemoryContextDelete(bottom_list->ctx); @@ -2096,21 +2737,209 @@ processChanges(Action action) MemoryContextDelete(ModuleContext); packagesHash = NULL; ModuleContext = NULL; - resetVariablesCache(true); + resetVariablesCache(); changesStack = NULL; changesStackContext = NULL; } } +/* + * ATX and connection pooling are not compatible with pg_variables. + */ static void compatibility_check(void) { + /* ---------------------- + * | Edition | ConnPool | + * ---------------------- + * | std 9.6 | no | + * | std 10 | no | + * | std 11 | no | + * | std 12 | no | + * | std 13 | no | + * | ee 9.6 | no | + * | ee 10 | no | + * | ee 11 | yes | + * | ee 12 | yes | + * | ee 13 | yes | + * ---------------------- + */ +#if defined(PGPRO_EE) && PG_VERSION_NUM >= 110000 + if (!IsDedicatedBackend) + { + freeStatsLists(); + elog(ERROR, "pg_variables extension is incompatible with connection pooling"); + } +#endif /* PGPRO_EE */ +} + #ifdef PGPRO_EE - if (getNestLevelATX() != 0) - elog(ERROR, "pg_variable extension is not compatible with autonomous transactions and connection pooling"); -#endif +/* + * At the beginning of ATX store the pg_variables's env into + * pgv_context. + */ +static void +pgvSaveContext(void) +{ + Package *package; + HASH_SEQ_STATUS pstat; + PgvContextStruct *sus = MemoryContextAlloc(CurTransactionContext, + sizeof(PgvContextStruct)); + + /* Save transactional variables for all packages (in packages structs) */ + if (packagesHash != NULL) + { + /* Get packages list */ + hash_seq_init(&pstat, packagesHash); + while ((package = (Package *) hash_seq_search(&pstat)) != NULL) + { + PackageContext *context = MemoryContextAlloc(ModuleContext, + sizeof(PackageContext)); + + context->next = package->context; + package->context = context; + + /* Save transactional variables in context */ + context->hctxTransact = package->hctxTransact; + context->varHashTransact = package->varHashTransact; + + /* + * Package structure has a transactional part 'transObject'. This + * part is used in asserts like + * Assert(GetActualState(object)->levels.level == + * GetCurrentTransactionNestLevel()) But this comparison is not + * valid for ATX transactions because + * 'CurrentTransactionState->nestingLevel' for each of new ATX + * level is starts with 1. We should save package state at start + * of ATX transaction and restore it at finish. No need do this + * for transactional variables (we clean them at end of ATX + * transaction) and regular variables (we modify them directly). + */ + context->state = GetActualState(&package->transObject); + + package->hctxTransact = NULL; + package->varHashTransact = NULL; + } + } + + /* Remove stats for all transactional variables */ + remove_variables_transactional(&variables_stats); + resetVariablesCache(); + + sus->changesStack = changesStack; + changesStack = NULL; + sus->changesStackContext = changesStackContext; + changesStackContext = NULL; + + sus->next = pgv_context; + pgv_context = sus; } +/* + * Restore pg_variables's env pointer from pgv_context. + */ +static void +pgvRestoreContext() +{ + Package *package; + HASH_SEQ_STATUS pstat; + PgvContextStruct *sus = pgv_context; + + resetVariablesCache(); + /* Delete changes stack for all transactional variables */ + if (changesStackContext) + { + MemoryContextDelete(changesStackContext); + changesStack = NULL; + changesStackContext = NULL; + } + /* We just finished ATX => need to free all hash_seq_search scans */ + freeStatsLists(); + + /* Restore transactional variables for all packages */ + if (packagesHash != NULL) + { + /* Get packages list */ + hash_seq_init(&pstat, packagesHash); + while ((package = (Package *) hash_seq_search(&pstat)) != NULL) + { + /* + * Delete context with transactional variables (they are no need + * outside ATX transaction) + */ + if (package->hctxTransact) + MemoryContextDelete(package->hctxTransact); + + /* We have stored context for this package? */ + if (package->context) + { + PackageContext *context = package->context; + PackageContext *next = context->next; + TransObject *object = &package->transObject; + TransState *state; + bool actual_valid_state; + + /* Restore transactional variables from context */ + package->hctxTransact = context->hctxTransact; + package->varHashTransact = context->varHashTransact; + + /* Save last actual state of package */ + actual_valid_state = GetActualState(object)->is_valid; + + /* Remove all package states, generated in ATX transaction */ + while ((state = GetActualState(object)) != context->state) + { + removeState(object, TRANS_PACKAGE, state); + if (dlist_is_empty(&object->states)) + elog(ERROR, "pg_variables extension can not find " + "transaction state for package"); + } + + /* + * Package could be removed in the autonomous transaction. So + * need to mark it as invalid. Or removed package could be + * re-created - so need to mark it as valid. + */ + if (actual_valid_state != GetActualState(object)->is_valid) + GetActualState(object)->is_valid = actual_valid_state; + + /* Mark empty package as deleted. */ + if (GetPackState(package)->trans_var_num + numOfRegVars(package) == 0) + GetActualState(object)->is_valid = false; + + pfree(context); + package->context = next; + } + else + { + /* Package was created in this autonomous transaction */ + package->hctxTransact = NULL; + package->varHashTransact = NULL; + + /* + * No need to remove package states: for just created package + * we have one state with level = 0 + */ + } + } + } + + /* + * 'sus' can be NULL in case pg_variables was not initialized at start of + * transaction + */ + if (sus) + { + /* Restore changes stack for previous level: */ + changesStack = sus->changesStack; + changesStackContext = sus->changesStackContext; + + pgv_context = sus->next; + pfree(sus); + } +} +#endif /* PGPRO_EE */ + /* * Intercept execution during subtransaction processing */ @@ -2118,6 +2947,8 @@ static void pgvSubTransCallback(SubXactEvent event, SubTransactionId mySubid, SubTransactionId parentSubid, void *arg) { + Levels levels; + if (changesStack) { switch (event) @@ -2127,15 +2958,22 @@ pgvSubTransCallback(SubXactEvent event, SubTransactionId mySubid, compatibility_check(); break; case SUBXACT_EVENT_COMMIT_SUB: - processChanges(RELEASE_SAVEPOINT); + processChanges(RELEASE_SAVEPOINT, true); break; case SUBXACT_EVENT_ABORT_SUB: - processChanges(ROLLBACK_TO_SAVEPOINT); + processChanges(ROLLBACK_TO_SAVEPOINT, true); break; case SUBXACT_EVENT_PRE_COMMIT_SUB: break; } } + + levels.level = GetCurrentTransactionNestLevel(); +#ifdef PGPRO_EE + levels.atxlevel = getNestLevelATX(); +#endif + remove_variables_level(&variables_stats, &levels); + remove_packages_level(&packages_stats, &levels); } /* @@ -2150,21 +2988,40 @@ pgvTransCallback(XactEvent event, void *arg) { case XACT_EVENT_PRE_COMMIT: compatibility_check(); - processChanges(RELEASE_SAVEPOINT); + processChanges(RELEASE_SAVEPOINT, false); break; case XACT_EVENT_ABORT: - processChanges(ROLLBACK_TO_SAVEPOINT); + processChanges(ROLLBACK_TO_SAVEPOINT, false); break; case XACT_EVENT_PARALLEL_PRE_COMMIT: - processChanges(RELEASE_SAVEPOINT); + processChanges(RELEASE_SAVEPOINT, false); break; case XACT_EVENT_PARALLEL_ABORT: - processChanges(ROLLBACK_TO_SAVEPOINT); + processChanges(ROLLBACK_TO_SAVEPOINT, false); break; default: break; } } + + if (event == XACT_EVENT_PRE_COMMIT || event == XACT_EVENT_ABORT) + freeStatsLists(); + +#ifdef PGPRO_EE + if (getNestLevelATX() > 0) + { + if (event == XACT_EVENT_START) + { /* on each ATX transaction start */ + pgvSaveContext(); + } + else if (event == XACT_EVENT_ABORT || event == XACT_EVENT_PARALLEL_ABORT || + event == XACT_EVENT_COMMIT || event == XACT_EVENT_PARALLEL_COMMIT || + event == XACT_EVENT_PREPARE) + { /* on each ATX transaction finish */ + pgvRestoreContext(); + } + } +#endif } /* @@ -2177,6 +3034,43 @@ variable_ExecutorEnd(QueryDesc *queryDesc) prev_ExecutorEnd(queryDesc); else standard_ExecutorEnd(queryDesc); + + freeStatsLists(); +} + +/* + * Free hash_seq_search scans + */ +static void +freeStatsLists(void) +{ + ListCell *cell; + + foreach(cell, variables_stats) + { + VariableStatEntry *entry = (VariableStatEntry *) lfirst(cell); + +#ifdef PGPRO_EE + hash_seq_term_all_levels(entry->status); +#else + hash_seq_term(entry->status); +#endif + } + + variables_stats = NIL; + + foreach(cell, packages_stats) + { + PackageStatEntry *entry = (PackageStatEntry *) lfirst(cell); + +#ifdef PGPRO_EE + hash_seq_term_all_levels(entry->status); +#else + hash_seq_term(entry->status); +#endif + } + + packages_stats = NIL; } /* @@ -2185,7 +3079,22 @@ variable_ExecutorEnd(QueryDesc *queryDesc) void _PG_init(void) { + DefineCustomBoolVariable("pg_variables.convert_unknownoid", + "Use \'TEXT\' format for all values of \'UNKNOWNOID\', default is true.", + NULL, + &convert_unknownoid, + true, + PGC_USERSET, + 0, /* FLAGS??? */ + NULL, + NULL, + NULL); + +#ifdef PGPRO_EE + PgproRegisterXactCallback(pgvTransCallback, NULL, XACT_EVENT_KIND_VANILLA | XACT_EVENT_KIND_ATX); +#else RegisterXactCallback(pgvTransCallback, NULL); +#endif RegisterSubXactCallback(pgvSubTransCallback, NULL); /* Install hooks. */ @@ -2193,6 +3102,7 @@ _PG_init(void) ExecutorEnd_hook = variable_ExecutorEnd; } +#if PG_VERSION_NUM < 150000 /* * Unregister callback function when module unloads */ @@ -2203,3 +3113,4 @@ _PG_fini(void) UnregisterSubXactCallback(pgvSubTransCallback, NULL); ExecutorEnd_hook = prev_ExecutorEnd; } +#endif diff --git a/pg_variables.h b/pg_variables.h old mode 100755 new mode 100644 index 3e233bd..6508e9f --- a/pg_variables.h +++ b/pg_variables.h @@ -3,7 +3,7 @@ * pg_variables.c * exported definitions for pg_variables.c * - * Copyright (c) 2015-2016, Postgres Professional + * Copyright (c) 2015-2022, Postgres Professional * *------------------------------------------------------------------------- */ @@ -16,6 +16,7 @@ #include "access/tupdesc.h" #include "datatype/timestamp.h" #include "utils/date.h" +#include "utils/guc.h" #include "utils/hsearch.h" #include "utils/numeric.h" #include "utils/jsonb.h" @@ -42,7 +43,7 @@ typedef struct RecordVar FmgrInfo hash_proc; /* Match function info */ FmgrInfo cmp_proc; -} RecordVar; +} RecordVar; typedef struct ScalarVar { @@ -52,19 +53,28 @@ typedef struct ScalarVar int16 typlen; } ScalarVar; +/* Object levels (subxact + atx) */ +typedef struct Levels +{ + int level; +#ifdef PGPRO_EE + int atxlevel; +#endif +} Levels; + /* State of TransObject instance */ typedef struct TransState { dlist_node node; bool is_valid; - int level; + Levels levels; } TransState; /* List node that stores one of the package's states */ typedef struct PackState { - TransState state; - unsigned long trans_var_num; /* Number of valid transactional variables */ + TransState state; + unsigned long trans_var_num; /* Number of valid transactional variables */ } PackState; /* List node that stores one of the variable's states */ @@ -76,14 +86,25 @@ typedef struct VarState ScalarVar scalar; RecordVar record; } value; -} VarState; +} VarState; /* Transactional object */ typedef struct TransObject { char name[NAMEDATALEN]; dlist_head states; -} TransObject; +} TransObject; + +#ifdef PGPRO_EE +/* Package context for save transactional part of package */ +typedef struct PackageContext +{ + HTAB *varHashTransact; + MemoryContext hctxTransact; + TransState *state; + struct PackageContext *next; +} PackageContext; +#endif /* Transactional package */ typedef struct Package @@ -94,7 +115,10 @@ typedef struct Package /* Memory context for package variables for easy memory release */ MemoryContext hctxRegular, hctxTransact; -} Package; +#ifdef PGPRO_EE + PackageContext *context; +#endif +} Package; /* Transactional variable */ typedef struct Variable @@ -102,6 +126,7 @@ typedef struct Variable TransObject transObject; Package *package; Oid typid; + /* * We need an additional flag to determine variable's type since we can * store record type DATUM within scalar variable @@ -113,7 +138,8 @@ typedef struct Variable * specified only when creating a variable. */ bool is_transactional; -} Variable; + bool is_deleted; +} Variable; typedef struct HashRecordKey { @@ -154,8 +180,12 @@ typedef struct ChangesStackNode MemoryContext ctx; } ChangesStackNode; +/* pg_variables.c */ +extern bool convert_unknownoid; + extern void init_record(RecordVar *record, TupleDesc tupdesc, Variable *variable); -extern void check_attributes(Variable *variable, TupleDesc tupdesc); +extern void check_attributes(Variable *variable, HeapTupleHeader *rec, TupleDesc tupdesc); +extern void coerce_unknown_first_record(TupleDesc *tupdesc, HeapTupleHeader * rec); extern void check_record_key(Variable *variable, Oid typid); extern void insert_record(Variable *variable, HeapTupleHeader tupleHeader); @@ -163,7 +193,7 @@ extern bool update_record(Variable *variable, HeapTupleHeader tupleHeader); extern bool delete_record(Variable *variable, Datum value, bool is_null); extern void insert_record_copy(RecordVar *dest_record, Datum src_tuple, Variable *variable); -extern void removeObject(TransObject *object, TransObjectType type); +extern bool removeObject(TransObject *object, TransObjectType type); #define GetActualState(object) \ (dlist_head_element(TransState, node, &((TransObject *) object)->states)) diff --git a/pg_variables_record.c b/pg_variables_record.c index beba0d6..dadb14f 100644 --- a/pg_variables_record.c +++ b/pg_variables_record.c @@ -3,7 +3,7 @@ * pg_variables_record.c * Functions to work with record types * - * Copyright (c) 2015-2016, Postgres Professional + * Copyright (c) 2015-2022, Postgres Professional * *------------------------------------------------------------------------- */ @@ -11,11 +11,25 @@ #include "funcapi.h" #include "access/htup_details.h" +/* + * See https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=8b94dab06617ef80a0901ab103ebd8754427ef + * + * Split tuptoaster.c into three separate files. + */ +#if PG_VERSION_NUM >= 130000 +#include "access/detoast.h" +#include "access/heaptoast.h" +#else #include "access/tuptoaster.h" +#endif + #include "catalog/pg_collation.h" #include "catalog/pg_type.h" +#include "parser/parse_type.h" #include "utils/builtins.h" #include "utils/datum.h" +#include "utils/lsyscache.h" +#include "utils/syscache.h" #include "utils/memutils.h" #include "utils/typcache.h" @@ -85,8 +99,8 @@ init_record(RecordVar *record, TupleDesc tupdesc, Variable *variable) /* * In case something went wrong, you need to roll back the changes before - * completing the transaction, because the variable may be regular - * and not present in list of changed vars. + * completing the transaction, because the variable may be regular and not + * present in list of changed vars. */ if (!OidIsValid(typentry->hash_proc_finfo.fn_oid)) { @@ -161,14 +175,118 @@ init_record(RecordVar *record, TupleDesc tupdesc, Variable *variable) MemoryContextSwitchTo(oldcxt); } +/* Check if any attributes of type UNKNOWNOID are in given tupdesc */ +static int +is_unknownoid_in_tupdesc(TupleDesc tupdesc) +{ + int i = 0; + for (i = 0; i < tupdesc->natts; i++) + { + Form_pg_attribute attr = GetTupleDescAttr(tupdesc, i); + + if (attr->atttypid == UNKNOWNOID) + return true; + + } + return false; +} + +/* Replace all attributes of type UNKNOWNOID to TEXTOID in given tupdesc */ +static void +coerce_unknown_rewrite_tupdesc(TupleDesc old_tupdesc, TupleDesc *return_tupdesc) +{ + int i; + + (*return_tupdesc) = CreateTupleDescCopy(old_tupdesc); + + for (i = 0; i < old_tupdesc->natts; i++) + { + Form_pg_attribute attr = GetTupleDescAttr(old_tupdesc, i); + + if (attr->atttypid == UNKNOWNOID) + { + FormData_pg_attribute new_attr = *attr; + + new_attr.atttypid = TEXTOID; + new_attr.attlen = -1; + new_attr.atttypmod = -1; + memcpy(TupleDescAttr((*return_tupdesc), i), &new_attr, sizeof(FormData_pg_attribute)); + } + } +} + +/* + * Deform tuple with old_tupdesc, coerce values of type UNKNOWNOID to TEXTOID, form tuple with new_tupdesc. + * new_tupdesc must have the same attributes as old_tupdesc except such of types UNKNOWNOID -- they must be of TEXTOID type + */ +static void +reconstruct_tuple(TupleDesc old_tupdesc, TupleDesc new_tupdesc, HeapTupleHeader *rec) +{ + HeapTupleData tuple; + HeapTuple newtup; + Datum *values = (Datum*)palloc(old_tupdesc->natts * sizeof(Datum)); + bool *isnull = (bool*)palloc(old_tupdesc->natts * sizeof(bool)); + Oid baseTypeId = UNKNOWNOID; + int32 baseTypeMod = -1; + int32 inputTypeMod = -1; + Type baseType = NULL; + int i; + + baseTypeId = getBaseTypeAndTypmod(TEXTOID, &baseTypeMod); + baseType = typeidType(baseTypeId); + /* Build a temporary HeapTuple control structure */ + tuple.t_len = HeapTupleHeaderGetDatumLength(*rec); + tuple.t_data = *rec; + heap_deform_tuple(&tuple, old_tupdesc, values, isnull); + + for (i = 0; i < old_tupdesc->natts; i++) + { + Form_pg_attribute attr = GetTupleDescAttr(old_tupdesc, i); + + if (attr->atttypid == UNKNOWNOID) + { + values[i] = stringTypeDatum(baseType, + DatumGetCString(values[i]), + inputTypeMod); + } + } + + newtup = heap_form_tuple(new_tupdesc, values, isnull); + (*rec) = newtup->t_data; + pfree(isnull); + pfree(values); + ReleaseSysCache(baseType); +} + +/* + * Used in pg_variables.c insert_record for coercing types in first record in variable. + * If there are UNKNOWNOIDs in tupdesc, rewrites it and reconstructs tuple with new tupdesc. + * Replaces given tupdesc with the new one. + */ +void +coerce_unknown_first_record(TupleDesc *tupdesc, HeapTupleHeader *rec) +{ + TupleDesc new_tupdesc = NULL; + + if (!is_unknownoid_in_tupdesc(*tupdesc)) + return; + + coerce_unknown_rewrite_tupdesc(*tupdesc, &new_tupdesc); + reconstruct_tuple(*tupdesc, new_tupdesc, rec); + + ReleaseTupleDesc(*tupdesc); + (*tupdesc) = new_tupdesc; +} + /* * New record structure should be the same as the first record. */ void -check_attributes(Variable *variable, TupleDesc tupdesc) +check_attributes(Variable *variable, HeapTupleHeader *rec, TupleDesc tupdesc) { int i; RecordVar *record; + bool unknowns = false; Assert(variable->typid == RECORDOID); @@ -177,8 +295,9 @@ check_attributes(Variable *variable, TupleDesc tupdesc) if (record->tupdesc->natts != tupdesc->natts) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("new record structure differs from variable \"%s\" " - "structure", GetName(variable)))); + errmsg("new record structure have %d attributes, but variable " + "\"%s\" structure have %d.", + tupdesc->natts, GetName(variable), record->tupdesc->natts))); /* Second, check columns type. */ for (i = 0; i < tupdesc->natts; i++) @@ -186,14 +305,29 @@ check_attributes(Variable *variable, TupleDesc tupdesc) Form_pg_attribute attr1 = GetTupleDescAttr(record->tupdesc, i), attr2 = GetTupleDescAttr(tupdesc, i); + /* + * For the sake of convenience, we consider all the unknown types are to be + * a text type. + */ + if (convert_unknownoid && (attr1->atttypid == TEXTOID) && (attr2->atttypid == UNKNOWNOID)) + { + unknowns = true; + continue; + } + if ((attr1->atttypid != attr2->atttypid) || (attr1->attndims != attr2->attndims) || (attr1->atttypmod != attr2->atttypmod)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("new record structure differs from variable \"%s\" " - "structure", GetName(variable)))); + errmsg("new record attribute type for attribute number %d " + "differs from variable \"%s\" structure.", + i + 1, GetName(variable)), + errhint("You may need explicit type casts."))); } + + if (unknowns) + reconstruct_tuple(tupdesc, record->tupdesc, rec); } /* @@ -229,7 +363,7 @@ copy_record_tuple(RecordVar *record, HeapTupleHeader tupleHeader) */ if (HeapTupleHeaderHasExternal(tupleHeader)) return toast_flatten_tuple_to_datum(tupleHeader, - HeapTupleHeaderGetDatumLength(tupleHeader), + HeapTupleHeaderGetDatumLength(tupleHeader), tupdesc); /* @@ -253,9 +387,9 @@ get_record_key(Datum tuple, TupleDesc tupdesc, bool *isnull) { HeapTupleHeader th = (HeapTupleHeader) DatumGetPointer(tuple); bool hasnulls = th->t_infomask & HEAP_HASNULL; - bits8 *bp = th->t_bits; /* ptr to null bitmap in tuple */ - char *tp; /* ptr to tuple data */ - long off; /* offset in tuple data */ + bits8 *bp = th->t_bits; /* ptr to null bitmap in tuple */ + char *tp; /* ptr to tuple data */ + long off; /* offset in tuple data */ int keyatt = 0; Form_pg_attribute attr = GetTupleDescAttr(tupdesc, keyatt); diff --git a/run_tests.sh b/run_tests.sh index 2574328..2d88e36 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # -# Copyright (c) 2018, Postgres Professional +# Copyright (c) 2018-2022, Postgres Professional # # supported levels: # * standard @@ -43,7 +43,7 @@ if [ "$LEVEL" = "hardcore" ] || \ # enable additional options ./configure \ CFLAGS='-O0 -ggdb3 -fno-omit-frame-pointer' \ - --enable-cassert \ + --enable-cassert --enable-debug \ --prefix=$CUSTOM_PG_BIN \ --quiet @@ -100,6 +100,7 @@ if [ "$LEVEL" = "nightmare" ]; then --time-stamp=yes \ --track-origins=yes \ --trace-children=yes \ + --trace-children-skip="/bin/*,/usr/bin/*,/lib/*" \ --gen-suppressions=all \ --suppressions=$CUSTOM_PG_SRC/src/tools/valgrind.supp \ --suppressions=$PWD/valgrind.supp \ diff --git a/sql/pg_variables.sql b/sql/pg_variables.sql index a9fdbbd..4b9a3d2 100644 --- a/sql/pg_variables.sql +++ b/sql/pg_variables.sql @@ -222,7 +222,7 @@ COMMIT; -- warning RESET client_min_messages; -- Clean memory after unsuccessful creation of a variable -SELECT pgv_insert('vars4', 'r1', row('str1', 'str1')); -- fail +SELECT pgv_insert('vars4', 'r1', row (('str1'::text, 'str1'::text))); -- fail SELECT package FROM pgv_stats() WHERE package = 'vars4'; -- Remove package if it is empty @@ -259,3 +259,27 @@ SELECT pgv_free(); SELECT pgv_exists('vars'); SELECT * FROM pgv_list() order by package, name; +-- Check insert of record with various amount of fields +CREATE TEMP TABLE foo(id int, t text); +INSERT INTO foo VALUES (0, 'str00'); + +SELECT pgv_insert('vars', 'r1', row(1, 'str1'::text, 'str2'::text)); +SELECT pgv_select('vars', 'r1'); +SELECT pgv_insert('vars', 'r1', foo) FROM foo; +SELECT pgv_select('vars', 'r1'); + +SELECT pgv_insert('vars', 'r2', row(1, 'str1')); -- ok, UNKNOWNOID of 'str1' converts to TEXTOID +SELECT pgv_insert('vars', 'r2', foo) FROM foo; -- ok +SELECT pgv_select('vars', 'r2'); + +SELECT pgv_insert('vars', 'r3', row(1, 'str1'::text)); +SELECT pgv_insert('vars', 'r3', foo) FROM foo; -- ok, no conversions +SELECT pgv_select('vars', 'r3'); + +SELECT pgv_insert('vars', 'r4', row(1, 2::int)); +SELECT pgv_insert('vars', 'r4', row(0, 'str1')); -- fail, UNKNOWNOID of 'str1' can't be converted to int +SELECT pgv_select('vars', 'r4'); + +SELECT pgv_insert('vars', 'r5', foo) FROM foo; -- types: int, text +SELECT pgv_insert('vars', 'r5', row(1, 'str1')); -- ok, UNKNOWNOID of 'str1' converts to TEXTOID +SELECT pgv_select('vars', 'r5'); diff --git a/sql/pg_variables_atx.sql b/sql/pg_variables_atx.sql new file mode 100644 index 0000000..396ea18 --- /dev/null +++ b/sql/pg_variables_atx.sql @@ -0,0 +1,196 @@ +select pgv_free(); + +------------------------------ +-- Non-transactional variables +------------------------------ +select pgv_set('vars', 'int1', 101); +begin; + select pgv_set('vars', 'int2', 102); + begin autonomous; + select pgv_set('vars', 'int3', 103); +-- 101, 102, 103: + select pgv_get('vars', 'int1', null::int), pgv_get('vars', 'int2', null::int), pgv_get('vars', 'int3', null::int); + select pgv_set('vars', 'int1', 1001); + begin autonomous; +-- 1001, 102, 103: + select pgv_get('vars', 'int1', null::int), pgv_get('vars', 'int2', null::int), pgv_get('vars', 'int3', null::int); + select pgv_set('vars', 'int2', 1002); + commit; + commit; +-- 1001, 1002, 103: + select pgv_get('vars', 'int1', null::int), pgv_get('vars', 'int2', null::int), pgv_get('vars', 'int3', null::int); + select pgv_set('vars', 'int3', 1003); +rollback; + +-- 1001, 1002, 1003: +select pgv_get('vars', 'int1', null::int), pgv_get('vars', 'int2', null::int), pgv_get('vars', 'int3', null::int); +-- vars:int1, vars:int2, vars:int3: +select * from pgv_list() order by package, name; + +select pgv_free(); + +-------------------------- +-- Transactional variables +-------------------------- +select pgv_set('vars', 'int1', 101, true); +begin; + select pgv_set('vars', 'int2', 102, true); + begin autonomous; + select pgv_set('vars', 'int3', 103, true); +-- 103: + select pgv_get('vars', 'int3', null::int); + begin autonomous; + select pgv_set('vars', 'int2', 1002, true); +-- 1002: + select pgv_get('vars', 'int2', null::int); + commit; +-- 103: + select pgv_get('vars', 'int3', null::int); + commit; + select pgv_set('vars', 'int1', 1001, true); +-- 1001: + select pgv_get('vars', 'int1', null::int); +-- 102: + select pgv_get('vars', 'int2', null::int); +rollback; +-- 101: +select pgv_get('vars', 'int1', null::int); +-- vars:int1: +select * from pgv_list() order by package, name; + +select pgv_free(); + +---------- +-- Cursors +---------- +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_insert('test', 'y', row (10::int, 20::int), true); +select pgv_insert('test', 'y', row (20::int, 30::int), true); +select pgv_insert('test', 'y', row (30::int, 40::int), true); + +begin; + declare r1_cur cursor for select pgv_select('test', 'x'); + begin autonomous; + begin autonomous; + begin autonomous; + begin autonomous; + begin autonomous; + select pgv_insert('test', 'z', row (11::int, 22::int), false); + select pgv_insert('test', 'z', row (22::int, 33::int), false); + select pgv_insert('test', 'z', row (33::int, 44::int), false); + + declare r11_cur cursor for select pgv_select('test', 'x'); +-- (1,2),(2,3): + fetch 2 in r11_cur; + declare r2_cur cursor for select pgv_select('test', 'y'); +-- correct error: unrecognized variable "y" + fetch 2 in r2_cur; + rollback; + rollback; + rollback; + rollback; + rollback; + declare r2_cur cursor for select pgv_select('test', 'y'); + declare r3_cur cursor for select pgv_select('test', 'z'); +-- (1,2),(2,3): + fetch 2 in r1_cur; +-- (10,20),(20,30): + fetch 2 in r2_cur; +-- (11,22),(22,33): + fetch 2 in r3_cur; +rollback; + +select pgv_free(); + +------------------------------------------ +-- Savepoint: rollback in main transaction +------------------------------------------ +begin; + select pgv_set('vars', 'trans_int', 101, true); +-- 101: + select pgv_get('vars', 'trans_int', null::int); + savepoint sp1; + select pgv_set('vars', 'trans_int', 102, true); +-- 102: + select pgv_get('vars', 'trans_int', null::int); + begin autonomous; + select pgv_set('vars', 'trans_int', 103, true); +-- 103: + select pgv_get('vars', 'trans_int', null::int); + commit; +-- 102: + select pgv_get('vars', 'trans_int', null::int); + rollback to sp1; +commit; +-- 101: +select pgv_get('vars', 'trans_int', null::int); + +select pgv_free(); + +------------------------------------------------ +-- Savepoint: rollback in autonomous transaction +------------------------------------------------ +begin; + select pgv_set('vars', 'trans_int', 1, true); + savepoint sp1; + select pgv_set('vars', 'trans_int', 100, true); + begin autonomous; + begin autonomous; + select pgv_set('vars1', 'int1', 2); + select pgv_set('vars1', 'trans_int1', 3, true); + savepoint sp2; + select pgv_set('vars1', 'trans_int1', 4, true); +-- 2 + select pgv_get('vars1', 'int1', null::int); +-- 4 + select pgv_get('vars1', 'trans_int1', null::int); + rollback to sp2; +-- 3 + select pgv_get('vars1', 'trans_int1', null::int); +-- vars1:int1, vars1:trans_int1: + select * from pgv_list() order by package, name; + select pgv_set('vars1', 'trans_int2', 4, true); + select pgv_set('vars1', 'trans_int3', 5, true); + select pgv_set('vars1', 'int2', 3); + rollback; + commit; + rollback to sp1; +-- 1 + select pgv_get('vars', 'trans_int', null::int); +-- 2 + select pgv_get('vars1', 'int1', null::int); +-- 3 + select pgv_get('vars1', 'int2', null::int); +-- vars:trans_int, vars1:int1, vars1:int2: + select * from pgv_list() order by package, name; +commit; + +select pgv_free(); + +------------------------------------------------------------ +-- Sample with (subxact inside ATX) == (subxact outside ATX) +------------------------------------------------------------ +select pgv_set('vars1', 'int1', 0); +select pgv_set('vars1', 'trans_int1', 0, true); +begin; + begin autonomous; + select pgv_set('vars1', 'int1', 1); + select pgv_set('vars1', 'trans_int1', 2, true); + savepoint sp2; + select pgv_set('vars1', 'trans_int1', 3, true); + rollback to sp2; +-- 2 + select pgv_get('vars1', 'trans_int1', null::int); + commit; +rollback; +-- vars1:int1, vars1:trans_int1 +select * from pgv_list() order by package, name; +-- 1 +select pgv_get('vars1', 'int1', null::int); +-- 0 +select pgv_get('vars1', 'trans_int1', null::int); + +select pgv_free(); diff --git a/sql/pg_variables_atx_pkg.sql b/sql/pg_variables_atx_pkg.sql new file mode 100644 index 0000000..49113d6 --- /dev/null +++ b/sql/pg_variables_atx_pkg.sql @@ -0,0 +1,238 @@ +-- +-- PGPRO-7614: function pgv_free() inside autonomous transaction +-- +SELECT pgv_free(); +-- +-- +-- Functions pgv_free() + pgv_get() inside autonomous transaction; package +-- with regular variable; autonomous transaction with commit. +-- +BEGIN; + SELECT pgv_set('vars', 'int1', 1); + BEGIN AUTONOMOUS; + SELECT pgv_get('vars', 'int1', null::int); + SELECT pgv_free(); +-- ERROR: unrecognized package "vars" + SELECT pgv_get('vars', 'int1', null::int); + COMMIT; +-- ERROR: unrecognized package "vars" + SELECT pgv_get('vars', 'int1', null::int); +ROLLBACK; +-- +-- +-- Function pgv_free() inside autonomous transaction; package with +-- regular variable; autonomous transaction with commit. +-- +BEGIN; + SELECT pgv_set('vars', 'int1', 1); + BEGIN AUTONOMOUS; + SELECT pgv_free(); + COMMIT; +-- ERROR: unrecognized package "vars" + SELECT pgv_get('vars', 'int1', null::int); +ROLLBACK; +-- +-- +-- Function pgv_free() inside autonomous transaction; package with +-- regular variable; autonomous transaction with rollback. +-- +BEGIN; + SELECT pgv_set('vars', 'int1', 1); + BEGIN AUTONOMOUS; + SELECT pgv_free(); + ROLLBACK; +-- ERROR: unrecognized package "vars" + SELECT pgv_get('vars', 'int1', null::int); +ROLLBACK; +-- +-- +-- Function pgv_free() inside autonomous transaction; package with +-- transactional variable; autonomous transaction with rollback. +-- +BEGIN; + SELECT pgv_set('vars', 'int1', 1, true); + BEGIN AUTONOMOUS; + SELECT pgv_free(); + ROLLBACK; + SELECT pgv_get('vars', 'int1', null::int); +ROLLBACK; +-- +-- +-- Function pgv_free() inside autonomous transaction; package with +-- transactional variable; autonomous transaction with commit. +-- +BEGIN; + SELECT pgv_set('vars', 'int1', 1, true); + BEGIN AUTONOMOUS; + SELECT pgv_free(); + COMMIT; +-- ERROR: unrecognized package "vars" + SELECT pgv_get('vars', 'int1', null::int); +ROLLBACK; +-- +-- +-- Function pgv_free() inside recursive autonomous transactions. +-- +BEGIN; + BEGIN AUTONOMOUS; + SELECT pgv_set('vars', 'int1', 1); + BEGIN AUTONOMOUS; + BEGIN AUTONOMOUS; + SELECT pgv_free(); + COMMIT; +-- ERROR: unrecognized package "vars" + SELECT pgv_get('vars', 'int1', null::int); + COMMIT; +-- ERROR: unrecognized package "vars" + SELECT pgv_get('vars', 'int1', null::int); + COMMIT; +ROLLBACK; +-- +-- +-- Function pgv_free() inside recursive autonomous transactions; +-- recreating the package after deletion with using regular +-- variable. +-- +BEGIN; + SELECT pgv_set('vars', 'int1', 1); + BEGIN AUTONOMOUS; + BEGIN AUTONOMOUS; + SELECT pgv_get('vars', 'int1', null::int); + BEGIN AUTONOMOUS; + SELECT pgv_free(); + COMMIT; +-- ERROR: unrecognized package "vars" + SELECT pgv_get('vars', 'int1', null::int); + COMMIT; + SELECT pgv_set('vars', 'int1', 2); + COMMIT; + SELECT pgv_get('vars', 'int1', null::int); +ROLLBACK; +-- +-- +-- Function pgv_free() inside recursive autonomous transactions; +-- recreating the package after deletion with using transactional +-- variable. +-- +BEGIN; + SELECT pgv_set('vars', 'int1', 1); + BEGIN AUTONOMOUS; + BEGIN AUTONOMOUS; + SELECT pgv_get('vars', 'int1', null::int); + BEGIN AUTONOMOUS; + SELECT pgv_free(); + COMMIT; +-- ERROR: unrecognized package "vars" + SELECT pgv_get('vars', 'int1', null::int); + COMMIT; + SELECT pgv_set('vars', 'int1', 2, true); + SELECT pgv_list(); + COMMIT; +-- ERROR: unrecognized package "vars" + SELECT pgv_get('vars', 'int1', null::int); +ROLLBACK; +-- +-- +-- Test for case: do not free hash_seq_search scans of parent transaction +-- at end of the autonomous transaction. +-- +BEGIN; + SELECT pgv_insert('test', 'x', row (1::int, 2::int), false); + SELECT pgv_insert('test', 'x', row (3::int, 4::int), false); + DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x'); +-- (1,2) + FETCH 1 IN r1_cur; + BEGIN AUTONOMOUS; + ROLLBACK; +-- (3,4) + FETCH 1 IN r1_cur; + SELECT pgv_remove('test', 'x'); +-- ERROR: unrecognized package "test" + FETCH 1 IN r1_cur; +ROLLBACK; +-- +-- +-- Test for case: pgv_free() should free hash_seq_search scans of all +-- (current ATX + parent) transactions. +-- +BEGIN; + SELECT pgv_insert('test', 'x', row (1::int, 2::int), false); + DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x'); +-- (1,2) + FETCH 1 IN r1_cur; + BEGIN AUTONOMOUS; + SELECT pgv_free(); + ROLLBACK; +-- ERROR: unrecognized package "test" + FETCH 1 IN r1_cur; +ROLLBACK; +-- +-- +-- Test for case: pgv_set() created a regular variable; rollback +-- removes package state and creates a new state to make package valid. +-- Commit of next autonomous transaction should not replace this new +-- state (this is not allowed for autonomous transaction). +-- +BEGIN; + BEGIN AUTONOMOUS; + SELECT pgv_set('vars', 'int1', 1); + ROLLBACK; + BEGIN AUTONOMOUS; + SELECT pgv_set('vars', 'int1', 2); + COMMIT; +ROLLBACK; +SELECT pgv_remove('vars', 'int1'); +-- +-- +-- Test for case: pgv_set() created a regular variable and package with +-- (atxlevel=1, level=1). COMMIT changes this level to (atxlevel=1, level=0). +-- In the next autonomous transaction (atxlevel=1, level=1) we erroneously +-- detect that the package changed in upper transaction and remove the +-- package state (this is not allowed for autonomous transaction). +-- +BEGIN; + BEGIN AUTONOMOUS; + SELECT pgv_set('vars', 'int1', 2); + COMMIT; + BEGIN AUTONOMOUS; + SELECT pgv_free(); + SELECT pgv_set('vars', 'int1', 2, true); + COMMIT; +ROLLBACK; +-- +-- +-- Test for case: pgv_set() created a regular variable and package with +-- (atxlevel=1, level=1). ROLLBACK changes this level to (atxlevel=0, level=0). +-- But ROLLBACK shouldn't change atxlevel in case rollback of sub-transaction. +-- +BEGIN; + BEGIN AUTONOMOUS; + SAVEPOINT sp1; + SELECT pgv_set('vars1', 'int1', 0); + ROLLBACK TO sp1; + COMMIT; +ROLLBACK; +SELECT pgv_remove('vars1', 'int1'); + +SELECT pgv_free(); +-- +-- +-- PGPRO-7856 +-- Test for case: we don't remove the package object without any variables at +-- the end of autonomous transaction but need to move the state of this object +-- to upper level. +-- +BEGIN; + BEGIN AUTONOMOUS; + SAVEPOINT sp1; + SELECT pgv_set('vars2', 'any1', 'variable exists'::text, true); + SELECT pgv_free(); + RELEASE sp1; + ROLLBACK; + + BEGIN AUTONOMOUS; + SAVEPOINT sp2; + SAVEPOINT sp3; + SELECT pgv_free(); + COMMIT; +ROLLBACK; diff --git a/sql/pg_variables_trans.sql b/sql/pg_variables_trans.sql index c4effd9..23b7afd 100644 --- a/sql/pg_variables_trans.sql +++ b/sql/pg_variables_trans.sql @@ -470,7 +470,7 @@ FROM generate_series(1,5) AS gs(n) WHERE 1.0/(n-3)<>0; SELECT pgv_insert('package', 'errs',row(1), true); -- Variable should not exists in case when error occurs during creation -SELECT pgv_insert('vars4', 'r1', row('str1', 'str1')); +SELECT pgv_insert('vars4', 'r1', row (('str1'::text, 'str1'::text))); SELECT pgv_select('vars4', 'r1', 0); -- If variable created and removed in same transaction level, @@ -534,3 +534,660 @@ ROLLBACK; SELECT package FROM pgv_stats() ORDER BY package; SELECT pgv_free(); + +-- 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 (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 (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_remove (package) +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'); +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', '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(); + +-- 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; +CLOSE r1_cur; +SELECT pgv_remove('test', 'x'); +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; +CLOSE r1_cur; +SELECT pgv_remove('test'); +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; +CLOSE r1_cur; +SELECT pgv_free(); +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; +--CLOSE r1_cur; +--SELECT pgv_remove('test', 'x'); +--COMMIT; +SELECT pgv_free(); +--- +--- 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; +CLOSE 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; +CLOSE 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(); +-- 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; + +--- +--- Test cases for pgv_stats +--- +--- Amount of allocated memory may vary from version to version, as well as from +--- platform to platform. Moreover, postgres versions less than 90600 always +--- show zero allocated memory. So, it's much easier to check if allocated +--- memory size is multiple of 8k since we use ALLOCSET_DEFAULT_INITSIZE +--- (see memutils.h), insted of creating multiple outputs files. +--- +CREATE TEMP VIEW pgv_stats_view(pack, mem_mult) AS + SELECT package, allocated_memory % 8192 as allocated_multiple_8192 + FROM pgv_stats() + ORDER BY 1; + +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 pack, mem_mult FROM pgv_stats_view; +DECLARE r2_cur CURSOR FOR SELECT pack, mem_mult FROM pgv_stats_view; +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 pack, mem_mult FROM pgv_stats_view; +DECLARE r2_cur CURSOR FOR SELECT pack, mem_mult FROM pgv_stats_view; +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 pack, mem_mult FROM pgv_stats_view; +DECLARE r2_cur CURSOR FOR SELECT pack, mem_mult FROM pgv_stats_view; +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 pack, mem_mult FROM pgv_stats_view; +DECLARE r2_cur CURSOR FOR SELECT pack, mem_mult FROM pgv_stats_view; +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(); + +--- +--- Some special cases +--- +-- 1 +BEGIN; +SAVEPOINT comm2; +SELECT pgv_insert('test', 'x1', ROW (2::float, 1::float), TRUE); +DECLARE r1_cur CURSOR FOR SELECT pack, mem_mult FROM pgv_stats_view; +DECLARE r2_cur CURSOR FOR SELECT pack, mem_mult FROM pgv_stats_view; +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 pack, mem_mult FROM pgv_stats_view; +DECLARE r2_cur CURSOR FOR SELECT pack, mem_mult FROM pgv_stats_view; +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 pack, mem_mult FROM pgv_stats_view; +DECLARE r2_cur CURSOR FOR SELECT pack, mem_mult FROM pgv_stats_view; +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 pack, mem_mult FROM pgv_stats_view; +DECLARE r2_cur CURSOR FOR SELECT pack, mem_mult FROM pgv_stats_view; +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 pack, mem_mult FROM pgv_stats_view; +DECLARE r2_cur CURSOR FOR SELECT pack, mem_mult FROM pgv_stats_view; +FETCH 1 in r1_cur; +FETCH 1 in r2_cur; +SAVEPOINT comm2; +COMMIT; + +DROP VIEW pgv_stats_view; +SELECT pgv_free(); + +--- +--- Test case for issue #32 [PGPRO-4456] +--- +CREATE TEMP TABLE tab (id int, t varchar); +INSERT INTO tab VALUES (0, 'str00'); + +SELECT pgv_insert('vars', 'r1', row(1, 'str1', 'str2')); +SELECT pgv_insert('vars', 'a', tab) FROM tab; +SELECT pgv_insert('vars', 'r1', tab) FROM tab; +SELECT pgv_select('vars', 'r1'); + +SELECT pgv_insert('vars', 'r2', row(1, 'str1'::varchar)); +SELECT pgv_insert('vars', 'b', tab) FROM tab; +SELECT pgv_insert('vars', 'r2', tab) FROM tab; +SELECT pgv_select('vars', 'r2'); + +SELECT pgv_free(); + + +-- +-- Test case for issue #38 [PGPRO-4676] +-- +SELECT pgv_insert('test', 'x5', ROW ((2::int, 1::int)), TRUE); + +-- +-- Test case for PGPRO-7614: crash by using cursor after rollback of cursor +-- creation. +-- +BEGIN; + SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), true); + DECLARE r1_cur CURSOR FOR SELECT pgv_select('test', 'x'); + SAVEPOINT sp1; + FETCH 1 in r1_cur; + ROLLBACK TO SAVEPOINT sp1; + FETCH 1 in r1_cur; +ROLLBACK; + +BEGIN; + SELECT pgv_insert('test', 'x', ROW (1::int, 2::int), TRUE); + DECLARE r1_cur CURSOR FOR SELECT pgv_stats(); + SAVEPOINT sp1; + FETCH 1 in r1_cur; + ROLLBACK TO SAVEPOINT sp1; + FETCH 1 in r1_cur; +ROLLBACK;