diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..ce3c9e6 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +*.gcno +*.gcda +*.gcov +*.so +*.o diff --git a/.gitignore b/.gitignore index cbf8d79..03329b5 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ objfiles.txt *.gcov.out lcov.info coverage/ +results/ *.vcproj *.vcxproj win32ver.rc @@ -37,4 +38,8 @@ lib*.pc /pgsql.sln.cache /Debug/ /Release/ +/log/ /tmp_install/ +Dockerfile +pg_variables--1.1.sql +pg_variables--1.2.sql diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..80d5de7 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,50 @@ +os: linux + +dist: focal + +language: c + +services: + - docker + +install: + - ./mk_dockerfile.sh + - docker-compose build + +script: + - docker-compose run $(bash <(curl -s https://codecov.io/env)) tests + +notifications: + email: + on_success: change + 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 + +# 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 new file mode 100644 index 0000000..2792b6e --- /dev/null +++ b/Dockerfile.tmpl @@ -0,0 +1,36 @@ +FROM postgres:${PG_VERSION}-alpine + +# Install dependencies +RUN apk add --no-cache \ + openssl curl \ + perl perl-ipc-run \ + make musl-dev gcc bison flex coreutils \ + zlib-dev libedit-dev linux-headers \ + pkgconf icu-dev clang clang15 clang-analyzer; + +# Install fresh valgrind +RUN apk add valgrind \ + --update-cache \ + --repository http://dl-3.alpinelinux.org/alpine/edge/main; + +# Environment +ENV LANG=C.UTF-8 PGDATA=/pg/data + +# Make directories +RUN mkdir -p ${PGDATA} && \ + mkdir -p /pg/testdir + +# Grant privileges +RUN chown postgres:postgres ${PGDATA} && \ + chown postgres:postgres /pg/testdir && \ + chmod a+rwx /usr/local/lib/postgresql && \ + chmod a+rwx /usr/local/share/postgresql/extension + +COPY run_tests.sh /run.sh +RUN chmod 755 /run.sh + +ADD . /pg/testdir +WORKDIR /pg/testdir + +USER postgres +ENTRYPOINT LEVEL=${LEVEL} /run.sh diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9c8fe9d --- /dev/null +++ b/LICENSE @@ -0,0 +1,11 @@ +pg_variables is released under the PostgreSQL License, a liberal Open Source license, similar to the BSD or MIT licenses. + +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 + +Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies. + +IN NO EVENT SHALL POSTGRES PROFESSIONAL BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF POSTGRES PROFESSIONAL HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +POSTGRES PROFESSIONAL SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND POSTGRES PROFESSIONAL HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. diff --git a/Makefile b/Makefile index 49592e0..7253e93 100644 --- a/Makefile +++ b/Makefile @@ -4,10 +4,14 @@ MODULE_big = pg_variables OBJS = pg_variables.o pg_variables_record.o $(WIN32RES) EXTENSION = pg_variables -DATA = pg_variables--1.0.sql +EXTVERSION = 1.2 +DATA = pg_variables--1.0.sql pg_variables--1.0--1.1.sql pg_variables--1.1--1.2.sql +DATA_built = $(EXTENSION)--$(EXTVERSION).sql + PGFILEDESC = "pg_variables - sessional variables" -REGRESS = pg_variables pg_variables_any +REGRESS = pg_variables pg_variables_any pg_variables_trans pg_variables_atx \ + pg_variables_atx_pkg ifdef USE_PGXS PG_CONFIG = pg_config @@ -19,3 +23,6 @@ top_builddir = ../.. include $(top_builddir)/src/Makefile.global include $(top_srcdir)/contrib/contrib-global.mk endif + +$(EXTENSION)--$(EXTVERSION).sql: $(DATA) + cat $^ > $@ diff --git a/README.md b/README.md index ee9d34a..294ed37 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,16 @@ # pg_variables - session variables with various types +[![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) + ## Introduction The **pg_variables** module provides functions to work with variables of various types. Created variables live only in the current user session. - -Note that the module does **not support transactions and savepoints**. For -example: - +By default, created variables are not transactional (i.e. they are not affected +by `BEGIN`, `COMMIT` or `ROLLBACK` statements). This, however, is customizable +by argument `is_transactional` of `pgv_set()`: ```sql SELECT pgv_set('vars', 'int1', 101); BEGIN; @@ -15,16 +18,33 @@ SELECT pgv_set('vars', 'int2', 102); ROLLBACK; SELECT * FROM pgv_list() order by package, name; - package | name ----------+------ - vars | int1 - vars | int2 -(2 rows) + package | name | is_transactional +---------+------+------------------ + vars | int1 | f + vars | int2 | f ``` +But if variable created with flag **is_transactional**: +```sql +BEGIN; +SELECT pgv_set('vars', 'trans_int', 101, true); +SAVEPOINT sp1; +SELECT pgv_set('vars', 'trans_int', 102, true); +ROLLBACK TO sp1; +COMMIT; +SELECT pgv_get('vars', 'trans_int', NULL::int); + pgv_get +--------- + 101 +``` + +You can aggregate variables into packages. This is done to be able to have +variables with different names or to quickly remove the whole batch of +variables. If the package becomes empty, it is automatically deleted. + ## License -This module available under the same license as +This module available under the [license](LICENSE) similar to [PostgreSQL](http://www.postgresql.org/about/licence/). ## Installation @@ -40,7 +60,7 @@ Typical installation procedure may look like this: ## Module functions The functions provided by the **pg_variables** module are shown in the tables -below. The module supports the following scalar and record types. +below. To use **pgv_get()** function required package and variable must exists. It is necessary to set variable with **pgv_set()** function to use **pgv_get()** @@ -72,58 +92,87 @@ ERROR: variable "int1" requires "integer" value Function | Returns -------- | ------- -`pgv_set(package text, name text, value anynonarray)` | `void` +`pgv_set(package text, name text, value anynonarray, is_transactional bool default false)` | `void` `pgv_get(package text, name text, var_type anynonarray, strict bool default true)` | `anynonarray` +## Array variables functions + +Function | Returns +-------- | ------- +`pgv_set(package text, name text, value anyarray, is_transactional bool default false)` | `void` +`pgv_get(package text, name text, var_type anyarray, strict bool default true)` | `anyarray` + +`pgv_set` arguments: +- `package` - name of the package, it will be created if it doesn't exist. +- `name` - name of the variable, it will be created if it doesn't exist. +`pgv_set` fails if the variable already exists and its transactionality doesn't +match `is_transactional` argument. +- `value` - new value for the variable. `pgv_set` fails if the variable already +exists and its type doesn't match new value's type. +- `is_transactional` - transactionality of the newly created variable, by +default it is false. + +`pgv_get` arguments: +- `package` - name of the existing package. If the package doesn't exist result +depends on `strict` argument: if it is false then `pgv_get` returns NULL +otherwise it fails. +- `name` - name of the the existing variable. If the variable doesn't exist +result depends on `strict` argument: if it is false then `pgv_get` returns NULL +otherwise it fails. +- `var_type` - type of the existing variable. It is necessary to pass it to get +correct return type. +- `strict` - pass false if `pgv_get` shouldn't raise an error if a variable or a +package didn't created before, by default it is true. + ## **Deprecated** scalar variables functions ### Integer variables Function | Returns -------- | ------- -`pgv_set_int(package text, name text, value int)` | `void` +`pgv_set_int(package text, name text, value int, is_transactional bool default false)` | `void` `pgv_get_int(package text, name text, strict bool default true)` | `int` ### Text variables Function | Returns -------- | ------- -`pgv_set_text(package text, name text, value text)` | `void` +`pgv_set_text(package text, name text, value text, is_transactional bool default false)` | `void` `pgv_get_text(package text, name text, strict bool default true)` | `text` ### Numeric variables Function | Returns -------- | ------- -`pgv_set_numeric(package text, name text, value numeric)` | `void` +`pgv_set_numeric(package text, name text, value numeric, is_transactional bool default false)` | `void` `pgv_get_numeric(package text, name text, strict bool default true)` | `numeric` ### Timestamp variables Function | Returns -------- | ------- -`pgv_set_timestamp(package text, name text, value timestamp)` | `void` +`pgv_set_timestamp(package text, name text, value timestamp, is_transactional bool default false)` | `void` `pgv_get_timestamp(package text, name text, strict bool default true)` | `timestamp` ### Timestamp with timezone variables Function | Returns -------- | ------- -`pgv_set_timestamptz(package text, name text, value timestamptz)` | `void` +`pgv_set_timestamptz(package text, name text, value timestamptz, is_transactional bool default false)` | `void` `pgv_get_timestamptz(package text, name text, strict bool default true)` | `timestamptz` ### Date variables Function | Returns -------- | ------- -`pgv_set_date(package text, name text, value date)` | `void` +`pgv_set_date(package text, name text, value date, is_transactional bool default false)` | `void` `pgv_get_date(package text, name text, strict bool default true)` | `date` ### Jsonb variables Function | Returns -------- | ------- -`pgv_set_jsonb(package text, name text, value jsonb)` | `void` +`pgv_set_jsonb(package text, name text, value jsonb, is_transactional bool default false)` | `void` `pgv_get_jsonb(package text, name text, strict bool default true)` | `jsonb` ## Record variables functions @@ -142,7 +191,7 @@ raised. Function | Returns | Description -------- | ------- | ----------- -`pgv_insert(package text, name text, r record)` | `void` | Inserts a record to the variable collection. If package and variable do not exists they will be created. The first column of **r** will be a primary key. If exists a record with the same primary key the error will be raised. If this variable collection has other structure the error will be raised. +`pgv_insert(package text, name text, r record, is_transactional bool default false)` | `void` | Inserts a record to the variable collection. If package and variable do not exists they will be created. The first column of **r** will be a primary key. If exists a record with the same primary key the error will be raised. If this variable collection has other structure the error will be raised. `pgv_update(package text, name text, r record)` | `boolean` | Updates a record with the corresponding primary key (the first column of **r** is a primary key). Returns **true** if a record was found. If this variable collection has other structure the error will be raised. `pgv_delete(package text, name text, value anynonarray)` | `boolean` | Deletes a record with the corresponding primary key (the first column of **r** is a primary key). Returns **true** if a record was found. `pgv_select(package text, name text)` | `set of record` | Returns the variable collection records. @@ -158,30 +207,35 @@ Function | Returns | Description `pgv_remove(package text, name text)` | `void` | Removes the variable with the corresponding name. Required package and variable must exists, otherwise the error will be raised. `pgv_remove(package text)` | `void` | Removes the package and all package variables with the corresponding name. Required package must exists, otherwise the error will be raised. `pgv_free()` | `void` | Removes all packages and variables. -`pgv_list()` | `table(package text, name text)` | Returns set of records of assigned packages and variables. -`pgv_stats()` | `table(package text, used_memory bigint)` | Returns list of assigned packages and used memory in bytes. +`pgv_list()` | `table(package text, name text, is_transactional bool)` | Returns set of records of assigned packages and variables. +`pgv_stats()` | `table(package text, allocated_memory bigint)` | Returns list of assigned packages and used memory in bytes. Note that **pgv_stats()** works only with the PostgreSQL 9.6 and newer. ## Examples -It is easy to use functions to work with scalar variables: +It is easy to use functions to work with scalar and array variables: ```sql SELECT pgv_set('vars', 'int1', 101); -SELECT pgv_set('vars', 'int2', 102); +SELECT pgv_set('vars', 'text1', 'text variable'::text); SELECT pgv_get('vars', 'int1', NULL::int); - pgv_get_int + pgv_get_int ------------- 101 -(1 row) -SELECT pgv_get('vars', 'int2', NULL::int); - pgv_get_int -------------- - 102 -(1 row) +SELECT SELECT pgv_get('vars', 'text1', NULL::text); + pgv_get +--------------- + text variable + +SELECT pgv_set('vars', 'arr1', '{101,102}'::int[]); + +SELECT pgv_get('vars', 'arr1', NULL::int[]); + pgv_get +----------- + {101,102} ``` Let's assume we have a **tab** table: @@ -201,26 +255,22 @@ SELECT pgv_select('vars', 'r1'); ------------ (1,str11) (0,str00) -(2 rows) SELECT pgv_select('vars', 'r1', 1); pgv_select ------------ (1,str11) -(1 row) SELECT pgv_select('vars', 'r1', 0); pgv_select ------------ (0,str00) -(1 row) SELECT pgv_select('vars', 'r1', ARRAY[1, 0]); pgv_select ------------ (1,str11) (0,str00) -(2 rows) SELECT pgv_delete('vars', 'r1', 1); @@ -228,32 +278,30 @@ SELECT pgv_select('vars', 'r1'); pgv_select ------------ (0,str00) -(1 row) ``` You can list packages and variables: ```sql SELECT * FROM pgv_list() order by package, name; - package | name ----------+------ - vars | int1 - vars | int2 - vars | r1 -(3 rows) + package | name | is_transactional +---------+-------+------------------ + vars | arr1 | f + vars | int1 | f + vars | r1 | f + vars | text1 | f ``` And get used memory in bytes: ```sql SELECT * FROM pgv_stats() order by package; - package | used_memory ----------+------------- - vars | 16736 -(1 row) + package | allocated_memory +---------+------------------ + vars | 49152 ``` -You can delete variables or hole packages: +You can delete variables or whole packages: ```sql SELECT pgv_remove('vars', 'int1'); @@ -264,3 +312,95 @@ You can delete all packages and variables: ```sql SELECT pgv_free(); ``` + +If you want variables with support of transactions and savepoints, you should +add flag `is_transactional = true` as the last argument in functions `pgv_set()` +or `pgv_insert()`. +Following use cases describe behavior of transactional variables: + +```sql +SELECT pgv_set('pack', 'var_text', 'before transaction block'::text, true); +BEGIN; +SELECT pgv_set('pack', 'var_text', 'before savepoint'::text, true); +SAVEPOINT sp1; +SELECT pgv_set('pack', 'var_text', 'savepoint sp1'::text, true); +SAVEPOINT sp2; +SELECT pgv_set('pack', 'var_text', 'savepoint sp2'::text, true); +RELEASE sp2; +SELECT pgv_get('pack', 'var_text', NULL::text); + pgv_get +--------------- + savepoint sp2 + +ROLLBACK TO sp1; +SELECT pgv_get('pack', 'var_text', NULL::text); + pgv_get +------------------ + before savepoint + +ROLLBACK; +SELECT pgv_get('pack', 'var_text', NULL::text); + pgv_get +-------------------------- + before transaction block +``` + +If you create a transactional variable after `BEGIN` or `SAVEPOINT` statements +and then rollback to previous state - variable will not be exist: + +```sql +BEGIN; +SAVEPOINT sp1; +SAVEPOINT sp2; +SELECT pgv_set('pack', 'var_int', 122, true); +RELEASE SAVEPOINT sp2; +SELECT pgv_get('pack', 'var_int', NULL::int); +pgv_get +--------- + 122 + +ROLLBACK TO sp1; +SELECT pgv_get('pack','var_int', NULL::int); +ERROR: unrecognized variable "var_int" +COMMIT; +``` + +You can undo removal of a transactional variable by `ROLLBACK`, but if you remove +a whole package, all regular variables will be removed permanently: + +```sql +SELECT pgv_set('pack', 'var_reg', 123); +SELECT pgv_set('pack', 'var_trans', 456, true); +BEGIN; +SELECT pgv_free(); +SELECT * FROM pgv_list(); + package | name | is_transactional +---------+------+------------------ +(0 rows) + +-- Memory is allocated yet +SELECT * FROM pgv_stats(); + package | allocated_memory +---------+------------------ + pack | 24576 + +ROLLBACK; +SELECT * FROM pgv_list(); + package | name | is_transactional +---------+-----------+------------------ + pack | var_trans | t +``` + +If you created transactional variable once, you should use flag `is_transactional` +every time when you want to change variable value by functions `pgv_set()`, +`pgv_insert()` and deprecated setters (i.e. `pgv_set_int()`). If you try to +change this option, you'll get an error: + +```sql +SELECT pgv_insert('pack', 'var_record', row(123::int, 'text'::text), true); + +SELECT pgv_insert('pack', 'var_record', row(456::int, 'another text'::text)); +ERROR: variable "var_record" already created as TRANSACTIONAL +``` + +Functions `pgv_update()` and `pgv_delete()` do not require this flag. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..471ab77 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,2 @@ +tests: + build: . diff --git a/expected/pg_variables.out b/expected/pg_variables.out index 326b3f1..1e5bee6 100644 --- a/expected/pg_variables.out +++ b/expected/pg_variables.out @@ -1,4 +1,19 @@ CREATE EXTENSION pg_variables; +-- Test packages - sanity checks +SELECT pgv_free(); + pgv_free +---------- + +(1 row) + +SELECT pgv_exists(NULL); -- fail +ERROR: package name can not be NULL +SELECT pgv_remove(NULL); -- fail +ERROR: package name can not be NULL +SELECT pgv_remove('vars'); -- fail +ERROR: unrecognized package "vars" +SELECT pgv_exists('vars111111111111111111111111111111111111111111111111111111111111'); -- fail +ERROR: name "vars111111111111111111111111111111111111111111111111111111111111" is too long -- Integer variables SELECT pgv_get_int('vars', 'int1'); ERROR: unrecognized package "vars" @@ -548,11 +563,51 @@ 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 +SELECT pgv_insert('vars3', 'r2', row(1, 'str1', 'str2')); + pgv_insert +------------ + +(1 row) + +SELECT pgv_update('vars3', 'r1', row(3, 'str22'::varchar)); + pgv_update +------------ + f +(1 row) + +SELECT pgv_update('vars4', 'r1', row(3, 'str22'::varchar)); -- fail +ERROR: unrecognized package "vars4" +select pgv_delete('vars3', 'r2', NULL::int); + pgv_delete +------------ + f +(1 row) + +select pgv_delete('vars4', 'r2', NULL::int); -- fail +ERROR: unrecognized package "vars4" +-- Test NULL values +SELECT pgv_insert('vars3', 'r2', NULL); -- fail +ERROR: record argument can not be NULL +SELECT pgv_update('vars3', 'r2', NULL); -- fail +ERROR: record argument can not be NULL +select pgv_delete('vars3', 'r2', NULL::int); + pgv_delete +------------ + f +(1 row) + +SELECT pgv_select('vars3', 'r1', NULL::int[]); -- fail +ERROR: array argument can not be NULL SELECT pgv_select('vars3', 'r1'); pgv_select ------------ @@ -568,6 +623,8 @@ SELECT pgv_select('vars3', 'r1', 1); (1,str11) (1 row) +SELECT pgv_select('vars3', 'r1', 1::float); -- fail +ERROR: requested value type differs from variable "r1" key type SELECT pgv_select('vars3', 'r1', 0); pgv_select ------------ @@ -598,6 +655,12 @@ SELECT pgv_update('vars3', 'r1', tab) FROM tab; t (4 rows) +SELECT pgv_update('vars3', 'r1', row(4, 'str44'::varchar)); + pgv_update +------------ + f +(1 row) + SELECT pgv_select('vars3', 'r1'); pgv_select ------------ @@ -643,33 +706,157 @@ SELECT pgv_exists('vars3', 'r1'); SELECT pgv_select('vars2', 'j1'); ERROR: variable "j1" requires "jsonb" value +-- PGPRO-2601 - Test pgv_select() on TupleDesc of dropped table +DROP TABLE tab; +SELECT pgv_select('vars3', 'r1'); + pgv_select +------------ + (,strNULL) + (2,) + (0,str00) +(3 rows) + +-- Tests for SRF's sequential scan of an internal hash table +DO +$$BEGIN + PERFORM pgv_select('vars3', 'r1') LIMIT 2 OFFSET 2; + PERFORM pgv_select('vars3', 'r3'); +END$$; +ERROR: unrecognized variable "r3" +CONTEXT: SQL statement "SELECT pgv_select('vars3', 'r3')" +PL/pgSQL function inline_code_block line 3 at PERFORM +-- Check that the hash table was cleaned up after rollback +SET client_min_messages to 'ERROR'; +SELECT pgv_select('vars3', 'r1', 1); + pgv_select +------------ + +(1 row) + +SELECT pgv_select('vars3', 'r1') LIMIT 2; -- warning + pgv_select +------------ + (,strNULL) + (2,) +(2 rows) + +SELECT pgv_select('vars3', 'r1') LIMIT 2 OFFSET 2; + pgv_select +------------ + (0,str00) +(1 row) + +-- PGPRO-2601 - Test a cursor with the hash table +BEGIN; +DECLARE r1_cur CURSOR FOR SELECT pgv_select('vars3', 'r1'); +FETCH 1 in r1_cur; + pgv_select +------------ + (,strNULL) +(1 row) + +SELECT pgv_select('vars3', 'r1'); + pgv_select +------------ + (,strNULL) + (2,) + (0,str00) +(3 rows) + +FETCH 1 in r1_cur; + pgv_select +------------ + (2,) +(1 row) + +CLOSE r1_cur; +COMMIT; -- warning +RESET client_min_messages; +-- Clean memory after unsuccessful creation of a variable +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 +--------- +(0 rows) + +-- Remove package if it is empty +SELECT pgv_insert('vars4', 'r2', row(1, 'str1', 'str2')); + pgv_insert +------------ + +(1 row) + +SELECT pgv_remove('vars4', 'r2'); + pgv_remove +------------ + +(1 row) + +SELECT package FROM pgv_stats() WHERE package = 'vars4'; + package +--------- +(0 rows) + +-- Record variables as scalar +SELECT pgv_set('vars5', 'r1', row(1, 'str11')); + pgv_set +--------- + +(1 row) + +SELECT pgv_get('vars5', 'r1', NULL::record); + pgv_get +----------- + (1,str11) +(1 row) + +SELECT pgv_set('vars5', 'r1', row(1, 'str11'), true); -- fail +ERROR: variable "r1" already created as NOT TRANSACTIONAL +SELECT pgv_insert('vars5', 'r1', row(1, 'str11')); -- fail +ERROR: "r1" isn't a record variable +SELECT pgv_select('vars5', 'r1'); -- fail +ERROR: "r1" isn't a record variable +SELECT pgv_get('vars3', 'r1', NULL::record); -- fail +ERROR: "r1" isn't a scalar variable -- Manipulate variables SELECT * FROM pgv_list() order by package, name; - package | name ----------+---------- - vars | d1 - vars | d2 - vars | dNULL - vars | int1 - vars | int2 - vars | intNULL - vars | jNULL - vars | num1 - vars | num2 - vars | numNULL - vars | str1 - vars | str2 - vars | strNULL - vars | ts1 - vars | ts2 - vars | tsNULL - vars | tstz1 - vars | tstz2 - vars | tstzNULL - vars2 | j1 - vars2 | j2 - vars3 | r1 -(22 rows) + package | name | is_transactional +---------+----------+------------------ + vars | d1 | f + vars | d2 | f + vars | dNULL | f + vars | int1 | f + vars | int2 | f + vars | intNULL | f + vars | jNULL | f + vars | num1 | f + vars | num2 | f + vars | numNULL | f + vars | str1 | f + vars | str2 | f + vars | strNULL | f + vars | ts1 | f + vars | ts2 | f + vars | tsNULL | f + vars | tstz1 | f + vars | tstz2 | f + vars | tstzNULL | f + vars2 | j1 | f + vars2 | j2 | f + vars3 | r1 | f + vars3 | r2 | f + vars5 | r1 | f +(24 rows) + +SELECT package FROM pgv_stats() order by package; + package +--------- + vars + vars2 + vars3 + vars5 +(4 rows) SELECT pgv_remove('vars', 'int3'); ERROR: unrecognized variable "int3" @@ -702,28 +889,30 @@ SELECT pgv_exists('vars2'); (1 row) SELECT * FROM pgv_list() order by package, name; - package | name ----------+---------- - vars | d1 - vars | d2 - vars | dNULL - vars | int2 - vars | intNULL - vars | jNULL - vars | num1 - vars | num2 - vars | numNULL - vars | str1 - vars | str2 - vars | strNULL - vars | ts1 - vars | ts2 - vars | tsNULL - vars | tstz1 - vars | tstz2 - vars | tstzNULL - vars3 | r1 -(19 rows) + package | name | is_transactional +---------+----------+------------------ + vars | d1 | f + vars | d2 | f + vars | dNULL | f + vars | int2 | f + vars | intNULL | f + vars | jNULL | f + vars | num1 | f + vars | num2 | f + vars | numNULL | f + vars | str1 | f + vars | str2 | f + vars | strNULL | f + vars | ts1 | f + vars | ts2 | f + vars | tsNULL | f + vars | tstz1 | f + vars | tstz2 | f + vars | tstzNULL | f + vars3 | r1 | f + vars3 | r2 | f + vars5 | r1 | f +(21 rows) SELECT pgv_free(); pgv_free @@ -738,7 +927,102 @@ SELECT pgv_exists('vars'); (1 row) SELECT * FROM pgv_list() order by package, name; - package | name ----------+------ + package | name | is_transactional +---------+------+------------------ (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_any.out b/expected/pg_variables_any.out index b7b6cb9..814b40a 100644 --- a/expected/pg_variables_any.out +++ b/expected/pg_variables_any.out @@ -530,32 +530,106 @@ SELECT pgv_get('vars', 'jNULL', NULL::jsonb); (1 row) +-- Array variables +SELECT pgv_set('vars', 'arr1', '{1, 2, null}'::int[]); + pgv_set +--------- + +(1 row) + +SELECT pgv_set('vars', 'arr2', '{"bar", "balance", "active"}'::text[]); + pgv_set +--------- + +(1 row) + +SELECT pgv_set('vars2', 'j1', '{1, 2, null}'::int[]); +ERROR: variable "j1" requires "jsonb" value +SELECT pgv_get('vars', 'arr1', NULL::int[]); + pgv_get +------------ + {1,2,NULL} +(1 row) + +SELECT pgv_get('vars', 'arr2', NULL::int[]); +ERROR: variable "arr2" requires "text[]" value +SELECT pgv_set('vars', 'arr1', '{"bar", "balance", "active"}'::text[]); +ERROR: variable "arr1" requires "integer[]" value +SELECT pgv_set('vars', 'arr1', '{3, 4, 5}'::int[]); + pgv_set +--------- + +(1 row) + +SELECT pgv_get('vars', 'arr1', NULL::int[]); + pgv_get +--------- + {3,4,5} +(1 row) + +SELECT pgv_get('vars', 'arr3', NULL::int[]); +ERROR: unrecognized variable "arr3" +SELECT pgv_get('vars', 'arr3', NULL::int[], false); + pgv_get +--------- + +(1 row) + +SELECT pgv_exists('vars', 'arr3'); + pgv_exists +------------ + f +(1 row) + +SELECT pgv_exists('vars', 'arr1'); + pgv_exists +------------ + t +(1 row) + +SELECT pgv_get('vars2', 'j1', NULL::int[]); +ERROR: variable "j1" requires "jsonb" value +SELECT pgv_set('vars', 'arrNULL', NULL::int[]); + pgv_set +--------- + +(1 row) + +SELECT pgv_get('vars', 'arrNULL', NULL::int[]); + pgv_get +--------- + +(1 row) + -- Manipulate variables SELECT * FROM pgv_list() order by package, name; - package | name ----------+---------- - vars | d1 - vars | d2 - vars | dNULL - vars | int1 - vars | int2 - vars | intNULL - vars | jNULL - vars | num1 - vars | num2 - vars | numNULL - vars | str1 - vars | str2 - vars | strNULL - vars | ts1 - vars | ts2 - vars | tsNULL - vars | tstz1 - vars | tstz2 - vars | tstzNULL - vars2 | j1 - vars2 | j2 -(21 rows) + package | name | is_transactional +---------+----------+------------------ + vars | arr1 | f + vars | arr2 | f + vars | arrNULL | f + vars | d1 | f + vars | d2 | f + vars | dNULL | f + vars | int1 | f + vars | int2 | f + vars | intNULL | f + vars | jNULL | f + vars | num1 | f + vars | num2 | f + vars | numNULL | f + vars | str1 | f + vars | str2 | f + vars | strNULL | f + vars | ts1 | f + vars | ts2 | f + vars | tsNULL | f + vars | tstz1 | f + vars | tstz2 | f + vars | tstzNULL | f + vars2 | j1 | f + vars2 | j2 | f +(24 rows) SELECT pgv_remove('vars', 'int3'); ERROR: unrecognized variable "int3" @@ -588,27 +662,30 @@ SELECT pgv_exists('vars2'); (1 row) SELECT * FROM pgv_list() order by package, name; - package | name ----------+---------- - vars | d1 - vars | d2 - vars | dNULL - vars | int2 - vars | intNULL - vars | jNULL - vars | num1 - vars | num2 - vars | numNULL - vars | str1 - vars | str2 - vars | strNULL - vars | ts1 - vars | ts2 - vars | tsNULL - vars | tstz1 - vars | tstz2 - vars | tstzNULL -(18 rows) + package | name | is_transactional +---------+----------+------------------ + vars | arr1 | f + vars | arr2 | f + vars | arrNULL | f + vars | d1 | f + vars | d2 | f + vars | dNULL | f + vars | int2 | f + vars | intNULL | f + vars | jNULL | f + vars | num1 | f + vars | num2 | f + vars | numNULL | f + vars | str1 | f + vars | str2 | f + vars | strNULL | f + vars | ts1 | f + vars | ts2 | f + vars | tsNULL | f + vars | tstz1 | f + vars | tstz2 | f + vars | tstzNULL | f +(21 rows) SELECT pgv_free(); pgv_free @@ -623,7 +700,7 @@ SELECT pgv_exists('vars'); (1 row) SELECT * FROM pgv_list() order by package, name; - package | name ----------+------ + package | name | is_transactional +---------+------+------------------ (0 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 new file mode 100644 index 0000000..4370646 --- /dev/null +++ b/expected/pg_variables_trans.out @@ -0,0 +1,3884 @@ +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'::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, +-- 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); +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/mk_dockerfile.sh b/mk_dockerfile.sh new file mode 100755 index 0000000..f15433c --- /dev/null +++ b/mk_dockerfile.sh @@ -0,0 +1,16 @@ +if [ -z ${PG_VERSION+x} ]; then + echo PG_VERSION is not set! + exit 1 +fi + +if [ -z ${LEVEL+x} ]; then + LEVEL=scan-build +fi + +echo PG_VERSION=${PG_VERSION} +echo LEVEL=${LEVEL} + +sed \ + -e 's/${PG_VERSION}/'${PG_VERSION}/g \ + -e 's/${LEVEL}/'${LEVEL}/g \ + Dockerfile.tmpl > Dockerfile diff --git a/pg_variables--1.0--1.1.sql b/pg_variables--1.0--1.1.sql new file mode 100644 index 0000000..1627b24 --- /dev/null +++ b/pg_variables--1.0--1.1.sql @@ -0,0 +1,68 @@ +/* contrib/pg_variables/pg_variables--1.0--1.1.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pg_variables UPDATE TO '1.1'" to load this file. \quit + +-- Delete previous vresion of functions. +DROP FUNCTION pgv_set(package text, name text, value anynonarray); +DROP FUNCTION pgv_set_int(package text, name text, value int); +DROP FUNCTION pgv_set_text(package text, name text, value text); +DROP FUNCTION pgv_set_numeric(package text, name text, value numeric); +DROP FUNCTION pgv_set_timestamp(package text, name text, value timestamp); +DROP FUNCTION pgv_set_timestamptz(package text, name text, value timestamptz); +DROP FUNCTION pgv_set_date(package text, name text, value date); +DROP FUNCTION pgv_set_jsonb(package text, name text, value jsonb); +DROP FUNCTION pgv_insert(package text, name text, r record); +DROP FUNCTION pgv_list(); + +-- Create new versions of setters +CREATE FUNCTION pgv_set(package text, name text, value anynonarray, is_transactional bool default false) +RETURNS void +AS 'MODULE_PATHNAME', 'variable_set_any' +LANGUAGE C VOLATILE; + +CREATE FUNCTION pgv_set_int(package text, name text, value int, is_transactional bool default false) +RETURNS void +AS 'MODULE_PATHNAME', 'variable_set_int' +LANGUAGE C VOLATILE; + +CREATE FUNCTION pgv_set_text(package text, name text, value text, is_transactional bool default false) +RETURNS void +AS 'MODULE_PATHNAME', 'variable_set_text' +LANGUAGE C VOLATILE; + +CREATE FUNCTION pgv_set_numeric(package text, name text, value numeric, is_transactional bool default false) +RETURNS void +AS 'MODULE_PATHNAME', 'variable_set_numeric' +LANGUAGE C VOLATILE; + +CREATE FUNCTION pgv_set_timestamp(package text, name text, value timestamp, is_transactional bool default false) +RETURNS void +AS 'MODULE_PATHNAME', 'variable_set_timestamp' +LANGUAGE C VOLATILE; + +CREATE FUNCTION pgv_set_timestamptz(package text, name text, value timestamptz, is_transactional bool default false) +RETURNS void +AS 'MODULE_PATHNAME', 'variable_set_timestamptz' +LANGUAGE C VOLATILE; + +CREATE FUNCTION pgv_set_date(package text, name text, value date, is_transactional bool default false) +RETURNS void +AS 'MODULE_PATHNAME', 'variable_set_date' +LANGUAGE C VOLATILE; + +CREATE FUNCTION pgv_set_jsonb(package text, name text, value jsonb, is_transactional bool default false) +RETURNS void +AS 'MODULE_PATHNAME', 'variable_set_jsonb' +LANGUAGE C VOLATILE; + +CREATE FUNCTION pgv_insert(package text, name text, r record, is_transactional bool default false) +RETURNS void +AS 'MODULE_PATHNAME', 'variable_insert' +LANGUAGE C VOLATILE; + +-- pgv_list() changed output +CREATE FUNCTION pgv_list() +RETURNS TABLE(package text, name text, is_transactional bool) +AS 'MODULE_PATHNAME', 'get_packages_and_variables' +LANGUAGE C VOLATILE; diff --git a/pg_variables--1.1--1.2.sql b/pg_variables--1.1--1.2.sql new file mode 100644 index 0000000..b28a964 --- /dev/null +++ b/pg_variables--1.1--1.2.sql @@ -0,0 +1,16 @@ +/* contrib/pg_variables/pg_variables--1.1--1.2.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pg_variables UPDATE TO '1.2'" to load this file. \quit + +-- Functions to work with arrays + +CREATE FUNCTION pgv_set(package text, name text, value anyarray, is_transactional bool default false) +RETURNS void +AS 'MODULE_PATHNAME', 'variable_set_array' +LANGUAGE C VOLATILE; + +CREATE FUNCTION pgv_get(package text, name text, var_type anyarray, strict bool default true) +RETURNS anyarray +AS 'MODULE_PATHNAME', 'variable_get_array' +LANGUAGE C VOLATILE; diff --git a/pg_variables.c b/pg_variables.c index ef3386a..c8ee939 100644 --- a/pg_variables.c +++ b/pg_variables.c @@ -3,17 +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" @@ -25,26 +28,6 @@ PG_MODULE_MAGIC; -/* Scalar variables functions */ -PG_FUNCTION_INFO_V1(variable_set_any); -PG_FUNCTION_INFO_V1(variable_get_any); - -/* Deprecated scalar variables functions */ -PG_FUNCTION_INFO_V1(variable_set_int); -PG_FUNCTION_INFO_V1(variable_get_int); -PG_FUNCTION_INFO_V1(variable_set_text); -PG_FUNCTION_INFO_V1(variable_get_text); -PG_FUNCTION_INFO_V1(variable_set_numeric); -PG_FUNCTION_INFO_V1(variable_get_numeric); -PG_FUNCTION_INFO_V1(variable_set_timestamp); -PG_FUNCTION_INFO_V1(variable_get_timestamp); -PG_FUNCTION_INFO_V1(variable_set_timestamptz); -PG_FUNCTION_INFO_V1(variable_get_timestamptz); -PG_FUNCTION_INFO_V1(variable_set_date); -PG_FUNCTION_INFO_V1(variable_get_date); -PG_FUNCTION_INFO_V1(variable_set_jsonb); -PG_FUNCTION_INFO_V1(variable_get_jsonb); - /* Functions to work with records */ PG_FUNCTION_INFO_V1(variable_insert); PG_FUNCTION_INFO_V1(variable_update); @@ -63,16 +46,68 @@ PG_FUNCTION_INFO_V1(remove_packages); 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); -static void ensurePackagesHashExists(); -static HashPackageEntry *getPackageByName(text *name, bool create, bool strict); -static HashVariableEntry *getVariableByNameWithType(HTAB *variables, - text *name, - Oid typid, - bool create, - bool strict); +static Package *getPackage(text *name, bool strict); +static Package *createPackage(text *name, bool is_trans); +static Variable *getVariableInternal(Package *package, text *name, + Oid typid, bool is_record, bool strict); +static Variable *createVariableInternal(Package *package, text *name, Oid typid, + bool is_record, bool is_transactional); +static void removePackageInternal(Package *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, 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); +static void removeState(TransObject *object, TransObjectType type, + TransState *stateToDelete); +static bool isObjectChangedInCurrentTrans(TransObject *object); +static bool isObjectChangedInUpperTrans(TransObject *object); + +static void addToChangesStack(TransObject *object, TransObjectType type); +static void addToChangesStackUpperLevel(TransObject *object, + TransObjectType type); +static void pushChangesStack(void); + +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 void initObjectHistory(TransObject *object, TransObjectType type); +/* Hook functions */ +static void variable_ExecutorEnd(QueryDesc *queryDesc); + +#if PG_VERSION_NUM >= 120000 +#define CHECK_ARGS_FOR_NULL() \ +do { \ + if (fcinfo->args[0].isnull) \ + ereport(ERROR, \ + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), \ + errmsg("package name can not be NULL"))); \ + if (fcinfo->args[1].isnull) \ + ereport(ERROR, \ + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), \ + errmsg("variable name can not be NULL"))); \ +} while(0) +#else /* PG_VERSION_NUM < 120000 */ #define CHECK_ARGS_FOR_NULL() \ do { \ if (fcinfo->argnull[0]) \ @@ -84,31 +119,487 @@ do { \ (errcode(ERRCODE_INVALID_PARAMETER_VALUE), \ errmsg("variable name can not be NULL"))); \ } while(0) +#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 HashPackageEntry *LastPackage = NULL; +static Package *LastPackage = NULL; + /* Recent variable */ -static HashVariableEntry *LastVariable = NULL; +static Variable *LastVariable = NULL; + +/* Saved hook values for recall */ +static ExecutorEnd_hook_type prev_ExecutorEnd = NULL; + +/* This stack contains lists of changed variables and packages per each subxact level */ +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() \ + ( \ + AssertMacro(changesStack != NULL), \ + (dlist_head_element(ChangesStackNode, node, changesStack)) \ + ) +#define pack_hctx(pack, is_trans) \ + (is_trans ? pack->hctxTransact : pack->hctxRegular) +#define pack_htab(pack, is_trans) \ + (is_trans ? pack->varHashTransact : pack->varHashRegular) + +#define PGV_MCXT_MAIN "pg_variables: main memory context" +#define PGV_MCXT_VARS "pg_variables: variables hash" +#define PGV_MCXT_STACK "pg_variables: changesStack" +#define PGV_MCXT_STACK_NODE "pg_variables: changesStackNode" + + +#ifndef ALLOCSET_DEFAULT_SIZES +#define ALLOCSET_DEFAULT_SIZES \ + ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE +#endif + +#ifndef ALLOCSET_START_SMALL_SIZES +#define ALLOCSET_START_SMALL_SIZES \ + ALLOCSET_SMALL_MINSIZE, ALLOCSET_SMALL_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE +#endif + /* * Set value of variable, typlen could be 0 if typbyval == true */ static void variable_set(text *package_name, text *var_name, - Oid typid, Datum value, bool is_null) + Oid typid, Datum value, bool is_null, bool is_transactional) { - HashPackageEntry *package; - HashVariableEntry *variable; - ScalarVar *scalar; + Package *package; + Variable *variable; + ScalarVar *scalar; - package = getPackageByName(package_name, true, false); - variable = getVariableByNameWithType(package->variablesHash, - var_name, typid, true, false); + package = createPackage(package_name, is_transactional); + variable = createVariableInternal(package, var_name, typid, false, + is_transactional); - scalar = &variable->value.scalar; + scalar = &(GetActualValue(variable).scalar); /* Release memory for variable */ if (scalar->typbyval == false && scalar->is_null == false) @@ -117,11 +608,10 @@ variable_set(text *package_name, text *var_name, scalar->is_null = is_null; if (!scalar->is_null) { - MemoryContext oldcxt; - oldcxt = MemoryContextSwitchTo(package->hctx); + MemoryContext oldcxt; + oldcxt = MemoryContextSwitchTo(pack_hctx(package, is_transactional)); scalar->value = datumCopy(value, scalar->typbyval, scalar->typlen); - MemoryContextSwitchTo(oldcxt); } else @@ -132,19 +622,18 @@ static Datum variable_get(text *package_name, text *var_name, Oid typid, bool *is_null, bool strict) { - HashPackageEntry *package; - HashVariableEntry *variable; - ScalarVar *scalar; + Package *package; + Variable *variable; + ScalarVar *scalar; - package = getPackageByName(package_name, false, strict); + package = getPackage(package_name, strict); if (package == NULL) { *is_null = true; return 0; } - variable = getVariableByNameWithType(package->variablesHash, - var_name, typid, false, strict); + variable = getVariableInternal(package, var_name, typid, false, strict); if (variable == NULL) { @@ -152,987 +641,869 @@ variable_get(text *package_name, text *var_name, return 0; } - scalar = &(variable->value.scalar); - + scalar = &(GetActualValue(variable).scalar); *is_null = scalar->is_null; + return scalar->value; } -Datum -variable_set_any(PG_FUNCTION_ARGS) -{ - text *package_name; - text *var_name; - CHECK_ARGS_FOR_NULL(); +#define VARIABLE_GET_TEMPLATE(pkg_arg, var_arg, strict_arg, type, typid) \ + PG_FUNCTION_INFO_V1(variable_get_##type); \ + Datum \ + variable_get_##type(PG_FUNCTION_ARGS) \ + { \ + text *package_name; \ + text *var_name; \ + bool strict; \ + bool isnull; \ + Datum value; \ + \ + CHECK_ARGS_FOR_NULL(); \ + \ + package_name = PG_GETARG_TEXT_PP(pkg_arg); \ + var_name = PG_GETARG_TEXT_PP(var_arg); \ + strict = PG_GETARG_BOOL(strict_arg); \ + \ + value = variable_get(package_name, var_name, \ + (typid), &isnull, strict); \ + \ + PG_FREE_IF_COPY(package_name, pkg_arg); \ + PG_FREE_IF_COPY(var_name, var_arg); \ + \ + if (!isnull) \ + PG_RETURN_DATUM(value); \ + else \ + PG_RETURN_NULL(); \ + } - package_name = PG_GETARG_TEXT_PP(0); - var_name = PG_GETARG_TEXT_PP(1); +/* deprecated functions */ +VARIABLE_GET_TEMPLATE(0, 1, 2, int, INT4OID) +VARIABLE_GET_TEMPLATE(0, 1, 2, text, TEXTOID) +VARIABLE_GET_TEMPLATE(0, 1, 2, numeric, NUMERICOID) +VARIABLE_GET_TEMPLATE(0, 1, 2, timestamp, TIMESTAMPOID) +VARIABLE_GET_TEMPLATE(0, 1, 2, timestamptz, TIMESTAMPTZOID) +VARIABLE_GET_TEMPLATE(0, 1, 2, date, DATEOID) +VARIABLE_GET_TEMPLATE(0, 1, 2, jsonb, JSONBOID) + +/* current API */ +VARIABLE_GET_TEMPLATE(0, 1, 3, any, get_fn_expr_argtype(fcinfo->flinfo, 2)) +VARIABLE_GET_TEMPLATE(0, 1, 3, array, get_fn_expr_argtype(fcinfo->flinfo, 2)) + + +#define VARIABLE_SET_TEMPLATE(type, typid) \ + PG_FUNCTION_INFO_V1(variable_set_##type); \ + Datum \ + variable_set_##type(PG_FUNCTION_ARGS) \ + { \ + text *package_name; \ + text *var_name; \ + bool is_transactional; \ + \ + CHECK_ARGS_FOR_NULL(); \ + \ + package_name = PG_GETARG_TEXT_PP(0); \ + var_name = PG_GETARG_TEXT_PP(1); \ + is_transactional = PG_GETARG_BOOL(3); \ + \ + variable_set(package_name, var_name, (typid), \ + PG_ARGISNULL(2) ? 0 : PG_GETARG_DATUM(2), \ + PG_ARGISNULL(2), is_transactional); \ + \ + PG_FREE_IF_COPY(package_name, 0); \ + PG_FREE_IF_COPY(var_name, 1); \ + PG_RETURN_VOID(); \ + } - variable_set(package_name, var_name, get_fn_expr_argtype(fcinfo->flinfo, 2), - PG_ARGISNULL(2) ? 0 : PG_GETARG_DATUM(2), - PG_ARGISNULL(2)); - PG_FREE_IF_COPY(package_name, 0); - PG_FREE_IF_COPY(var_name, 1); - PG_RETURN_VOID(); -} +/* deprecated functions */ +VARIABLE_SET_TEMPLATE(int, INT4OID) +VARIABLE_SET_TEMPLATE(text, TEXTOID) +VARIABLE_SET_TEMPLATE(numeric, NUMERICOID) +VARIABLE_SET_TEMPLATE(timestamp, TIMESTAMPOID) +VARIABLE_SET_TEMPLATE(timestamptz, TIMESTAMPTZOID) +VARIABLE_SET_TEMPLATE(date, DATEOID) +VARIABLE_SET_TEMPLATE(jsonb, JSONBOID) + +/* current API */ +VARIABLE_SET_TEMPLATE(any, get_fn_expr_argtype(fcinfo->flinfo, 2)) +VARIABLE_SET_TEMPLATE(array, get_fn_expr_argtype(fcinfo->flinfo, 2)) + Datum -variable_get_any(PG_FUNCTION_ARGS) +variable_insert(PG_FUNCTION_ARGS) { text *package_name; text *var_name; - bool strict; - bool is_null; - Datum value; + HeapTupleHeader rec; + Package *package; + Variable *variable; + bool is_transactional; + + Oid tupType; + int32 tupTypmod; + TupleDesc tupdesc = NULL; + RecordVar *record; + /* Checks */ CHECK_ARGS_FOR_NULL(); + if (PG_ARGISNULL(2)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("record argument can not be NULL"))); + + /* Get arguments */ package_name = PG_GETARG_TEXT_PP(0); var_name = PG_GETARG_TEXT_PP(1); - strict = PG_GETARG_BOOL(3); - - value = variable_get(package_name, var_name, - get_fn_expr_argtype(fcinfo->flinfo, 2), - &is_null, strict); + rec = PG_GETARG_HEAPTUPLEHEADER(2); + is_transactional = PG_GETARG_BOOL(3); - PG_FREE_IF_COPY(package_name, 0); - PG_FREE_IF_COPY(var_name, 1); - if (!is_null) - PG_RETURN_DATUM(value); + /* Get cached package */ + if (LastPackage == NULL || + VARSIZE_ANY_EXHDR(package_name) != strlen(GetName(LastPackage)) || + strncmp(VARDATA_ANY(package_name), GetName(LastPackage), + VARSIZE_ANY_EXHDR(package_name)) != 0 || + !pack_htab(LastPackage, is_transactional)) + { + package = createPackage(package_name, is_transactional); + LastPackage = package; + LastVariable = NULL; + } else - PG_RETURN_NULL(); -} + package = LastPackage; -Datum -variable_set_int(PG_FUNCTION_ARGS) -{ - text *package_name; - text *var_name; + /* Get cached variable */ + if (LastVariable == NULL || + VARSIZE_ANY_EXHDR(var_name) != strlen(GetName(LastVariable)) || + strncmp(VARDATA_ANY(var_name), GetName(LastVariable), + VARSIZE_ANY_EXHDR(var_name)) != 0) + { + variable = createVariableInternal(package, var_name, RECORDOID, + true, is_transactional); + LastVariable = variable; + } + else + { + TransObject *transObj; - CHECK_ARGS_FOR_NULL(); + if (LastVariable->is_transactional != is_transactional) + { + char key[NAMEDATALEN]; - package_name = PG_GETARG_TEXT_PP(0); - var_name = PG_GETARG_TEXT_PP(1); + getKeyFromName(var_name, key); + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("variable \"%s\" already created as %sTRANSACTIONAL", + key, LastVariable->is_transactional ? "" : "NOT "))); + } - variable_set(package_name, var_name, INT4OID, - PG_ARGISNULL(2) ? 0 : PG_GETARG_DATUM(2), - PG_ARGISNULL(2)); + variable = LastVariable; + transObj = &variable->transObject; - PG_FREE_IF_COPY(package_name, 0); - PG_FREE_IF_COPY(var_name, 1); - PG_RETURN_VOID(); -} + if (variable->is_transactional && + !isObjectChangedInCurrentTrans(transObj)) + { + createSavepoint(transObj, TRANS_VARIABLE); + addToChangesStack(transObj, TRANS_VARIABLE); + } + } -Datum -variable_get_int(PG_FUNCTION_ARGS) -{ - text *package_name; - text *var_name; - bool strict; - bool is_null; - Datum value; - - CHECK_ARGS_FOR_NULL(); + /* Insert a record */ + tupType = HeapTupleHeaderGetTypeId(rec); + tupTypmod = HeapTupleHeaderGetTypMod(rec); - package_name = PG_GETARG_TEXT_PP(0); - var_name = PG_GETARG_TEXT_PP(1); - strict = PG_GETARG_BOOL(2); + record = &(GetActualValue(variable).record); + tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); - value = variable_get(package_name, var_name, - INT4OID, &is_null, strict); + if (!record->tupdesc || variable->is_deleted) + { + /* + * This is the first record for the var_name. Initialize record. + */ + /* Convert UNKNOWNOID to TEXTOID if needed + * tupdesc may be changed + */ + if (convert_unknownoid) + { + coerce_unknown_first_record(&tupdesc, &rec); + } - PG_FREE_IF_COPY(package_name, 0); - PG_FREE_IF_COPY(var_name, 1); - if (!is_null) - PG_RETURN_DATUM(value); + init_record(record, tupdesc, variable); + variable->is_deleted = false; + } else - PG_RETURN_NULL(); -} - -Datum -variable_set_text(PG_FUNCTION_ARGS) -{ - text *package_name; - text *var_name; + { + /* + * 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 + */ + check_attributes(variable, &rec, tupdesc); - CHECK_ARGS_FOR_NULL(); + } - package_name = PG_GETARG_TEXT_PP(0); - var_name = PG_GETARG_TEXT_PP(1); + insert_record(variable, rec); - variable_set(package_name, var_name, TEXTOID, - PG_ARGISNULL(2) ? 0 : PG_GETARG_DATUM(2), - PG_ARGISNULL(2)); + /* Release resources */ + if (tupdesc) + ReleaseTupleDesc(tupdesc); PG_FREE_IF_COPY(package_name, 0); PG_FREE_IF_COPY(var_name, 1); + PG_RETURN_VOID(); } Datum -variable_get_text(PG_FUNCTION_ARGS) +variable_update(PG_FUNCTION_ARGS) { text *package_name; text *var_name; - bool strict; - bool is_null; - Datum value; + HeapTupleHeader rec; + Package *package; + Variable *variable; + TransObject *transObject; + bool res; + Oid tupType; + int32 tupTypmod; + TupleDesc tupdesc = NULL; + /* Checks */ CHECK_ARGS_FOR_NULL(); + if (PG_ARGISNULL(2)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("record argument can not be NULL"))); + + /* Get arguments */ package_name = PG_GETARG_TEXT_PP(0); var_name = PG_GETARG_TEXT_PP(1); - strict = PG_GETARG_BOOL(2); + rec = PG_GETARG_HEAPTUPLEHEADER(2); - value = variable_get(package_name, var_name, - TEXTOID, &is_null, strict); + /* Get cached package */ + if (LastPackage == NULL || + VARSIZE_ANY_EXHDR(package_name) != strlen(GetName(LastPackage)) || + strncmp(VARDATA_ANY(package_name), GetName(LastPackage), + VARSIZE_ANY_EXHDR(package_name)) != 0) + { + package = getPackage(package_name, true); + LastPackage = package; + LastVariable = NULL; + } + else + package = LastPackage; - PG_FREE_IF_COPY(package_name, 0); - PG_FREE_IF_COPY(var_name, 1); - if (!is_null) - PG_RETURN_DATUM(value); + /* Get cached variable */ + if (LastVariable == NULL || + VARSIZE_ANY_EXHDR(var_name) != strlen(GetName(LastVariable)) || + strncmp(VARDATA_ANY(var_name), GetName(LastVariable), + VARSIZE_ANY_EXHDR(var_name)) != 0) + { + variable = getVariableInternal(package, var_name, RECORDOID, true, + true); + LastVariable = variable; + } else - PG_RETURN_NULL(); -} + variable = LastVariable; -Datum -variable_set_numeric(PG_FUNCTION_ARGS) -{ - text *package_name; - text *var_name; + transObject = &variable->transObject; + if (variable->is_transactional && + !isObjectChangedInCurrentTrans(transObject)) + { + createSavepoint(transObject, TRANS_VARIABLE); + addToChangesStack(transObject, TRANS_VARIABLE); + } - CHECK_ARGS_FOR_NULL(); + /* Update a record */ + tupType = HeapTupleHeaderGetTypeId(rec); + tupTypmod = HeapTupleHeaderGetTypMod(rec); - package_name = PG_GETARG_TEXT_PP(0); - var_name = PG_GETARG_TEXT_PP(1); + tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); + /* + * Convert UNKNOWNOID to TEXTOID if needed + * tupdesc may be changed + */ + check_attributes(variable, &rec, tupdesc); + ReleaseTupleDesc(tupdesc); - variable_set(package_name, var_name, NUMERICOID, - PG_ARGISNULL(2) ? 0 : PG_GETARG_DATUM(2), - PG_ARGISNULL(2)); + res = update_record(variable, rec); + /* Release resources */ PG_FREE_IF_COPY(package_name, 0); PG_FREE_IF_COPY(var_name, 1); - PG_RETURN_VOID(); + + PG_RETURN_BOOL(res); } Datum -variable_get_numeric(PG_FUNCTION_ARGS) +variable_delete(PG_FUNCTION_ARGS) { text *package_name; text *var_name; - bool strict; - bool is_null; + Oid value_type; Datum value; + bool value_is_null = PG_ARGISNULL(2); + Package *package; + Variable *variable; + TransObject *transObject; + bool res; CHECK_ARGS_FOR_NULL(); + /* Get arguments */ package_name = PG_GETARG_TEXT_PP(0); var_name = PG_GETARG_TEXT_PP(1); - strict = PG_GETARG_BOOL(2); - - value = variable_get(package_name, var_name, - NUMERICOID, &is_null, strict); - PG_FREE_IF_COPY(package_name, 0); - PG_FREE_IF_COPY(var_name, 1); - if (!is_null) - PG_RETURN_DATUM(value); + if (!value_is_null) + { + value_type = get_fn_expr_argtype(fcinfo->flinfo, 2); + value = PG_GETARG_DATUM(2); + } else - PG_RETURN_NULL(); -} + { + value_type = InvalidOid; + value = 0; + } -Datum -variable_set_timestamp(PG_FUNCTION_ARGS) -{ - text *package_name; - text *var_name; + /* Get cached package */ + if (LastPackage == NULL || + VARSIZE_ANY_EXHDR(package_name) != strlen(GetName(LastPackage)) || + strncmp(VARDATA_ANY(package_name), GetName(LastPackage), + VARSIZE_ANY_EXHDR(package_name)) != 0) + { + package = getPackage(package_name, true); + LastPackage = package; + LastVariable = NULL; + } + else + package = LastPackage; - CHECK_ARGS_FOR_NULL(); + /* Get cached variable */ + if (LastVariable == NULL || + VARSIZE_ANY_EXHDR(var_name) != strlen(GetName(LastVariable)) || + strncmp(VARDATA_ANY(var_name), GetName(LastVariable), + VARSIZE_ANY_EXHDR(var_name)) != 0) + { + variable = getVariableInternal(package, var_name, RECORDOID, true, + true); + LastVariable = variable; + } + else + variable = LastVariable; - package_name = PG_GETARG_TEXT_PP(0); - var_name = PG_GETARG_TEXT_PP(1); + transObject = &variable->transObject; + if (variable->is_transactional && + !isObjectChangedInCurrentTrans(transObject)) + { + createSavepoint(transObject, TRANS_VARIABLE); + addToChangesStack(transObject, TRANS_VARIABLE); + } - variable_set(package_name, var_name, TIMESTAMPOID, - PG_ARGISNULL(2) ? 0 : PG_GETARG_DATUM(2), - PG_ARGISNULL(2)); + /* Delete a record */ + if (!value_is_null) + check_record_key(variable, value_type); + res = delete_record(variable, value, value_is_null); + /* Release resources */ PG_FREE_IF_COPY(package_name, 0); PG_FREE_IF_COPY(var_name, 1); - PG_RETURN_VOID(); + + PG_RETURN_BOOL(res); } Datum -variable_get_timestamp(PG_FUNCTION_ARGS) +variable_select(PG_FUNCTION_ARGS) { + FuncCallContext *funcctx; + HASH_SEQ_STATUS *rstat; + HashRecordEntry *item; text *package_name; text *var_name; - bool strict; - bool is_null; - Datum value; + Package *package; + Variable *variable; CHECK_ARGS_FOR_NULL(); + /* Get arguments */ package_name = PG_GETARG_TEXT_PP(0); var_name = PG_GETARG_TEXT_PP(1); - strict = PG_GETARG_BOOL(2); - value = variable_get(package_name, var_name, - TIMESTAMPOID, &is_null, strict); + package = getPackage(package_name, true); + variable = getVariableInternal(package, var_name, RECORDOID, true, + true); - PG_FREE_IF_COPY(package_name, 0); - PG_FREE_IF_COPY(var_name, 1); - if (!is_null) - PG_RETURN_DATUM(value); - else - PG_RETURN_NULL(); -} + if (SRF_IS_FIRSTCALL()) + { + MemoryContext oldcontext; + RecordVar *record; + VariableStatEntry *entry; -Datum -variable_set_timestamptz(PG_FUNCTION_ARGS) -{ - text *package_name; - text *var_name; + record = &(GetActualValue(variable).record); + funcctx = SRF_FIRSTCALL_INIT(); - CHECK_ARGS_FOR_NULL(); + oldcontext = MemoryContextSwitchTo(TopTransactionContext); - package_name = PG_GETARG_TEXT_PP(0); - var_name = PG_GETARG_TEXT_PP(1); + funcctx->tuple_desc = record->tupdesc; - variable_set(package_name, var_name, TIMESTAMPTZOID, - PG_ARGISNULL(2) ? 0 : PG_GETARG_DATUM(2), - PG_ARGISNULL(2)); + rstat = (HASH_SEQ_STATUS *) palloc0(sizeof(HASH_SEQ_STATUS)); + hash_seq_init(rstat, record->rhash); + funcctx->user_fctx = rstat; - PG_FREE_IF_COPY(package_name, 0); - PG_FREE_IF_COPY(var_name, 1); - PG_RETURN_VOID(); -} + 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); -Datum -variable_get_timestamptz(PG_FUNCTION_ARGS) -{ - text *package_name; - text *var_name; - bool strict; - bool is_null; - Datum value; + MemoryContextSwitchTo(oldcontext); + PG_FREE_IF_COPY(package_name, 0); + PG_FREE_IF_COPY(var_name, 1); + } - CHECK_ARGS_FOR_NULL(); + funcctx = SRF_PERCALL_SETUP(); - package_name = PG_GETARG_TEXT_PP(0); - var_name = PG_GETARG_TEXT_PP(1); - strict = PG_GETARG_BOOL(2); + if (funcctx->user_fctx == NULL) + { + /* + * VariableStatEntry was removed. For example, after call + * 'ROLLBACK TO SAVEPOINT ...' + */ + SRF_RETURN_DONE(funcctx); + } - value = variable_get(package_name, var_name, - TIMESTAMPTZOID, &is_null, strict); + /* 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))); - PG_FREE_IF_COPY(package_name, 0); - PG_FREE_IF_COPY(var_name, 1); - if (!is_null) - PG_RETURN_DATUM(value); + SRF_RETURN_NEXT(funcctx, item->tuple); + } else - PG_RETURN_NULL(); + { + remove_variables_status(&variables_stats, rstat); + SRF_RETURN_DONE(funcctx); + } } Datum -variable_set_date(PG_FUNCTION_ARGS) +variable_select_by_value(PG_FUNCTION_ARGS) { text *package_name; text *var_name; + Oid value_type; + Datum value; + bool value_is_null = PG_ARGISNULL(2); + Package *package; + Variable *variable; + + HashRecordEntry *item; + RecordVar *record; + HashRecordKey k; + bool found; CHECK_ARGS_FOR_NULL(); + /* Get arguments */ package_name = PG_GETARG_TEXT_PP(0); var_name = PG_GETARG_TEXT_PP(1); - variable_set(package_name, var_name, DATEOID, - PG_ARGISNULL(2) ? 0 : PG_GETARG_DATUM(2), - PG_ARGISNULL(2)); + if (!value_is_null) + { + value_type = get_fn_expr_argtype(fcinfo->flinfo, 2); + value = PG_GETARG_DATUM(2); + } + else + { + value_type = InvalidOid; + value = 0; + } - PG_FREE_IF_COPY(package_name, 0); - PG_FREE_IF_COPY(var_name, 1); - PG_RETURN_VOID(); -} + package = getPackage(package_name, true); + variable = getVariableInternal(package, var_name, RECORDOID, true, true); -Datum -variable_get_date(PG_FUNCTION_ARGS) -{ - text *package_name; - text *var_name; - bool strict; - bool is_null; - Datum value; + if (!value_is_null) + check_record_key(variable, value_type); - CHECK_ARGS_FOR_NULL(); + record = &(GetActualValue(variable).record); - package_name = PG_GETARG_TEXT_PP(0); - var_name = PG_GETARG_TEXT_PP(1); - strict = PG_GETARG_BOOL(2); + /* Search a record */ + k.value = value; + k.is_null = value_is_null; + k.hash_proc = &record->hash_proc; + k.cmp_proc = &record->cmp_proc; - value = variable_get(package_name, var_name, - DATEOID, &is_null, strict); + item = (HashRecordEntry *) hash_search(record->rhash, &k, + HASH_FIND, &found); PG_FREE_IF_COPY(package_name, 0); PG_FREE_IF_COPY(var_name, 1); - if (!is_null) - PG_RETURN_DATUM(value); + + if (found) + { + Assert(!HeapTupleHeaderHasExternal( + (HeapTupleHeader) DatumGetPointer(item->tuple))); + + PG_RETURN_DATUM(item->tuple); + } else PG_RETURN_NULL(); } -Datum -variable_set_jsonb(PG_FUNCTION_ARGS) +/* Structure for variable_select_by_values() */ +typedef struct { - text *package_name; - text *var_name; + Variable *variable; + ArrayIterator iterator; +} VariableIteratorRec; - CHECK_ARGS_FOR_NULL(); +Datum +variable_select_by_values(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + VariableIteratorRec *var; + Datum value; + HashRecordEntry *item; + bool isnull; - package_name = PG_GETARG_TEXT_PP(0); - var_name = PG_GETARG_TEXT_PP(1); + if (SRF_IS_FIRSTCALL()) + { + text *package_name; + text *var_name; + ArrayType *values; + Package *package; + Variable *variable; + MemoryContext oldcontext; - variable_set(package_name, var_name, JSONBOID, - PG_ARGISNULL(2) ? 0 : PG_GETARG_DATUM(2), - PG_ARGISNULL(2)); + /* Checks */ + CHECK_ARGS_FOR_NULL(); - PG_FREE_IF_COPY(package_name, 0); - PG_FREE_IF_COPY(var_name, 1); - PG_RETURN_VOID(); -} + if (PG_ARGISNULL(2)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("array argument can not be NULL"))); -Datum -variable_get_jsonb(PG_FUNCTION_ARGS) -{ - text *package_name; - text *var_name; - bool strict; - bool is_null; - Datum value; + values = PG_GETARG_ARRAYTYPE_P(2); + if (ARR_NDIM(values) > 1) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("searching for elements in multidimensional arrays is not supported"))); - CHECK_ARGS_FOR_NULL(); + /* Get arguments */ + package_name = PG_GETARG_TEXT_PP(0); + var_name = PG_GETARG_TEXT_PP(1); - package_name = PG_GETARG_TEXT_PP(0); - var_name = PG_GETARG_TEXT_PP(1); - strict = PG_GETARG_BOOL(2); + package = getPackage(package_name, true); + variable = getVariableInternal(package, var_name, RECORDOID, true, + true); - value = variable_get(package_name, var_name, - JSONBOID, &is_null, strict); + check_record_key(variable, ARR_ELEMTYPE(values)); - PG_FREE_IF_COPY(package_name, 0); - PG_FREE_IF_COPY(var_name, 1); - if (!is_null) - PG_RETURN_DATUM(value); - else - PG_RETURN_NULL(); + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + funcctx->tuple_desc = GetActualValue(variable).record.tupdesc; + + var = (VariableIteratorRec *) palloc(sizeof(VariableIteratorRec)); + var->iterator = array_create_iterator(values, 0, NULL); + var->variable = variable; + funcctx->user_fctx = var; + + MemoryContextSwitchTo(oldcontext); + PG_FREE_IF_COPY(package_name, 0); + PG_FREE_IF_COPY(var_name, 1); + } + + funcctx = SRF_PERCALL_SETUP(); + var = (VariableIteratorRec *) funcctx->user_fctx; + + /* Get next array element */ + while (array_iterate(var->iterator, &value, &isnull)) + { + HashRecordKey k; + bool found; + RecordVar *record; + + record = &(GetActualValue(var->variable).record); + /* Search a record */ + k.value = value; + k.is_null = isnull; + k.hash_proc = &record->hash_proc; + k.cmp_proc = &record->cmp_proc; + + item = (HashRecordEntry *) hash_search(record->rhash, &k, + HASH_FIND, &found); + if (found) + { + Assert(!HeapTupleHeaderHasExternal( + (HeapTupleHeader) DatumGetPointer(item->tuple))); + SRF_RETURN_NEXT(funcctx, item->tuple); + } + } + + array_free_iterator(var->iterator); + pfree(var); + SRF_RETURN_DONE(funcctx); } +/* + * Check if variable exists. + */ Datum -variable_insert(PG_FUNCTION_ARGS) +variable_exists(PG_FUNCTION_ARGS) { - text *package_name; - text *var_name; - HeapTupleHeader rec; - HashPackageEntry *package; - HashVariableEntry *variable; - - Oid tupType; - int32 tupTypmod; - TupleDesc tupdesc; + text *package_name; + text *var_name; + Package *package; + Variable *variable = NULL; + char key[NAMEDATALEN]; + bool found = false; - /* Checks */ CHECK_ARGS_FOR_NULL(); - if (PG_ARGISNULL(2)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("record argument can not be NULL"))); - - /* Get arguments */ package_name = PG_GETARG_TEXT_PP(0); var_name = PG_GETARG_TEXT_PP(1); - rec = PG_GETARG_HEAPTUPLEHEADER(2); - /* Get cached package */ - if (LastPackage == NULL || - VARSIZE_ANY_EXHDR(package_name) != strlen(LastPackage->name) || - strncmp(VARDATA_ANY(package_name), LastPackage->name, - VARSIZE_ANY_EXHDR(package_name)) != 0) - { - package = getPackageByName(package_name, true, false); - LastPackage = package; - LastVariable = NULL; - } - else - package = LastPackage; - - /* Get cached variable */ - if (LastVariable == NULL || - VARSIZE_ANY_EXHDR(var_name) != strlen(LastVariable->name) || - strncmp(VARDATA_ANY(var_name), LastVariable->name, - VARSIZE_ANY_EXHDR(var_name)) != 0) + package = getPackage(package_name, false); + if (package == NULL) { - variable = getVariableByNameWithType(package->variablesHash, - var_name, RECORDOID, true, false); - LastVariable = variable; - } - else - variable = LastVariable; - - /* Insert a record */ - tupType = HeapTupleHeaderGetTypeId(rec); - tupTypmod = HeapTupleHeaderGetTypMod(rec); - tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); + PG_FREE_IF_COPY(package_name, 0); + PG_FREE_IF_COPY(var_name, 1); - if (!variable->value.record.tupdesc) - { - /* - * This is the first record for the var_name. Initialize attributes. - */ - init_attributes(variable, tupdesc, package->hctx); + PG_RETURN_BOOL(false); } - else - check_attributes(variable, tupdesc); - insert_record(variable, rec); + getKeyFromName(var_name, key); - /* Release resources */ - ReleaseTupleDesc(tupdesc); + if (package->varHashRegular) + variable = (Variable *) hash_search(package->varHashRegular, + key, HASH_FIND, &found); + if (!found && package->varHashTransact) + variable = (Variable *) hash_search(package->varHashTransact, + key, HASH_FIND, &found); PG_FREE_IF_COPY(package_name, 0); PG_FREE_IF_COPY(var_name, 1); - PG_RETURN_VOID(); + PG_RETURN_BOOL(variable ? GetActualState(variable)->is_valid : false); } +/* + * Check if package exists. + */ Datum -variable_update(PG_FUNCTION_ARGS) +package_exists(PG_FUNCTION_ARGS) { - text *package_name; - text *var_name; - HeapTupleHeader rec; - HashPackageEntry *package; - HashVariableEntry *variable; - bool res; - - Oid tupType; - int32 tupTypmod; - TupleDesc tupdesc; - - /* Checks */ - CHECK_ARGS_FOR_NULL(); + text *package_name; + bool res; - if (PG_ARGISNULL(2)) + if (PG_ARGISNULL(0)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("record argument can not be NULL"))); + errmsg("package name can not be NULL"))); - /* Get arguments */ package_name = PG_GETARG_TEXT_PP(0); - var_name = PG_GETARG_TEXT_PP(1); - rec = PG_GETARG_HEAPTUPLEHEADER(2); - - /* Get cached package */ - if (LastPackage == NULL || - VARSIZE_ANY_EXHDR(package_name) != strlen(LastPackage->name) || - strncmp(VARDATA_ANY(package_name), LastPackage->name, - VARSIZE_ANY_EXHDR(package_name)) != 0) - { - package = getPackageByName(package_name, false, true); - LastPackage = package; - LastVariable = NULL; - } - else - package = LastPackage; - - /* Get cached variable */ - if (LastVariable == NULL || - VARSIZE_ANY_EXHDR(var_name) != strlen(LastVariable->name) || - strncmp(VARDATA_ANY(var_name), LastVariable->name, - VARSIZE_ANY_EXHDR(var_name)) != 0) - { - variable = getVariableByNameWithType(package->variablesHash, - var_name, RECORDOID, false, true); - LastVariable = variable; - } - else - variable = LastVariable; - - /* Update a record */ - tupType = HeapTupleHeaderGetTypeId(rec); - tupTypmod = HeapTupleHeaderGetTypMod(rec); - tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); - - check_attributes(variable, tupdesc); - res = update_record(variable, rec); - /* Release resources */ - ReleaseTupleDesc(tupdesc); + res = getPackage(package_name, false) != NULL; PG_FREE_IF_COPY(package_name, 0); - PG_FREE_IF_COPY(var_name, 1); - PG_RETURN_BOOL(res); } +/* + * Remove variable from package by name. + */ Datum -variable_delete(PG_FUNCTION_ARGS) +remove_variable(PG_FUNCTION_ARGS) { - text *package_name; - text *var_name; - Oid value_type; - Datum value; - bool value_is_null = PG_ARGISNULL(2); - HashPackageEntry *package; - HashVariableEntry *variable; - bool res; + text *package_name; + text *var_name; + Package *package; + Variable *variable; + TransObject *transObject; CHECK_ARGS_FOR_NULL(); - /* Get arguments */ package_name = PG_GETARG_TEXT_PP(0); var_name = PG_GETARG_TEXT_PP(1); - if (!value_is_null) - { - value_type = get_fn_expr_argtype(fcinfo->flinfo, 2); - value = PG_GETARG_DATUM(2); - } - else - { - value_type = InvalidOid; - value = 0; - } + package = getPackage(package_name, true); + variable = getVariableInternal(package, var_name, InvalidOid, false, true); - /* Get cached package */ - if (LastPackage == NULL || - VARSIZE_ANY_EXHDR(package_name) != strlen(LastPackage->name) || - strncmp(VARDATA_ANY(package_name), LastPackage->name, - VARSIZE_ANY_EXHDR(package_name)) != 0) + /* Add package to changes list, so we can remove it if it is empty */ + if (!isObjectChangedInCurrentTrans(&package->transObject)) { - package = getPackageByName(package_name, false, true); - LastPackage = package; - LastVariable = NULL; + createSavepoint(&package->transObject, TRANS_PACKAGE); + addToChangesStack(&package->transObject, TRANS_PACKAGE); } - else - package = LastPackage; - /* Get cached variable */ - if (LastVariable == NULL || - VARSIZE_ANY_EXHDR(var_name) != strlen(LastVariable->name) || - strncmp(VARDATA_ANY(var_name), LastVariable->name, - VARSIZE_ANY_EXHDR(var_name)) != 0) + transObject = &variable->transObject; + if (variable->is_transactional) { - variable = getVariableByNameWithType(package->variablesHash, var_name, - RECORDOID, false, true); - LastVariable = variable; + if (!isObjectChangedInCurrentTrans(transObject)) + { + 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) + GetActualState(package)->is_valid = false; } else - variable = LastVariable; + removeObject(&variable->transObject, TRANS_VARIABLE); - /* Delete a record */ - if (!value_is_null) - check_record_key(variable, value_type); - res = delete_record(variable, value, value_is_null); + resetVariablesCache(); - /* Release resources */ PG_FREE_IF_COPY(package_name, 0); PG_FREE_IF_COPY(var_name, 1); - PG_RETURN_BOOL(res); + PG_RETURN_VOID(); } +/* + * Remove package by name. + */ Datum -variable_select(PG_FUNCTION_ARGS) +remove_package(PG_FUNCTION_ARGS) { - FuncCallContext *funcctx; - HASH_SEQ_STATUS *rstat; - HashRecordEntry *item; - - if (SRF_IS_FIRSTCALL()) - { - text *package_name; - text *var_name; - HashPackageEntry *package; - HashVariableEntry *variable; - MemoryContext oldcontext; - RecordVar *record; + Package *package; + text *package_name; - CHECK_ARGS_FOR_NULL(); + if (PG_ARGISNULL(0)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("package name can not be NULL"))); - /* Get arguments */ - package_name = PG_GETARG_TEXT_PP(0); - var_name = PG_GETARG_TEXT_PP(1); + package_name = PG_GETARG_TEXT_PP(0); - package = getPackageByName(package_name, false, true); - variable = getVariableByNameWithType(package->variablesHash, - var_name, RECORDOID, false, true); + 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); - record = &(variable->value.record); + removePackageInternal(package); - funcctx = SRF_FIRSTCALL_INIT(); - oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + resetVariablesCache(); - funcctx->tuple_desc = CreateTupleDescCopy(record->tupdesc); + PG_FREE_IF_COPY(package_name, 0); + PG_RETURN_VOID(); +} - rstat = (HASH_SEQ_STATUS *) palloc0(sizeof(HASH_SEQ_STATUS)); - hash_seq_init(rstat, record->rhash); - funcctx->user_fctx = rstat; +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); - MemoryContextSwitchTo(oldcontext); - PG_FREE_IF_COPY(package_name, 0); - PG_FREE_IF_COPY(var_name, 1); + while ((variable = + (Variable *) hash_seq_search(&vstat)) != NULL) + { + if (GetActualState(variable)->is_valid) + variable->is_deleted = true; + } + } } - funcctx = SRF_PERCALL_SETUP(); - - /* Get next hash record */ - rstat = (HASH_SEQ_STATUS *) funcctx->user_fctx; - item = (HashRecordEntry *) hash_seq_search(rstat); - if (item != NULL) + /* All regular variables will be freed */ + if (package->hctxRegular) { - Datum result; - - result = HeapTupleGetDatum(item->tuple); - - SRF_RETURN_NEXT(funcctx, result); + MemoryContextDelete(package->hctxRegular); + package->hctxRegular = NULL; + package->varHashRegular = NULL; } - else + + /* Add to changes list */ + transObject = &package->transObject; + if (!isObjectChangedInCurrentTrans(transObject)) { - pfree(rstat); - SRF_RETURN_DONE(funcctx); + createSavepoint(transObject, TRANS_PACKAGE); + addToChangesStack(transObject, TRANS_PACKAGE); } + GetActualState(package)->is_valid = false; + GetPackState(package)->trans_var_num = 0; } -Datum -variable_select_by_value(PG_FUNCTION_ARGS) -{ - text *package_name; - text *var_name; - Oid value_type; - Datum value; - bool value_is_null = PG_ARGISNULL(2); - HashPackageEntry *package; - HashVariableEntry *variable; - - HashRecordEntry *item; - RecordVar *record; - HashRecordKey k; - bool found; - - CHECK_ARGS_FOR_NULL(); - - /* Get arguments */ - package_name = PG_GETARG_TEXT_PP(0); - var_name = PG_GETARG_TEXT_PP(1); - - if (!value_is_null) - { - value_type = get_fn_expr_argtype(fcinfo->flinfo, 2); - value = PG_GETARG_DATUM(2); - } - else - { - value_type = InvalidOid; - value = 0; - } - - package = getPackageByName(package_name, false, true); - variable = getVariableByNameWithType(package->variablesHash, - var_name, RECORDOID, false, true); - - if (!value_is_null) - check_record_key(variable, value_type); - - record = &(variable->value.record); - - /* Search a record */ - k.value = value; - k.is_null = value_is_null; - k.hash_proc = &record->hash_proc; - k.cmp_proc = &record->cmp_proc; - - item = (HashRecordEntry *) hash_search(record->rhash, &k, - HASH_FIND, &found); - - PG_FREE_IF_COPY(package_name, 0); - PG_FREE_IF_COPY(var_name, 1); - - if (found) - PG_RETURN_DATUM(HeapTupleGetDatum(item->tuple)); - else - PG_RETURN_NULL(); -} - -/* Structure for variable_select_by_values() */ -typedef struct -{ - HashVariableEntry *variable; - ArrayIterator iterator; -} VariableIteratorRec; - -Datum -variable_select_by_values(PG_FUNCTION_ARGS) -{ - FuncCallContext *funcctx; - VariableIteratorRec *var; - Datum value; - HashRecordEntry *item; - bool isnull; - - if (SRF_IS_FIRSTCALL()) - { - text *package_name; - text *var_name; - ArrayType *values; - HashPackageEntry *package; - HashVariableEntry *variable; - MemoryContext oldcontext; - - /* Checks */ - CHECK_ARGS_FOR_NULL(); - - if (PG_ARGISNULL(2)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("array argument can not be NULL"))); - - values = PG_GETARG_ARRAYTYPE_P(2); - if (ARR_NDIM(values) > 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("searching for elements in multidimensional arrays is not supported"))); - - /* Get arguments */ - package_name = PG_GETARG_TEXT_PP(0); - var_name = PG_GETARG_TEXT_PP(1); - - package = getPackageByName(package_name, false, true); - variable = getVariableByNameWithType(package->variablesHash, - var_name, RECORDOID, false, true); - - check_record_key(variable, ARR_ELEMTYPE(values)); - - funcctx = SRF_FIRSTCALL_INIT(); - oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); - - funcctx->tuple_desc = CreateTupleDescCopy(variable->value.record.tupdesc); - - var = (VariableIteratorRec *) palloc(sizeof(VariableIteratorRec)); - var->iterator = array_create_iterator(values, 0, NULL); - var->variable = variable; - funcctx->user_fctx = var; - - MemoryContextSwitchTo(oldcontext); - PG_FREE_IF_COPY(package_name, 0); - PG_FREE_IF_COPY(var_name, 1); - } - - funcctx = SRF_PERCALL_SETUP(); - var = (VariableIteratorRec *) funcctx->user_fctx; - - /* Get next array element */ - while (array_iterate(var->iterator, &value, &isnull)) - { - HashRecordKey k; - bool found; - RecordVar *record; - - record = &(var->variable->value.record); - /* Search a record */ - k.value = value; - k.is_null = isnull; - k.hash_proc = &record->hash_proc; - k.cmp_proc = &record->cmp_proc; - - item = (HashRecordEntry *) hash_search(record->rhash, &k, - HASH_FIND, &found); - if (found) - { - Datum result; - - result = HeapTupleGetDatum(item->tuple); - - SRF_RETURN_NEXT(funcctx, result); - } - } - - array_free_iterator(var->iterator); - pfree(var); - SRF_RETURN_DONE(funcctx); -} - -static void -clean_variable(HashVariableEntry *variable) -{ - if (variable->typid == RECORDOID) - clean_records(variable); - else if (variable->value.scalar.typbyval == false && - variable->value.scalar.is_null == false) - pfree(DatumGetPointer(variable->value.scalar.value)); -} - -/* - * Check if variable exists. - */ -Datum -variable_exists(PG_FUNCTION_ARGS) -{ - text *package_name; - text *var_name; - HashPackageEntry *package; - char key[NAMEDATALEN]; - bool found; - - CHECK_ARGS_FOR_NULL(); - - package_name = PG_GETARG_TEXT_PP(0); - var_name = PG_GETARG_TEXT_PP(1); - - package = getPackageByName(package_name, false, false); - if (package == NULL) - { - PG_FREE_IF_COPY(package_name, 0); - PG_FREE_IF_COPY(var_name, 1); - - PG_RETURN_BOOL(false); - } - - getKeyFromName(var_name, key); - - hash_search(package->variablesHash, key, HASH_FIND, &found); - - PG_FREE_IF_COPY(package_name, 0); - PG_FREE_IF_COPY(var_name, 1); - - PG_RETURN_BOOL(found); -} - -/* - * Check if package exists. - */ -Datum -package_exists(PG_FUNCTION_ARGS) -{ - text *package_name; - bool res; - - if (PG_ARGISNULL(0)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("package name can not be NULL"))); - - package_name = PG_GETARG_TEXT_PP(0); - - res = getPackageByName(package_name, false, false) != NULL; - - PG_FREE_IF_COPY(package_name, 0); - PG_RETURN_BOOL(res); -} - -/* - * Remove variable from package by name. - */ -Datum -remove_variable(PG_FUNCTION_ARGS) +/* Check if package has any valid variables */ +static bool +isPackageEmpty(Package *package) { - text *package_name; - text *var_name; - HashPackageEntry *package; - HashVariableEntry *variable; - bool found; - char key[NAMEDATALEN]; - - CHECK_ARGS_FOR_NULL(); - - package_name = PG_GETARG_TEXT_PP(0); - var_name = PG_GETARG_TEXT_PP(1); - - package = getPackageByName(package_name, false, true); - getKeyFromName(var_name, key); - - variable = (HashVariableEntry *) hash_search(package->variablesHash, - key, HASH_REMOVE, &found); - if (!found) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("unrecognized variable \"%s\"", key))); - - /* Remove variable from cache */ - LastVariable = NULL; - - clean_variable(variable); + int var_num = GetPackState(package)->trans_var_num; - PG_FREE_IF_COPY(package_name, 0); - PG_FREE_IF_COPY(var_name, 1); + if (package->varHashRegular) + var_num += hash_get_num_entries(package->varHashRegular); - PG_RETURN_VOID(); + return var_num == 0; } /* - * Remove package by name. + * Reset cache variables to their default values. It is necessary to do in case + * of some changes: removing, rollbacking, etc. */ -Datum -remove_package(PG_FUNCTION_ARGS) +static void +resetVariablesCache(void) { - text *package_name; - HashPackageEntry *package; - bool found; - char key[NAMEDATALEN]; - - if (PG_ARGISNULL(0)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("package name can not be NULL"))); - - package_name = PG_GETARG_TEXT_PP(0); - getKeyFromName(package_name, key); - - if (!packagesHash) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("unrecognized package \"%s\"", key))); - - package = (HashPackageEntry *) hash_search(packagesHash, key, - HASH_REMOVE, &found); - if (!found) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("unrecognized package \"%s\"", key))); - /* Remove package and variable from cache */ LastPackage = NULL; LastVariable = NULL; - - hash_destroy(package->variablesHash); - /* All variables will be freed */ - MemoryContextDelete(package->hctx); - - PG_FREE_IF_COPY(package_name, 0); - - PG_RETURN_VOID(); } /* * Remove all packages and variables. + * Memory context will be released after committing. */ Datum remove_packages(PG_FUNCTION_ARGS) { - /* There is not any packages and variables */ + Package *package; + HASH_SEQ_STATUS pstat; + + /* There is no any packages and variables */ if (packagesHash == NULL) PG_RETURN_VOID(); - /* Remove package and variable from cache */ - LastPackage = NULL; - LastVariable = NULL; + /* + * 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); - hash_destroy(packagesHash); - /* All packages and variables will be freed */ - MemoryContextDelete(ModuleContext); + /* Get packages list */ + hash_seq_init(&pstat, packagesHash); + while ((package = (Package *) hash_seq_search(&pstat)) != NULL) + { + removePackageInternal(package); + } - packagesHash = NULL; - ModuleContext = NULL; + resetVariablesCache(); PG_RETURN_VOID(); } @@ -1142,9 +1513,10 @@ remove_packages(PG_FUNCTION_ARGS) */ typedef struct { - char *package; - char *variable; -} VariableRec; + char *package; + char *variable; + bool is_transactional; +} VariableRec; /* * Get list of assigned packages and variables. @@ -1152,13 +1524,13 @@ typedef struct Datum get_packages_and_variables(PG_FUNCTION_ARGS) { - FuncCallContext *funcctx; - VariableRec *recs; - MemoryContext oldcontext; + FuncCallContext *funcctx; + VariableRec *recs; + MemoryContext oldcontext; if (SRF_IS_FIRSTCALL()) { - TupleDesc tupdesc; + TupleDesc tupdesc; funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); @@ -1178,37 +1550,52 @@ get_packages_and_variables(PG_FUNCTION_ARGS) */ if (packagesHash) { - HashPackageEntry *package; - HASH_SEQ_STATUS pstat; - int mRecs = NUMVARIABLES, - nRecs = 0; + Package *package; + HASH_SEQ_STATUS pstat; + int mRecs = NUMVARIABLES, + nRecs = 0; recs = (VariableRec *) palloc0(sizeof(VariableRec) * mRecs); /* Get packages list */ hash_seq_init(&pstat, packagesHash); - while ((package = - (HashPackageEntry *) hash_seq_search(&pstat)) != NULL) + while ((package = (Package *) hash_seq_search(&pstat)) != NULL) { - HashVariableEntry *variable; - HASH_SEQ_STATUS vstat; + Variable *variable; + HASH_SEQ_STATUS vstat; + int i; + + /* Skip packages marked as deleted */ + if (!GetActualState(package)->is_valid) + continue; /* Get variables list for package */ - hash_seq_init(&vstat, package->variablesHash); - while ((variable = - (HashVariableEntry *) hash_seq_search(&vstat)) != NULL) + for (i = 0; i < 2; i++) { - /* Resize recs if necessary */ - if (nRecs >= mRecs) + HTAB *htab = pack_htab(package, i); + + if (!htab) + continue; + hash_seq_init(&vstat, htab); + while ((variable = + (Variable *) hash_seq_search(&vstat)) != NULL) { - mRecs *= 2; - recs = (VariableRec *) repalloc(recs, - sizeof(VariableRec) * mRecs); + if (!GetActualState(variable)->is_valid) + continue; + + /* Resize recs if necessary */ + if (nRecs >= mRecs) + { + mRecs *= 2; + recs = (VariableRec *) repalloc(recs, + sizeof(VariableRec) * mRecs); + } + + recs[nRecs].package = GetName(package); + recs[nRecs].variable = GetName(variable); + recs[nRecs].is_transactional = variable->is_transactional; + nRecs++; } - - recs[nRecs].package = package->name; - recs[nRecs].variable = variable->name; - nRecs++; } } @@ -1228,295 +1615,1502 @@ get_packages_and_variables(PG_FUNCTION_ARGS) if (funcctx->call_cntr < funcctx->max_calls) { - Datum values[2]; - bool nulls[2]; + Datum values[3]; + bool nulls[3]; HeapTuple tuple; Datum result; int i = funcctx->call_cntr; - memset(nulls, 0, sizeof(nulls)); + memset(nulls, 0, sizeof(nulls)); + + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + values[0] = PointerGetDatum(cstring_to_text(recs[i].package)); + values[1] = PointerGetDatum(cstring_to_text(recs[i].variable)); + values[2] = recs[i].is_transactional; + + tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); + result = HeapTupleGetDatum(tuple); + + MemoryContextSwitchTo(oldcontext); + + SRF_RETURN_NEXT(funcctx, result); + } + else + SRF_RETURN_DONE(funcctx); +} + +static void +getMemoryTotalSpace(MemoryContext context, int level, Size *totalspace) +{ +#if PG_VERSION_NUM >= 90600 + MemoryContext child; + MemoryContextCounters totals; + + Assert(MemoryContextIsValid(context)); + + /* Examine the context itself */ + memset(&totals, 0, sizeof(totals)); +#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); +#endif + *totalspace += totals.totalspace; + + /* + * Examine children. + */ + for (child = context->firstchild; child != NULL; child = child->nextchild) + getMemoryTotalSpace(child, level + 1, totalspace); +#else + *totalspace = 0; +#endif +} + +/* + * Get list of assigned packages and used memory in bytes. + */ +Datum +get_packages_stats(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + MemoryContext oldcontext; + HASH_SEQ_STATUS *rstat; + Package *package; + + if (SRF_IS_FIRSTCALL()) + { + TupleDesc tupdesc; + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + /* Build a tuple descriptor for our result type */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("function returning record called in context " + "that cannot accept type record"))); + + funcctx->tuple_desc = BlessTupleDesc(tupdesc); + + /* + * Get all packages and variables names and save them into + * funcctx->user_fctx. + */ + if (packagesHash) + { + MemoryContext ctx; + PackageStatEntry *entry; + + ctx = MemoryContextSwitchTo(TopTransactionContext); + rstat = (HASH_SEQ_STATUS *) palloc0(sizeof(HASH_SEQ_STATUS)); + /* Get packages list */ + hash_seq_init(rstat, packagesHash); + + 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; + + MemoryContextSwitchTo(oldcontext); + } + + funcctx = SRF_PERCALL_SETUP(); + if (funcctx->user_fctx == NULL) + SRF_RETURN_DONE(funcctx); + + /* Get packages list */ + rstat = (HASH_SEQ_STATUS *) funcctx->user_fctx; + + package = (Package *) hash_seq_search(rstat); + if (package != NULL) + { + Datum values[2]; + bool nulls[2]; + HeapTuple tuple; + Datum result; + Size totalSpace = 0, + regularSpace = 0, + transactSpace = 0; + + memset(nulls, 0, sizeof(nulls)); + + /* Fill data */ + values[0] = PointerGetDatum(cstring_to_text(GetName(package))); + + if (package->hctxRegular) + getMemoryTotalSpace(package->hctxRegular, 0, ®ularSpace); + if (package->hctxTransact) + getMemoryTotalSpace(package->hctxTransact, 0, &transactSpace); + + totalSpace = regularSpace + transactSpace; + values[1] = Int64GetDatum(totalSpace); + + /* Data are ready */ + tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); + result = HeapTupleGetDatum(tuple); + + SRF_RETURN_NEXT(funcctx, result); + } + else + { + remove_packages_status(&packages_stats, rstat); + SRF_RETURN_DONE(funcctx); + } +} + +/* + * Static functions + */ + +static void +getKeyFromName(text *name, char *key) +{ + int key_len = VARSIZE_ANY_EXHDR(name); + + if (key_len >= NAMEDATALEN - 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("name \"%.*s\" is too long", + key_len, VARDATA_ANY(name)))); + + strncpy(key, VARDATA_ANY(name), key_len); + key[key_len] = '\0'; +} + +static void +ensurePackagesHashExists(void) +{ + HASHCTL ctl; + + if (packagesHash) + return; + + ModuleContext = AllocSetContextCreate(CacheMemoryContext, + PGV_MCXT_MAIN, + ALLOCSET_DEFAULT_SIZES); + + ctl.keysize = NAMEDATALEN; + ctl.entrysize = sizeof(Package); + ctl.hcxt = ModuleContext; + + packagesHash = hash_create("Packages hash", + NUMPACKAGES, &ctl, + HASH_ELEM | +#if PG_VERSION_NUM >= 140000 + HASH_STRINGS | +#endif + HASH_CONTEXT); +} + +/* + * Initialize a hash table with proper vars type + */ +static void +makePackHTAB(Package *package, bool is_trans) +{ + HASHCTL ctl; + char hash_name[BUFSIZ]; + HTAB **htab; + MemoryContext *context; + + htab = is_trans ? &package->varHashTransact : &package->varHashRegular; + context = is_trans ? &package->hctxTransact : &package->hctxRegular; + + *context = AllocSetContextCreate(ModuleContext, PGV_MCXT_VARS, + ALLOCSET_DEFAULT_SIZES); + + snprintf(hash_name, BUFSIZ, "%s variables hash for package \"%s\"", + is_trans ? "Transactional" : "Regular", GetName(package)); + ctl.keysize = NAMEDATALEN; + ctl.entrysize = sizeof(Variable); + ctl.hcxt = *context; + + *htab = hash_create(hash_name, NUMVARIABLES, &ctl, + HASH_ELEM | +#if PG_VERSION_NUM >= 140000 + HASH_STRINGS | +#endif + HASH_CONTEXT); +} + +static void +initObjectHistory(TransObject *object, TransObjectType type) +{ + /* Initialize history */ + TransState *state; + int size; + + size = (type == TRANS_PACKAGE ? sizeof(PackState) : sizeof(VarState)); + dlist_init(&object->states); + state = MemoryContextAllocZero(ModuleContext, size); + dlist_push_head(&object->states, &(state->node)); + + /* Initialize state */ + state->is_valid = true; + if (type == TRANS_PACKAGE) + ((PackState *) state)->trans_var_num = 0; + else + { + Variable *variable = (Variable *) object; + + if (!variable->is_record) + { + VarState *varState = (VarState *) state; + ScalarVar *scalar = &(varState->value.scalar); + + get_typlenbyval(variable->typid, &scalar->typlen, + &scalar->typbyval); + varState->value.scalar.is_null = true; + } + } +} + +static Package * +getPackage(text *name, bool strict) +{ + Package *package; + char key[NAMEDATALEN]; + bool found; + + getKeyFromName(name, key); + + /* Find a package entry */ + if (packagesHash) + { + package = (Package *) hash_search(packagesHash, key, HASH_FIND, &found); + + if (found && GetActualState(package)->is_valid) + { + Assert(GetPackState(package)->trans_var_num + + numOfRegVars(package) > 0); + return package; + } + } + /* Package not found or it's current state is "invalid" */ + if (strict) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized package \"%s\"", key))); + + return NULL; +} + +static Package * +createPackage(text *name, bool is_trans) +{ + Package *package; + char key[NAMEDATALEN]; + bool found; + + getKeyFromName(name, key); + ensurePackagesHashExists(); + + /* Find or create a package entry */ + package = (Package *) hash_search(packagesHash, key, HASH_ENTER, &found); + + if (found) + { + TransObject *transObj = &package->transObject; + + if (!isObjectChangedInCurrentTrans(transObj)) + createSavepoint(transObj, TRANS_PACKAGE); + + if (!GetActualState(package)->is_valid) + { + HASH_SEQ_STATUS vstat; + Variable *variable; + + GetActualState(package)->is_valid = true; + /* Mark all transactional variables in package as removed */ + if (package->varHashTransact) + { + hash_seq_init(&vstat, package->varHashTransact); + while ((variable = + (Variable *) hash_seq_search(&vstat)) != NULL) + { + transObj = &variable->transObject; + + if (!isObjectChangedInCurrentTrans(transObj)) + { + createSavepoint(transObj, TRANS_VARIABLE); + addToChangesStack(transObj, TRANS_VARIABLE); + } + GetActualState(variable)->is_valid = false; + } + } + } + } + else + { + /* Package entry was created, so initialize it. */ + package->varHashRegular = NULL; + package->varHashTransact = NULL; + package->hctxRegular = NULL; + package->hctxTransact = NULL; +#ifdef PGPRO_EE + package->context = NULL; +#endif + initObjectHistory(&package->transObject, TRANS_PACKAGE); + } + + /* Create corresponding HTAB if not exists */ + if (!pack_htab(package, is_trans)) + makePackHTAB(package, is_trans); + /* Add to changes list */ + if (!isObjectChangedInCurrentTrans(&package->transObject)) + addToChangesStack(&package->transObject, TRANS_PACKAGE); + + return package; +} + +/* + * Return a pointer to existing variable. + * Function is useful to request a value of existing variable and + * flag 'is_transactional' of this variable is unknown. + */ +static Variable * +getVariableInternal(Package *package, text *name, Oid typid, bool is_record, + bool strict) +{ + Variable *variable = NULL; + char key[NAMEDATALEN]; + bool found = false; + + getKeyFromName(name, key); + + if (package->varHashRegular) + variable = (Variable *) hash_search(package->varHashRegular, + key, HASH_FIND, &found); + if (!found && package->varHashTransact) + variable = (Variable *) hash_search(package->varHashTransact, + key, HASH_FIND, &found); + + /* Check variable type */ + if (found) + { + if (typid != InvalidOid) + { + if (variable->typid != typid) + { + char *var_type = DatumGetCString( + DirectFunctionCall1(regtypeout, + ObjectIdGetDatum(variable->typid))); + + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_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", + key, is_record ? "record" : "scalar"))); + } + if (!GetActualState(variable)->is_valid && strict) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized variable \"%s\"", key))); + } + else + { + if (strict) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized variable \"%s\"", key))); + } + + return variable; +} + +/* + * Create a variable or return a pointer to existing one. + * Function is useful to set new value to variable and flag 'is_transactional' + * is known. + */ +static Variable * +createVariableInternal(Package *package, text *name, Oid typid, bool is_record, + bool is_transactional) +{ + Variable *variable; + TransObject *transObject; + HTAB *htab; + char key[NAMEDATALEN]; + bool found; + + getKeyFromName(name, key); + + /* + * Reverse check: for non-transactional variable search in regular table + * and vice versa. + */ + htab = pack_htab(package, !is_transactional); + if (htab) + { + hash_search(htab, key, HASH_FIND, &found); + if (found) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("variable \"%s\" already created as %sTRANSACTIONAL", + key, is_transactional ? "NOT " : ""))); + } + + variable = (Variable *) hash_search(pack_htab(package, is_transactional), + key, HASH_ENTER, &found); + Assert(variable); + transObject = &variable->transObject; + + /* Check variable type */ + if (found) + { + if (variable->typid != typid) + { + char *var_type = DatumGetCString(DirectFunctionCall1(regtypeout, + ObjectIdGetDatum(variable->typid))); + + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_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", + key, is_record ? "record" : "scalar"))); + + /* + * Savepoint must be created when variable changed in current + * transaction. For each transaction level there should be a + * corresponding savepoint. New value should be stored in a last + * state. + */ + if (is_transactional && + !isObjectChangedInCurrentTrans(transObject)) + { + createSavepoint(transObject, TRANS_VARIABLE); + } + } + else + { + /* Variable entry was created, so initialize new variable. */ + variable->typid = typid; + 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)) + { + createSavepoint(&package->transObject, TRANS_PACKAGE); + addToChangesStack(&package->transObject, TRANS_PACKAGE); + } + } + + /* + * 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)) + GetPackState(package)->trans_var_num++; + GetActualState(variable)->is_valid = true; + + /* If it is necessary, put variable to changedVars */ + if (is_transactional) + addToChangesStack(transObject, TRANS_VARIABLE); + + return variable; +} + +static void +copyValue(VarState *src, VarState *dest, Variable *destVar) +{ + MemoryContext oldcxt; + + oldcxt = MemoryContextSwitchTo(destVar->package->hctxTransact); + + if (destVar->is_record) + /* copy record value */ + { + HASH_SEQ_STATUS rstat; + HashRecordEntry *item_src; + RecordVar *record_src = &src->value.record; + RecordVar *record_dest = &dest->value.record; + + init_record(record_dest, record_src->tupdesc, destVar); + + /* Copy previous history entry into the new one */ + hash_seq_init(&rstat, record_src->rhash); + while ((item_src = (HashRecordEntry *) hash_seq_search(&rstat)) != NULL) + insert_record_copy(record_dest, item_src->tuple, destVar); + } + else + /* copy scalar value */ + { + ScalarVar *scalar = &dest->value.scalar; + + *scalar = src->value.scalar; + if (!scalar->is_null) + scalar->value = datumCopy(src->value.scalar.value, + scalar->typbyval, scalar->typlen); + else + scalar->value = 0; + } + + MemoryContextSwitchTo(oldcxt); +} + +static void +freeValue(VarState *varstate, bool is_record) +{ + if (is_record && varstate->value.record.hctx) + { + /* All records will be freed */ + MemoryContextDelete(varstate->value.record.hctx); + } + else if (!is_record && varstate->value.scalar.typbyval == false && + varstate->value.scalar.is_null == false && + varstate->value.scalar.value) + pfree(DatumGetPointer(varstate->value.scalar.value)); +} + +static void +removeState(TransObject *object, TransObjectType type, TransState *stateToDelete) +{ + if (type == TRANS_VARIABLE) + { + Variable *var = (Variable *) object; + + freeValue((VarState *) stateToDelete, var->is_record); + } + dlist_delete(&stateToDelete->node); + pfree(stateToDelete); +} + +/* Remove package or variable (either transactional or regular) */ +bool +removeObject(TransObject *object, TransObjectType type) +{ + bool found; + HTAB *hash; + 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 */ + if (package->hctxRegular) + 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; + + package = var->package; + hash = var->is_transactional ? + 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 package if it became empty */ + if (type == TRANS_VARIABLE && isPackageEmpty(package)) + { + Assert(isObjectChangedInCurrentTrans(&package->transObject)); + GetActualState(&package->transObject)->is_valid = false; + } + + resetVariablesCache(); + + return true; +} + +/* + * Create a new state of object + */ +static void +createSavepoint(TransObject *object, TransObjectType type) +{ + TransState *newState, + *prevState; + + prevState = GetActualState(object); + if (type == TRANS_PACKAGE) + { + newState = (TransState *) MemoryContextAllocZero(ModuleContext, + sizeof(PackState)); + ((PackState *) newState)->trans_var_num = ((PackState *) prevState)->trans_var_num; + } + else + { + Variable *var = (Variable *) object; + + newState = (TransState *) MemoryContextAllocZero(var->package->hctxTransact, + sizeof(VarState)); + copyValue((VarState *) prevState, (VarState *) newState, var); + } + dlist_push_head(&object->states, &newState->node); + newState->is_valid = prevState->is_valid; +} + +static int +numOfRegVars(Package *package) +{ + if (package->varHashRegular) + return hash_get_num_entries(package->varHashRegular); + else + return 0; +} + +/* + * Rollback object to its previous state + */ +static void +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); + + if (type == TRANS_PACKAGE) + { + /* If there is no more states... */ + if (dlist_is_empty(&object->states)) + { + /* ...but object is a package and has some regular variables... */ + if (numOfRegVars((Package *) object) > 0) + { + /* ...create a new state to make package valid. */ + initObjectHistory(object, type); +#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); + } + else + /* ...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)) + { + if (dlist_is_empty(changesStack)) + { + removeObject(object, type); + return; + } + else if (!isObjectChangedInUpperTrans(object) && + !dlist_is_empty(changesStack)) + { + createSavepoint(object, type); + addToChangesStackUpperLevel(object, type); +#ifdef PGPRO_EE + GetActualState(object)->levels.atxlevel = getNestLevelATX(); +#endif + GetActualState(object)->levels.level = GetCurrentTransactionNestLevel() - 1; + } + GetActualState(object)->is_valid = false; + } + } + else + { + if (dlist_is_empty(&object->states)) + /* Remove a variable if it is no longer needed. */ + removeObject(object, type); + } +} + +/* + * Remove previous state of object + */ +static void +releaseSavepoint(TransObject *object, TransObjectType type, bool sub) +{ + dlist_head *states = &object->states; + + 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 (!GetActualState(object)->is_valid && + (!dlist_has_next(states, dlist_head_node(states)) || + dlist_is_empty(changesStack)) + ) + { + if (removeObject(object, type)) + return; + } + + /* + * 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. + */ + else if (!dlist_is_empty(changesStack)) + addToChangesStackUpperLevel(object, type); + + /* Change subxact level due to release */ + 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 +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 + */ + 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); +} + +/* + * Check if object was changed in current transaction level + */ +static bool +isObjectChangedInCurrentTrans(TransObject *object) +{ + TransState *state; + + if (!changesStack) + return false; - oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + state = GetActualState(object); + return +#ifdef PGPRO_EE - values[0] = PointerGetDatum(cstring_to_text(recs[i].package)); - values[1] = PointerGetDatum(cstring_to_text(recs[i].variable)); + /* + * We should separate states with equal subxacts but with different ATX + * level + */ + state->levels.atxlevel == getNestLevelATX() && +#endif + state->levels.level == GetCurrentTransactionNestLevel(); +} - tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); - result = HeapTupleGetDatum(tuple); +/* + * Check if object was changed in parent transaction level + */ +static bool +isObjectChangedInUpperTrans(TransObject *object) +{ + TransState *cur_state, + *prev_state; - MemoryContextSwitchTo(oldcontext); + cur_state = GetActualState(object); + if (dlist_has_next(&object->states, &cur_state->node) && +#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 +#ifdef PGPRO_EE - SRF_RETURN_NEXT(funcctx, result); + /* + * 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 - SRF_RETURN_DONE(funcctx); + 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; } +/* + * Create a new list of variables, changed in current transaction level + */ static void -getMemoryTotalSpace(MemoryContext context, int level, Size *totalspace) +pushChangesStack(void) { -#if PG_VERSION_NUM >= 90600 - MemoryContext child; - MemoryContextCounters totals; - - AssertArg(MemoryContextIsValid(context)); - - /* Examine the context itself */ - memset(&totals, 0, sizeof(totals)); - (*context->methods->stats) (context, level, false, &totals); - *totalspace += totals.totalspace; + MemoryContext oldcxt; + ChangesStackNode *csn; /* - * Examine children. + * Initialize changesStack and create MemoryContext for it if not done + * before. */ - for (child = context->firstchild; child != NULL; child = child->nextchild) - getMemoryTotalSpace(child, level + 1, totalspace); -#else - *totalspace = 0; -#endif + if (!changesStackContext) + changesStackContext = AllocSetContextCreate(ModuleContext, + PGV_MCXT_STACK, + ALLOCSET_START_SMALL_SIZES); + Assert(changesStackContext); + oldcxt = MemoryContextSwitchTo(changesStackContext); + + if (!changesStack) + { + changesStack = palloc0(sizeof(dlist_head)); + dlist_init(changesStack); + } + Assert(changesStack); + csn = palloc0(sizeof(ChangesStackNode)); + csn->changedVarsList = palloc0(sizeof(dlist_head)); + csn->changedPacksList = palloc0(sizeof(dlist_head)); + + csn->ctx = AllocSetContextCreate(changesStackContext, + PGV_MCXT_STACK_NODE, + ALLOCSET_START_SMALL_SIZES); + + dlist_init(csn->changedVarsList); + dlist_init(csn->changedPacksList); + dlist_push_head(changesStack, &csn->node); + + MemoryContextSwitchTo(oldcxt); } /* - * Get list of assigned packages and used memory in bytes. + * Create a changesStack with the required depth. */ -Datum -get_packages_stats(PG_FUNCTION_ARGS) +static void +prepareChangesStack(void) { - FuncCallContext *funcctx; - MemoryContext oldcontext; - HASH_SEQ_STATUS *pstat; - HashPackageEntry *package; - - if (SRF_IS_FIRSTCALL()) + if (!changesStack) { - TupleDesc tupdesc; + int level = GetCurrentTransactionNestLevel(); - funcctx = SRF_FIRSTCALL_INIT(); - oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + while (level-- > 0) + { + pushChangesStack(); + } + } +} - /* Build a tuple descriptor for our result type */ - if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("function returning record called in context " - "that cannot accept type record"))); +/* + * Initialize an instance of ChangedObject datatype + */ +static inline ChangedObject * +makeChangedObject(TransObject *object, MemoryContext ctx) +{ + ChangedObject *co; - funcctx->tuple_desc = BlessTupleDesc(tupdesc); + co = MemoryContextAllocZero(ctx, sizeof(ChangedObject)); + co->object = object; - /* - * Get all packages and variables names and save them into - * funcctx->user_fctx. - */ - if (packagesHash) - { - pstat = (HASH_SEQ_STATUS *) palloc0(sizeof(HASH_SEQ_STATUS)); - /* Get packages list */ - hash_seq_init(pstat, packagesHash); + return co; +} - funcctx->user_fctx = pstat; - } - else - funcctx->user_fctx = NULL; +/* + * Add an object to the list of created, removed, or changed objects + * in current transaction level + */ +static void +addToChangesStack(TransObject *object, TransObjectType type) +{ + prepareChangesStack(); - MemoryContextSwitchTo(oldcontext); + if (!isObjectChangedInCurrentTrans(object)) + { + ChangesStackNode *csn; + ChangedObject *co; + + csn = get_actual_changes_list(); + co = makeChangedObject(object, csn->ctx); + dlist_push_head(type == TRANS_PACKAGE ? csn->changedPacksList : + csn->changedVarsList, &co->node); + + /* Give this object current subxact level */ + GetActualState(object)->levels.level = GetCurrentTransactionNestLevel(); +#ifdef PGPRO_EE + GetActualState(object)->levels.atxlevel = getNestLevelATX(); +#endif } +} - funcctx = SRF_PERCALL_SETUP(); - if (funcctx->user_fctx == NULL) - SRF_RETURN_DONE(funcctx); +/* + * Possible actions on variables. + * Savepoints are created in setters so we don't need a CREATE_SAVEPOINT action. + */ +typedef enum Action +{ + RELEASE_SAVEPOINT, + ROLLBACK_TO_SAVEPOINT +} Action; - /* Get packages list */ - pstat = (HASH_SEQ_STATUS *) funcctx->user_fctx; +/* + * Apply savepoint actions on list of variables or packages. + */ +static void +applyAction(Action action, TransObjectType type, dlist_head *list, bool sub) +{ + dlist_iter iter; - package = (HashPackageEntry *) hash_seq_search(pstat); - if (package != NULL) + dlist_foreach(iter, list) { - Datum values[2]; - bool nulls[2]; - HeapTuple tuple; - Datum result; - Size totalspace = 0; + ChangedObject *co = dlist_container(ChangedObject, node, iter.cur); + TransObject *object = co->object; - memset(nulls, 0, sizeof(nulls)); + 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; - /* Fill data */ - values[0] = PointerGetDatum(cstring_to_text(package->name)); + if (!GetActualState(package)->is_valid) + GetActualState(variable)->is_valid = false; + } + + releaseSavepoint(object, type, sub); + break; + } + } +} - getMemoryTotalSpace(package->hctx, 0, &totalspace); - values[1] = Int64GetDatum(totalspace); +/* + * Iterate variables and packages from list of changes and + * apply corresponding action on them + */ +static void +processChanges(Action action, bool sub) +{ + ChangesStackNode *bottom_list; - /* Data are ready */ - tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); - result = HeapTupleGetDatum(tuple); + Assert(changesStack && changesStackContext); + /* List removed from stack but we still can use it */ + bottom_list = dlist_container(ChangesStackNode, node, + dlist_pop_head_node(changesStack)); - SRF_RETURN_NEXT(funcctx, result); + 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); + /* Remove the stack if it is empty */ + if (dlist_is_empty(changesStack)) + { + MemoryContextDelete(changesStackContext); + changesStack = NULL; + changesStackContext = NULL; } - else + if (!hash_get_num_entries(packagesHash)) { - pfree(pstat); - SRF_RETURN_DONE(funcctx); + MemoryContextDelete(ModuleContext); + packagesHash = NULL; + ModuleContext = NULL; + resetVariablesCache(); + changesStack = NULL; + changesStackContext = NULL; } } /* - * Static functions + * ATX and connection pooling are not compatible with pg_variables. */ - static void -getKeyFromName(text *name, char *key) +compatibility_check(void) { - int key_len = VARSIZE_ANY_EXHDR(name); - - if (key_len >= NAMEDATALEN - 1) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("name \"%.*s\" is too long", - key_len, VARDATA_ANY(name)))); - - strncpy(key, VARDATA_ANY(name), key_len); - key[key_len] = '\0'; + /* ---------------------- + * | 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 +/* + * At the beginning of ATX store the pg_variables's env into + * pgv_context. + */ static void -ensurePackagesHashExists() +pgvSaveContext(void) { - HASHCTL ctl; + Package *package; + HASH_SEQ_STATUS pstat; + PgvContextStruct *sus = MemoryContextAlloc(CurTransactionContext, + sizeof(PgvContextStruct)); - if (packagesHash) - return; + /* 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; + } + } - ModuleContext = AllocSetContextCreate(CacheMemoryContext, - "pg_variables memory context", - ALLOCSET_DEFAULT_MINSIZE, - ALLOCSET_DEFAULT_INITSIZE, - ALLOCSET_DEFAULT_MAXSIZE); + /* Remove stats for all transactional variables */ + remove_variables_transactional(&variables_stats); + resetVariablesCache(); - ctl.keysize = NAMEDATALEN; - ctl.entrysize = sizeof(HashPackageEntry); - ctl.hcxt = ModuleContext; + sus->changesStack = changesStack; + changesStack = NULL; + sus->changesStackContext = changesStackContext; + changesStackContext = NULL; - packagesHash = hash_create("Packages hash", - NUMPACKAGES, &ctl, HASH_ELEM | HASH_CONTEXT); + sus->next = pgv_context; + pgv_context = sus; } -static HashPackageEntry * -getPackageByName(text* name, bool create, bool strict) +/* + * Restore pg_variables's env pointer from pgv_context. + */ +static void +pgvRestoreContext() { - HashPackageEntry *package; - char key[NAMEDATALEN]; - bool found; + Package *package; + HASH_SEQ_STATUS pstat; + PgvContextStruct *sus = pgv_context; - getKeyFromName(name, key); + 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(); - if (create) - ensurePackagesHashExists(); - else + /* Restore transactional variables for all packages */ + if (packagesHash != NULL) { - if (!packagesHash) + /* Get packages list */ + hash_seq_init(&pstat, packagesHash); + while ((package = (Package *) hash_seq_search(&pstat)) != NULL) { - if (strict) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("unrecognized package \"%s\"", key))); + /* + * 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 - return NULL; + { + /* 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 + */ + } } } - if (create) - package = (HashPackageEntry *) hash_search(packagesHash, - key, HASH_ENTER, &found); - else - package = (HashPackageEntry *) hash_search(packagesHash, - key, HASH_FIND, &found); + /* + * '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 + */ +static void +pgvSubTransCallback(SubXactEvent event, SubTransactionId mySubid, + SubTransactionId parentSubid, void *arg) +{ + Levels levels; - /* Package entry was created, so we need create hash table for variables. */ - if (!found) + if (changesStack) { - if (create) + switch (event) { - HASHCTL ctl; - char hash_name[BUFSIZ]; - MemoryContext oldcxt; - - sprintf(hash_name, "Variables hash for package \"%s\"", key); - - package->hctx = AllocSetContextCreate(ModuleContext, - hash_name, - ALLOCSET_DEFAULT_MINSIZE, - ALLOCSET_DEFAULT_INITSIZE, - ALLOCSET_DEFAULT_MAXSIZE); - oldcxt = MemoryContextSwitchTo(package->hctx); - - ctl.keysize = NAMEDATALEN; - ctl.entrysize = sizeof(HashVariableEntry); - ctl.hcxt = package->hctx; - package->variablesHash = hash_create(hash_name, - NUMVARIABLES, &ctl, - HASH_ELEM | HASH_CONTEXT); - - MemoryContextSwitchTo(oldcxt); + case SUBXACT_EVENT_START_SUB: + pushChangesStack(); + compatibility_check(); + break; + case SUBXACT_EVENT_COMMIT_SUB: + processChanges(RELEASE_SAVEPOINT, true); + break; + case SUBXACT_EVENT_ABORT_SUB: + processChanges(ROLLBACK_TO_SAVEPOINT, true); + break; + case SUBXACT_EVENT_PRE_COMMIT_SUB: + break; } - else if (strict) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("unrecognized package \"%s\"", key))); } - return package; + levels.level = GetCurrentTransactionNestLevel(); +#ifdef PGPRO_EE + levels.atxlevel = getNestLevelATX(); +#endif + remove_variables_level(&variables_stats, &levels); + remove_packages_level(&packages_stats, &levels); } -static HashVariableEntry * -getVariableByNameWithType(HTAB *variables, text *name, Oid typid, - bool create, bool strict) +/* + * Intercept execution during transaction processing + */ +static void +pgvTransCallback(XactEvent event, void *arg) { - HashVariableEntry *variable; - char key[NAMEDATALEN]; - bool found; + if (changesStack) + { + switch (event) + { + case XACT_EVENT_PRE_COMMIT: + compatibility_check(); + processChanges(RELEASE_SAVEPOINT, false); + break; + case XACT_EVENT_ABORT: + processChanges(ROLLBACK_TO_SAVEPOINT, false); + break; + case XACT_EVENT_PARALLEL_PRE_COMMIT: + processChanges(RELEASE_SAVEPOINT, false); + break; + case XACT_EVENT_PARALLEL_ABORT: + processChanges(ROLLBACK_TO_SAVEPOINT, false); + break; + default: + break; + } + } - getKeyFromName(name, key); + 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 +} - if (create) - variable = (HashVariableEntry *) hash_search(variables, - key, HASH_ENTER, &found); +/* + * ExecutorEnd hook: clean up hash table sequential scan status + */ +static void +variable_ExecutorEnd(QueryDesc *queryDesc) +{ + if (prev_ExecutorEnd) + prev_ExecutorEnd(queryDesc); else - variable = (HashVariableEntry *) hash_search(variables, - key, HASH_FIND, &found); + standard_ExecutorEnd(queryDesc); - /* Check variable type */ - if (found) + freeStatsLists(); +} + +/* + * Free hash_seq_search scans + */ +static void +freeStatsLists(void) +{ + ListCell *cell; + + foreach(cell, variables_stats) { - if (variable->typid != typid) - { - char *var_type = DatumGetCString(DirectFunctionCall1( - regtypeout, ObjectIdGetDatum(variable->typid))); + VariableStatEntry *entry = (VariableStatEntry *) lfirst(cell); - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("variable \"%s\" requires \"%s\" value", - key, var_type))); - } +#ifdef PGPRO_EE + hash_seq_term_all_levels(entry->status); +#else + hash_seq_term(entry->status); +#endif } - else + + variables_stats = NIL; + + foreach(cell, packages_stats) { - /* Variable entry was created, so initialize new variable. */ - if (variable) - { - memset(&variable->value, 0, sizeof(variable->value)); - variable->typid = typid; - if (typid != RECORDOID) - { - get_typlenbyval(variable->typid, - &variable->value.scalar.typlen, - &variable->value.scalar.typbyval); - variable->value.scalar.is_null = true; - } - } - else if (strict) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("unrecognized variable \"%s\"", key))); + PackageStatEntry *entry = (PackageStatEntry *) lfirst(cell); + +#ifdef PGPRO_EE + hash_seq_term_all_levels(entry->status); +#else + hash_seq_term(entry->status); +#endif } - return variable; + packages_stats = NIL; +} + +/* + * Register callback function when module starts + */ +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. */ + prev_ExecutorEnd = ExecutorEnd_hook; + ExecutorEnd_hook = variable_ExecutorEnd; +} + +#if PG_VERSION_NUM < 150000 +/* + * Unregister callback function when module unloads + */ +void +_PG_fini(void) +{ + UnregisterXactCallback(pgvTransCallback, NULL); + UnregisterSubXactCallback(pgvSubTransCallback, NULL); + ExecutorEnd_hook = prev_ExecutorEnd; } +#endif diff --git a/pg_variables.control b/pg_variables.control index f44cf24..2d049e7 100644 --- a/pg_variables.control +++ b/pg_variables.control @@ -1,5 +1,5 @@ # pg_variables extension comment = 'session variables with various types' -default_version = '1.0' +default_version = '1.2' module_pathname = '$libdir/pg_variables' relocatable = true diff --git a/pg_variables.h b/pg_variables.h index 4b09fdb..6508e9f 100644 --- a/pg_variables.h +++ b/pg_variables.h @@ -3,64 +3,143 @@ * pg_variables.c * exported definitions for pg_variables.c * - * Copyright (c) 2015-2016, Postgres Professional + * Copyright (c) 2015-2022, Postgres Professional * *------------------------------------------------------------------------- */ #ifndef __PG_VARIABLES_H__ #define __PG_VARIABLES_H__ +#include "pg_config.h" + #include "access/htup.h" #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" +#include "lib/ilist.h" + +/* Accessor for the i'th attribute of tupdesc. */ +#if PG_VERSION_NUM > 100000 +#define GetTupleDescAttr(tupdesc, i) (TupleDescAttr(tupdesc, i)) +#else +#define GetTupleDescAttr(tupdesc, i) ((tupdesc)->attrs[(i)]) +#endif /* initial number of packages hashes */ #define NUMPACKAGES 8 #define NUMVARIABLES 16 -typedef struct HashPackageEntry -{ - char name[NAMEDATALEN]; - HTAB *variablesHash; - /* Memory context for package variables for easy memory release */ - MemoryContext hctx; -} HashPackageEntry; - typedef struct RecordVar { - HTAB *rhash; - TupleDesc tupdesc; + HTAB *rhash; + TupleDesc tupdesc; /* Memory context for records hash table for easy memory release */ - MemoryContext hctx; + MemoryContext hctx; /* Hash function info */ - FmgrInfo hash_proc; + FmgrInfo hash_proc; /* Match function info */ - FmgrInfo cmp_proc; + FmgrInfo cmp_proc; } RecordVar; typedef struct ScalarVar { - Datum value; - bool is_null; - bool typbyval; - int16 typlen; -} ScalarVar; + Datum value; + bool is_null; + bool typbyval; + int16 typlen; +} ScalarVar; -typedef struct HashVariableEntry +/* Object levels (subxact + atx) */ +typedef struct Levels { - char name[NAMEDATALEN]; + int level; +#ifdef PGPRO_EE + int atxlevel; +#endif +} Levels; + +/* State of TransObject instance */ +typedef struct TransState +{ + dlist_node node; + bool is_valid; + 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 */ +} PackState; + +/* List node that stores one of the variable's states */ +typedef struct VarState +{ + TransState state; union { ScalarVar scalar; RecordVar record; - } value; + } value; +} VarState; + +/* Transactional object */ +typedef struct TransObject +{ + char name[NAMEDATALEN]; + dlist_head states; +} TransObject; - Oid typid; -} HashVariableEntry; +#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 +{ + TransObject transObject; + HTAB *varHashRegular, + *varHashTransact; + /* Memory context for package variables for easy memory release */ + MemoryContext hctxRegular, + hctxTransact; +#ifdef PGPRO_EE + PackageContext *context; +#endif +} Package; + +/* Transactional variable */ +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 + */ + bool is_record; + + /* + * The flag determines the further behavior of the variable. Can be + * specified only when creating a variable. + */ + bool is_transactional; + bool is_deleted; +} Variable; typedef struct HashRecordKey { @@ -70,25 +149,63 @@ typedef struct HashRecordKey FmgrInfo *hash_proc; /* Match function info */ FmgrInfo *cmp_proc; -} HashRecordKey; +} HashRecordKey; typedef struct HashRecordEntry { - HashRecordKey key; - HeapTuple tuple; -} HashRecordEntry; - -extern void init_attributes(HashVariableEntry* variable, TupleDesc tupdesc, - MemoryContext topctx); -extern void check_attributes(HashVariableEntry *variable, TupleDesc tupdesc); -extern void check_record_key(HashVariableEntry *variable, Oid typid); - -extern void insert_record(HashVariableEntry* variable, - HeapTupleHeader tupleHeader); -extern bool update_record(HashVariableEntry *variable, - HeapTupleHeader tupleHeader); -extern bool delete_record(HashVariableEntry* variable, Datum value, - bool is_null); -extern void clean_records(HashVariableEntry *variable); - -#endif /* __PG_VARIABLES_H__ */ + HashRecordKey key; + Datum tuple; +} HashRecordEntry; + +/* Element of list with objects created, changed or removed within transaction */ +typedef struct ChangedObject +{ + dlist_node node; + TransObject *object; +} ChangedObject; + +/* Type of transactional object instance */ +typedef enum TransObjectType +{ + TRANS_PACKAGE, + TRANS_VARIABLE +} TransObjectType; + +/* Element of stack with 'changedVars' and 'changedPacks' list heads*/ +typedef struct ChangesStackNode +{ + dlist_node node; + dlist_head *changedVarsList; + dlist_head *changedPacksList; + 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, 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); +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 bool removeObject(TransObject *object, TransObjectType type); + +#define GetActualState(object) \ + (dlist_head_element(TransState, node, &((TransObject *) object)->states)) + +#define GetActualValue(variable) \ + (((VarState *) GetActualState(variable))->value) + +#define GetPackState(package) \ + (((PackState *) GetActualState(package))) + +#define GetName(object) \ + (AssertVariableIsOfTypeMacro(object->transObject, TransObject), \ + object->transObject.name) + +#endif /* __PG_VARIABLES_H__ */ diff --git a/pg_variables_record.c b/pg_variables_record.c index 5dcda5d..dadb14f 100644 --- a/pg_variables_record.c +++ b/pg_variables_record.c @@ -3,17 +3,33 @@ * pg_variables_record.c * Functions to work with record types * - * Copyright (c) 2015-2016, Postgres Professional + * Copyright (c) 2015-2022, Postgres Professional * *------------------------------------------------------------------------- */ #include "postgres.h" +#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" @@ -28,8 +44,8 @@ static uint32 record_hash(const void *key, Size keysize) { - HashRecordKey k = *((const HashRecordKey *) key); - Datum h; + HashRecordKey k = *((const HashRecordKey *) key); + Datum h; if (k.is_null) return 0; @@ -44,9 +60,9 @@ record_hash(const void *key, Size keysize) static int record_match(const void *key1, const void *key2, Size keysize) { - HashRecordKey k1 = *((const HashRecordKey *) key1); - HashRecordKey k2 = *((const HashRecordKey *) key2); - Datum c; + HashRecordKey k1 = *((const HashRecordKey *) key1); + HashRecordKey k2 = *((const HashRecordKey *) key2); + Datum c; if (k1.is_null) { @@ -64,28 +80,83 @@ record_match(const void *key1, const void *key2, Size keysize) } void -init_attributes(HashVariableEntry *variable, TupleDesc tupdesc, - MemoryContext topctx) +init_record(RecordVar *record, TupleDesc tupdesc, Variable *variable) { HASHCTL ctl; char hash_name[BUFSIZ]; - MemoryContext oldcxt; - RecordVar *record; - TypeCacheEntry *typentry; - Oid keyid; + MemoryContext oldcxt, + topctx; + TypeCacheEntry *typentry; + Oid keyid; Assert(variable->typid == RECORDOID); - sprintf(hash_name, "Records hash for variable \"%s\"", variable->name); + /* First get hash and match functions for key type. */ + keyid = GetTupleDescAttr(tupdesc, 0)->atttypid; + typentry = lookup_type_cache(keyid, + TYPECACHE_HASH_PROC_FINFO | + TYPECACHE_CMP_PROC_FINFO); + + /* + * 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. + */ + if (!OidIsValid(typentry->hash_proc_finfo.fn_oid)) + { + /* At this point variable is just created, so we simply remove it. */ + removeObject(&variable->transObject, TRANS_VARIABLE); + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify a hash function for type %s", + format_type_be(keyid)))); + } + + if (!OidIsValid(typentry->cmp_proc_finfo.fn_oid)) + { + removeObject(&variable->transObject, TRANS_VARIABLE); + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify a matching function for type %s", + format_type_be(keyid)))); + } - record = &(variable->value.record); + /* Initialize the record */ + + sprintf(hash_name, "Records hash for variable \"%s\"", GetName(variable)); + + topctx = variable->is_transactional ? + variable->package->hctxTransact : + variable->package->hctxRegular; + +#if PG_VERSION_NUM >= 120000 + record->hctx = AllocSetContextCreateInternal(topctx, + hash_name, + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); +#elif PG_VERSION_NUM >= 110000 + record->hctx = AllocSetContextCreateExtended(topctx, + hash_name, + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); +#else record->hctx = AllocSetContextCreate(topctx, hash_name, ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); +#endif + oldcxt = MemoryContextSwitchTo(record->hctx); - record->tupdesc = CreateTupleDescCopyConstr(tupdesc); + record->tupdesc = CreateTupleDescCopy(tupdesc); +#if PG_VERSION_NUM < 120000 + record->tupdesc->tdhasoid = false; +#endif + record->tupdesc->tdtypeid = RECORDOID; + record->tupdesc->tdtypmod = -1; + record->tupdesc = BlessTupleDesc(record->tupdesc); /* Initialize hash table. */ ctl.keysize = sizeof(HashRecordKey); @@ -95,26 +166,8 @@ init_attributes(HashVariableEntry *variable, TupleDesc tupdesc, ctl.match = record_match; record->rhash = hash_create(hash_name, NUMVARIABLES, &ctl, - HASH_ELEM | HASH_CONTEXT | - HASH_FUNCTION | HASH_COMPARE); - - /* Get hash and match functions for key type. */ - keyid = record->tupdesc->attrs[0]->atttypid; - typentry = lookup_type_cache(keyid, - TYPECACHE_HASH_PROC_FINFO | - TYPECACHE_CMP_PROC_FINFO); - - if (!OidIsValid(typentry->hash_proc_finfo.fn_oid)) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_FUNCTION), - errmsg("could not identify a hash function for type %s", - format_type_be(keyid)))); - - if (!OidIsValid(typentry->cmp_proc_finfo.fn_oid)) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_FUNCTION), - errmsg("could not identify a matching function for type %s", - format_type_be(keyid)))); + HASH_ELEM | HASH_CONTEXT | + HASH_FUNCTION | HASH_COMPARE); fmgr_info(typentry->hash_proc_finfo.fn_oid, &record->hash_proc); fmgr_info(typentry->cmp_proc_finfo.fn_oid, &record->cmp_proc); @@ -122,91 +175,269 @@ init_attributes(HashVariableEntry *variable, TupleDesc tupdesc, 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(HashVariableEntry *variable, TupleDesc tupdesc) +check_attributes(Variable *variable, HeapTupleHeader *rec, TupleDesc tupdesc) { int i; + RecordVar *record; + bool unknowns = false; Assert(variable->typid == RECORDOID); + record = &(GetActualValue(variable).record); /* First, check columns count. */ - if (variable->value.record.tupdesc->natts != tupdesc->natts) + if (record->tupdesc->natts != tupdesc->natts) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("new record structure differs from variable \"%s\" " - "structure", variable->name))); + 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++) { - Form_pg_attribute attr1 = variable->value.record.tupdesc->attrs[i], - attr2 = tupdesc->attrs[i]; + 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", variable->name))); + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + 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); } /* * Check record key type. If not same then throw a error. */ void -check_record_key(HashVariableEntry *variable, Oid typid) +check_record_key(Variable *variable, Oid typid) { + RecordVar *record; + Assert(variable->typid == RECORDOID); + record = &(GetActualValue(variable).record); - if (variable->value.record.tupdesc->attrs[0]->atttypid != typid) + if (GetTupleDescAttr(record->tupdesc, 0)->atttypid != typid) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("requested value type differs from variable \"%s\" " - "key type", variable->name))); + "key type", GetName(variable)))); +} + +static Datum +copy_record_tuple(RecordVar *record, HeapTupleHeader tupleHeader) +{ + TupleDesc tupdesc; + HeapTupleHeader result; + int tuple_len; + + tupdesc = record->tupdesc; + + /* + * If the tuple contains any external TOAST pointers, we have to inline + * those fields to meet the conventions for composite-type Datums. + */ + if (HeapTupleHeaderHasExternal(tupleHeader)) + return toast_flatten_tuple_to_datum(tupleHeader, + HeapTupleHeaderGetDatumLength(tupleHeader), + tupdesc); + + /* + * Fast path for easy case: just make a palloc'd copy and insert the + * correct composite-Datum header fields (since those may not be set if + * the given tuple came from disk, rather than from heap_form_tuple). + */ + tuple_len = HeapTupleHeaderGetDatumLength(tupleHeader); + result = (HeapTupleHeader) palloc(tuple_len); + memcpy((char *) result, (char *) tupleHeader, tuple_len); + + HeapTupleHeaderSetDatumLength(result, tuple_len); + HeapTupleHeaderSetTypeId(result, tupdesc->tdtypeid); + HeapTupleHeaderSetTypMod(result, tupdesc->tdtypmod); + + return PointerGetDatum(result); +} + +static Datum +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 */ + int keyatt = 0; + Form_pg_attribute attr = GetTupleDescAttr(tupdesc, keyatt); + + if (hasnulls && att_isnull(keyatt, bp)) + { + *isnull = true; + return (Datum) NULL; + } + + tp = (char *) th + th->t_hoff; + off = 0; + if (attr->attlen == -1) + off = att_align_pointer(off, attr->attalign, -1, tp + off); + else + { + /* not varlena, so safe to use att_align_nominal */ + off = att_align_nominal(off, attr->attalign); + } + + *isnull = false; + return fetchatt(attr, tp + off); } /* * Insert a new record. New record key should be unique in the variable. */ void -insert_record(HashVariableEntry *variable, HeapTupleHeader tupleHeader) +insert_record(Variable *variable, HeapTupleHeader tupleHeader) { - TupleDesc tupdesc; - HeapTuple tuple; - int tuple_len; - Datum value; - bool isnull; - RecordVar *record; - HashRecordKey k; - HashRecordEntry *item; - bool found; - MemoryContext oldcxt; + Datum tuple; + Datum value; + bool isnull; + RecordVar *record; + HashRecordKey k; + HashRecordEntry *item; + bool found; + MemoryContext oldcxt; Assert(variable->typid == RECORDOID); - record = &(variable->value.record); + record = &(GetActualValue(variable).record); oldcxt = MemoryContextSwitchTo(record->hctx); - tupdesc = record->tupdesc; - - /* Build a HeapTuple control structure */ - tuple_len = HeapTupleHeaderGetDatumLength(tupleHeader); - - tuple = (HeapTuple) palloc(HEAPTUPLESIZE + tuple_len); - tuple->t_len = tuple_len; - ItemPointerSetInvalid(&(tuple->t_self)); - tuple->t_tableOid = InvalidOid; - tuple->t_data = (HeapTupleHeader) ((char *) tuple + HEAPTUPLESIZE); - memcpy((char *) tuple->t_data, (char *) tupleHeader, tuple_len); + tuple = copy_record_tuple(record, tupleHeader); /* Inserting a new record */ - value = fastgetattr(tuple, 1, tupdesc, &isnull); + value = get_record_key(tuple, record->tupdesc, &isnull); /* First, check if there is a record with same key */ k.value = value; k.is_null = isnull; @@ -217,12 +448,12 @@ insert_record(HashVariableEntry *variable, HeapTupleHeader tupleHeader) HASH_ENTER, &found); if (found) { - heap_freetuple(tuple); + pfree(DatumGetPointer(tuple)); MemoryContextSwitchTo(oldcxt); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("there is a record in the variable \"%s\" with same " - "key", variable->name))); + "key", GetName(variable)))); } /* Second, insert a new record */ item->tuple = tuple; @@ -234,39 +465,27 @@ insert_record(HashVariableEntry *variable, HeapTupleHeader tupleHeader) * Insert a record. New record key should be unique in the variable. */ bool -update_record(HashVariableEntry* variable, HeapTupleHeader tupleHeader) +update_record(Variable *variable, HeapTupleHeader tupleHeader) { - TupleDesc tupdesc; - HeapTuple tuple; - int tuple_len; - Datum value; - bool isnull; - RecordVar *record; - HashRecordKey k; - HashRecordEntry *item; - bool found; - MemoryContext oldcxt; + Datum tuple; + Datum value; + bool isnull; + RecordVar *record; + HashRecordKey k; + HashRecordEntry *item; + bool found; + MemoryContext oldcxt; Assert(variable->typid == RECORDOID); - record = &(variable->value.record); + record = &(GetActualValue(variable).record); oldcxt = MemoryContextSwitchTo(record->hctx); - tupdesc = record->tupdesc; - - /* Build a HeapTuple control structure */ - tuple_len = HeapTupleHeaderGetDatumLength(tupleHeader); - - tuple = (HeapTuple) palloc(HEAPTUPLESIZE + tuple_len); - tuple->t_len = tuple_len; - ItemPointerSetInvalid(&(tuple->t_self)); - tuple->t_tableOid = InvalidOid; - tuple->t_data = (HeapTupleHeader) ((char *) tuple + HEAPTUPLESIZE); - memcpy((char *) tuple->t_data, (char *) tupleHeader, tuple_len); + tuple = copy_record_tuple(record, tupleHeader); /* Update a record */ - value = fastgetattr(tuple, 1, tupdesc, &isnull); + value = get_record_key(tuple, record->tupdesc, &isnull); k.value = value; k.is_null = isnull; k.hash_proc = &record->hash_proc; @@ -276,13 +495,13 @@ update_record(HashVariableEntry* variable, HeapTupleHeader tupleHeader) HASH_FIND, &found); if (!found) { - heap_freetuple(tuple); + pfree(DatumGetPointer(tuple)); MemoryContextSwitchTo(oldcxt); return false; } /* Release old tuple */ - heap_freetuple(item->tuple); + pfree(DatumGetPointer(item->tuple)); item->tuple = tuple; MemoryContextSwitchTo(oldcxt); @@ -290,16 +509,16 @@ update_record(HashVariableEntry* variable, HeapTupleHeader tupleHeader) } bool -delete_record(HashVariableEntry *variable, Datum value, bool is_null) +delete_record(Variable *variable, Datum value, bool is_null) { - HashRecordKey k; - HashRecordEntry *item; - bool found; - RecordVar *record; + HashRecordKey k; + HashRecordEntry *item; + bool found; + RecordVar *record; Assert(variable->typid == RECORDOID); - record = &(variable->value.record); + record = &(GetActualValue(variable).record); /* Delete a record */ k.value = value; @@ -310,22 +529,49 @@ delete_record(HashVariableEntry *variable, Datum value, bool is_null) item = (HashRecordEntry *) hash_search(record->rhash, &k, HASH_REMOVE, &found); if (found) - heap_freetuple(item->tuple); + pfree(DatumGetPointer(item->tuple)); return found; } /* - * Free allocated space for records hash table and datums array. + * Copy record using src_tuple. */ void -clean_records(HashVariableEntry *variable) +insert_record_copy(RecordVar *dest_record, Datum src_tuple, Variable *variable) { - Assert(variable->typid == RECORDOID); + Datum tuple; + Datum value; + bool isnull; + HashRecordKey k; + HashRecordEntry *item; + bool found; + MemoryContext oldcxt; - hash_destroy(variable->value.record.rhash); - FreeTupleDesc(variable->value.record.tupdesc); + oldcxt = MemoryContextSwitchTo(dest_record->hctx); - /* All records will be freed */ - MemoryContextDelete(variable->value.record.hctx); + /* Inserting a new record into dest_record */ + tuple = copy_record_tuple(dest_record, + (HeapTupleHeader) DatumGetPointer(src_tuple)); + value = get_record_key(tuple, dest_record->tupdesc, &isnull); + + k.value = value; + k.is_null = isnull; + k.hash_proc = &dest_record->hash_proc; + k.cmp_proc = &dest_record->cmp_proc; + + item = (HashRecordEntry *) hash_search(dest_record->rhash, &k, + HASH_ENTER, &found); + if (found) + { + pfree(DatumGetPointer(tuple)); + MemoryContextSwitchTo(oldcxt); + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("there is a record in the variable \"%s\" with same " + "key", GetName(variable)))); + } + item->tuple = tuple; + + MemoryContextSwitchTo(oldcxt); } diff --git a/run_tests.sh b/run_tests.sh new file mode 100755 index 0000000..2d88e36 --- /dev/null +++ b/run_tests.sh @@ -0,0 +1,145 @@ +#!/usr/bin/env bash + +# +# Copyright (c) 2018-2022, Postgres Professional +# +# supported levels: +# * standard +# * scan-build +# * hardcore +# * nightmare +# + +set -ux +status=0 + + +# rebuild PostgreSQL with cassert + valgrind support +if [ "$LEVEL" = "hardcore" ] || \ + [ "$LEVEL" = "nightmare" ]; then + + set -e + + CUSTOM_PG_BIN=$PWD/pg_bin + CUSTOM_PG_SRC=$PWD/postgresql + + # here PG_VERSION is provided by postgres:X-alpine docker image + curl "https://ftp.postgresql.org/pub/source/v$PG_VERSION/postgresql-$PG_VERSION.tar.bz2" -o postgresql.tar.bz2 + echo "$PG_SHA256 *postgresql.tar.bz2" | sha256sum -c - + + mkdir $CUSTOM_PG_SRC + + tar \ + --extract \ + --file postgresql.tar.bz2 \ + --directory $CUSTOM_PG_SRC \ + --strip-components 1 + + cd $CUSTOM_PG_SRC + + # enable Valgrind support + sed -i.bak "s/\/* #define USE_VALGRIND *\//#define USE_VALGRIND/g" src/include/pg_config_manual.h + + # enable additional options + ./configure \ + CFLAGS='-O0 -ggdb3 -fno-omit-frame-pointer' \ + --enable-cassert --enable-debug \ + --prefix=$CUSTOM_PG_BIN \ + --quiet + + time make -s -j$(nproc) && make -s install + + # override default PostgreSQL instance + export PATH=$CUSTOM_PG_BIN/bin:$PATH + export LD_LIBRARY_PATH=$CUSTOM_PG_BIN/lib + + # show pg_config path (just in case) + which pg_config + + cd - + + set +e +fi + +# show pg_config just in case +pg_config + +# perform code checks if asked to +if [ "$LEVEL" = "scan-build" ] || \ + [ "$LEVEL" = "hardcore" ] || \ + [ "$LEVEL" = "nightmare" ]; then + + # perform static analyzis + scan-build --status-bugs make USE_PGXS=1 || status=$? + + # something's wrong, exit now! + if [ $status -ne 0 ]; then exit 1; fi + + # don't forget to "make clean" + make USE_PGXS=1 clean +fi + + +# build and install extension (using PG_CPPFLAGS and SHLIB_LINK for gcov) +make USE_PGXS=1 PG_CPPFLAGS="-coverage" SHLIB_LINK="-coverage" install + +# initialize database +initdb -D $PGDATA + +# set appropriate port +export PGPORT=55435 +echo "port = $PGPORT" >> $PGDATA/postgresql.conf + +# restart cluster 'test' +if [ "$LEVEL" = "nightmare" ]; then + ls $CUSTOM_PG_BIN/bin + + valgrind \ + --tool=memcheck \ + --leak-check=no \ + --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 \ + --log-file=/tmp/valgrind-%p.log \ + pg_ctl start -l /tmp/postgres.log -w || status=$? +else + pg_ctl start -l /tmp/postgres.log -w || status=$? +fi + +# something's wrong, exit now! +if [ $status -ne 0 ]; then cat /tmp/postgres.log; exit 1; fi + +# run regression tests +export PG_REGRESS_DIFF_OPTS="-w -U3" # for alpine's diff (BusyBox) +make USE_PGXS=1 installcheck || status=$? + +# show Valgrind logs if necessary +if [ "$LEVEL" = "nightmare" ]; then + for f in $(find /tmp -name valgrind-*.log); do + if grep -q 'Command: [^ ]*/postgres' $f && grep -q 'ERROR SUMMARY: [1-9]' $f; then + echo "========= Contents of $f" + cat $f + status=1 + fi + done +fi + +# show diff if it exists +if test -f regression.diffs; then cat regression.diffs; fi + +# something's wrong, exit now! +if [ $status -ne 0 ]; then exit 1; fi + +# generate *.gcov files +gcov *.c *.h + + +set +ux + + +# send coverage stats to Codecov +bash <(curl -s https://codecov.io/bash) diff --git a/sql/pg_variables.sql b/sql/pg_variables.sql index 12155e0..4b9a3d2 100644 --- a/sql/pg_variables.sql +++ b/sql/pg_variables.sql @@ -1,5 +1,12 @@ CREATE EXTENSION pg_variables; +-- Test packages - sanity checks +SELECT pgv_free(); +SELECT pgv_exists(NULL); -- fail +SELECT pgv_remove(NULL); -- fail +SELECT pgv_remove('vars'); -- fail +SELECT pgv_exists('vars111111111111111111111111111111111111111111111111111111111111'); -- fail + -- Integer variables SELECT pgv_get_int('vars', 'int1'); SELECT pgv_get_int('vars', 'int1', false); @@ -152,15 +159,31 @@ SELECT pgv_insert('vars3', 'r1', tab) FROM tab; SELECT pgv_insert('vars3', 'r1', row(1, 'str1', 'str2')); SELECT pgv_insert('vars3', 'r1', row(1, 1)); SELECT pgv_insert('vars3', 'r1', row('str1', 'str1')); +SELECT pgv_select('vars3', 'r1', ARRAY[[1,2]]); -- fail + +-- Test variables caching +SELECT pgv_insert('vars3', 'r2', row(1, 'str1', 'str2')); +SELECT pgv_update('vars3', 'r1', row(3, 'str22'::varchar)); +SELECT pgv_update('vars4', 'r1', row(3, 'str22'::varchar)); -- fail +select pgv_delete('vars3', 'r2', NULL::int); +select pgv_delete('vars4', 'r2', NULL::int); -- fail + +-- Test NULL values +SELECT pgv_insert('vars3', 'r2', NULL); -- fail +SELECT pgv_update('vars3', 'r2', NULL); -- fail +select pgv_delete('vars3', 'r2', NULL::int); +SELECT pgv_select('vars3', 'r1', NULL::int[]); -- fail SELECT pgv_select('vars3', 'r1'); SELECT pgv_select('vars3', 'r1', 1); +SELECT pgv_select('vars3', 'r1', 1::float); -- fail SELECT pgv_select('vars3', 'r1', 0); SELECT pgv_select('vars3', 'r1', NULL::int); SELECT pgv_select('vars3', 'r1', ARRAY[1, 0, NULL]); UPDATE tab SET t = 'str33' WHERE id = 1; SELECT pgv_update('vars3', 'r1', tab) FROM tab; +SELECT pgv_update('vars3', 'r1', row(4, 'str44'::varchar)); SELECT pgv_select('vars3', 'r1'); SELECT pgv_delete('vars3', 'r1', 1); @@ -172,8 +195,54 @@ SELECT pgv_exists('vars3', 'r3'); SELECT pgv_exists('vars3', 'r1'); SELECT pgv_select('vars2', 'j1'); +-- PGPRO-2601 - Test pgv_select() on TupleDesc of dropped table +DROP TABLE tab; +SELECT pgv_select('vars3', 'r1'); + +-- Tests for SRF's sequential scan of an internal hash table +DO +$$BEGIN + PERFORM pgv_select('vars3', 'r1') LIMIT 2 OFFSET 2; + PERFORM pgv_select('vars3', 'r3'); +END$$; +-- Check that the hash table was cleaned up after rollback +SET client_min_messages to 'ERROR'; +SELECT pgv_select('vars3', 'r1', 1); +SELECT pgv_select('vars3', 'r1') LIMIT 2; -- warning +SELECT pgv_select('vars3', 'r1') LIMIT 2 OFFSET 2; + +-- PGPRO-2601 - Test a cursor with the hash table +BEGIN; +DECLARE r1_cur CURSOR FOR SELECT pgv_select('vars3', 'r1'); +FETCH 1 in r1_cur; +SELECT pgv_select('vars3', 'r1'); +FETCH 1 in r1_cur; +CLOSE r1_cur; +COMMIT; -- warning +RESET client_min_messages; + +-- Clean memory after unsuccessful creation of a variable +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 +SELECT pgv_insert('vars4', 'r2', row(1, 'str1', 'str2')); +SELECT pgv_remove('vars4', 'r2'); +SELECT package FROM pgv_stats() WHERE package = 'vars4'; + +-- Record variables as scalar +SELECT pgv_set('vars5', 'r1', row(1, 'str11')); +SELECT pgv_get('vars5', 'r1', NULL::record); +SELECT pgv_set('vars5', 'r1', row(1, 'str11'), true); -- fail + +SELECT pgv_insert('vars5', 'r1', row(1, 'str11')); -- fail +SELECT pgv_select('vars5', 'r1'); -- fail + +SELECT pgv_get('vars3', 'r1', NULL::record); -- fail + -- Manipulate variables SELECT * FROM pgv_list() order by package, name; +SELECT package FROM pgv_stats() order by package; SELECT pgv_remove('vars', 'int3'); SELECT pgv_remove('vars', 'int1'); @@ -190,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_any.sql b/sql/pg_variables_any.sql index 3b9d488..39da5c1 100644 --- a/sql/pg_variables_any.sql +++ b/sql/pg_variables_any.sql @@ -139,6 +139,26 @@ SELECT pgv_get('vars', 'd1', NULL::jsonb); SELECT pgv_set('vars', 'jNULL', NULL::jsonb); SELECT pgv_get('vars', 'jNULL', NULL::jsonb); +-- Array variables +SELECT pgv_set('vars', 'arr1', '{1, 2, null}'::int[]); +SELECT pgv_set('vars', 'arr2', '{"bar", "balance", "active"}'::text[]); +SELECT pgv_set('vars2', 'j1', '{1, 2, null}'::int[]); + +SELECT pgv_get('vars', 'arr1', NULL::int[]); +SELECT pgv_get('vars', 'arr2', NULL::int[]); +SELECT pgv_set('vars', 'arr1', '{"bar", "balance", "active"}'::text[]); +SELECT pgv_set('vars', 'arr1', '{3, 4, 5}'::int[]); +SELECT pgv_get('vars', 'arr1', NULL::int[]); + +SELECT pgv_get('vars', 'arr3', NULL::int[]); +SELECT pgv_get('vars', 'arr3', NULL::int[], false); +SELECT pgv_exists('vars', 'arr3'); +SELECT pgv_exists('vars', 'arr1'); +SELECT pgv_get('vars2', 'j1', NULL::int[]); + +SELECT pgv_set('vars', 'arrNULL', NULL::int[]); +SELECT pgv_get('vars', 'arrNULL', NULL::int[]); + -- Manipulate variables SELECT * FROM pgv_list() order by package, name; 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 new file mode 100644 index 0000000..23b7afd --- /dev/null +++ b/sql/pg_variables_trans.sql @@ -0,0 +1,1193 @@ +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); +SELECT pgv_set('vars', 'any2', 'some value'::text); +SELECT pgv_set_int('vars', 'int1', 101, true); +SELECT pgv_set_int('vars', 'int2', 102); +SELECT pgv_set_int('vars', 'intNULL', NULL, true); +SELECT pgv_set_text('vars', 'str1', 's101', true); +SELECT pgv_set_text('vars', 'str2', 's102'); +SELECT pgv_set_numeric('vars', 'num1', 1.01, true); +SELECT pgv_set_numeric('vars', 'num2', 1.02); +SELECT pgv_set_timestamp('vars', 'ts1', '2016-03-30 10:00:00', true); +SELECT pgv_set_timestamp('vars', 'ts2', '2016-03-30 11:00:00'); +SELECT pgv_set_timestamptz('vars', 'tstz1', '2016-03-30 10:00:00 GMT+01', true); +SELECT pgv_set_timestamptz('vars', 'tstz2', '2016-03-30 11:00:00 GMT+02'); +SELECT pgv_set_date('vars', 'd1', '2016-03-29', true); +SELECT pgv_set_date('vars', 'd2', '2016-03-30'); +SELECT pgv_set_jsonb('vars2', 'j1', '[1, 2, "foo", null]', true); +SELECT pgv_set_jsonb('vars2', 'j2', '{"bar": "baz", "balance": 7.77, "active": false}'); + +SAVEPOINT comm; +-- Set new values +SELECT pgv_set('vars', 'any1', 'another value'::text, true); +SELECT pgv_set('vars', 'any2', 'another value'::text); +SELECT pgv_set_int('vars', 'int1', 103, true); +SELECT pgv_set_int('vars', 'int2', 103); +SELECT pgv_set_int('vars', 'intNULL', 104, true); +SELECT pgv_set_text('vars', 'str1', 's103', true); +SELECT pgv_set_text('vars', 'str2', 's103'); +SELECT pgv_set_numeric('vars', 'num1', 1.03, true); +SELECT pgv_set_numeric('vars', 'num2', 1.03); +SELECT pgv_set_timestamp('vars', 'ts1', '2016-03-30 12:00:00', true); +SELECT pgv_set_timestamp('vars', 'ts2', '2016-03-30 12:00:00'); +SELECT pgv_set_timestamptz('vars', 'tstz1', '2016-03-30 12:00:00 GMT+03', true); +SELECT pgv_set_timestamptz('vars', 'tstz2', '2016-03-30 12:00:00 GMT+03'); +SELECT pgv_set_date('vars', 'd1', '2016-04-02', true); +SELECT pgv_set_date('vars', 'd2', '2016-04-02'); +SELECT pgv_set_jsonb('vars2', 'j1', '{"foo": [true, "bar"], "tags": {"a": 1, "b": null}}', true); +SELECT pgv_set_jsonb('vars2', 'j2', '{"foo": [true, "bar"], "tags": {"a": 1, "b": null}}'); + +-- Check values before releasing savepoint +SELECT pgv_get('vars', 'any1',NULL::text); +SELECT pgv_get('vars', 'any2',NULL::text); +SELECT pgv_get_int('vars', 'int1'); +SELECT pgv_get_int('vars', 'int2'); +SELECT pgv_get_int('vars', 'intNULL'); +SELECT pgv_get_text('vars', 'str1'); +SELECT pgv_get_text('vars', 'str2'); +SELECT pgv_get_numeric('vars', 'num1'); +SELECT pgv_get_numeric('vars', 'num2'); +SELECT pgv_get_timestamp('vars', 'ts1'); +SELECT pgv_get_timestamp('vars', 'ts2'); +SELECT pgv_get_timestamptz('vars', 'tstz1'); +SELECT pgv_get_timestamptz('vars', 'tstz2'); +SELECT pgv_get_date('vars', 'd1'); +SELECT pgv_get_date('vars', 'd2'); +SELECT pgv_get_jsonb('vars2', 'j1'); +SELECT pgv_get_jsonb('vars2', 'j2'); + +-- Check values after releasing savepoint +RELEASE comm; +SELECT pgv_get('vars', 'any1',NULL::text); +SELECT pgv_get('vars', 'any2',NULL::text); +SELECT pgv_get_int('vars', 'int1'); +SELECT pgv_get_int('vars', 'int2'); +SELECT pgv_get_int('vars', 'intNULL'); +SELECT pgv_get_text('vars', 'str1'); +SELECT pgv_get_text('vars', 'str2'); +SELECT pgv_get_numeric('vars', 'num1'); +SELECT pgv_get_numeric('vars', 'num2'); +SELECT pgv_get_timestamp('vars', 'ts1'); +SELECT pgv_get_timestamp('vars', 'ts2'); +SELECT pgv_get_timestamptz('vars', 'tstz1'); +SELECT pgv_get_timestamptz('vars', 'tstz2'); +SELECT pgv_get_date('vars', 'd1'); +SELECT pgv_get_date('vars', 'd2'); +SELECT pgv_get_jsonb('vars2', 'j1'); +SELECT pgv_get_jsonb('vars2', 'j2'); +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; +SELECT pgv_insert('vars3', 'r2', tab) FROM tab; +SAVEPOINT comm; +SELECT pgv_insert('vars3', 'r1', row(5 :: integer, 'str55' :: varchar),true); +SELECT pgv_insert('vars3', 'r2', row(5 :: integer, 'str55' :: varchar)); +SELECT pgv_select('vars3', 'r1'); +SELECT pgv_select('vars3', 'r2'); +RELEASE comm; +SELECT pgv_select('vars3', 'r1'); +SELECT pgv_select('vars3', 'r2'); +COMMIT; + + + +--CHECK SAVEPOINT ROLLBACK +BEGIN; +-- Variables are already declared +SAVEPOINT comm2; +-- Set new values +SELECT pgv_set('vars', 'any1', 'one more value'::text, true); +SELECT pgv_set('vars', 'any2', 'one more value'::text); +SELECT pgv_set_int('vars', 'int1', 101, true); +SELECT pgv_set_int('vars', 'int2', 102); +SELECT pgv_set_int('vars', 'intNULL', NULL, true); +SELECT pgv_set_text('vars', 'str1', 's101', true); +SELECT pgv_set_text('vars', 'str2', 's102'); +SELECT pgv_set_numeric('vars', 'num1', 1.01, true); +SELECT pgv_set_numeric('vars', 'num2', 1.02); +SELECT pgv_set_timestamp('vars', 'ts1', '2016-03-30 10:00:00', true); +SELECT pgv_set_timestamp('vars', 'ts2', '2016-03-30 11:00:00'); +SELECT pgv_set_timestamptz('vars', 'tstz1', '2016-03-30 10:00:00 GMT+01', true); +SELECT pgv_set_timestamptz('vars', 'tstz2', '2016-03-30 11:00:00 GMT+02'); +SELECT pgv_set_date('vars', 'd1', '2016-03-29', true); +SELECT pgv_set_date('vars', 'd2', '2016-03-30'); +SELECT pgv_set_jsonb('vars2', 'j1', '[1, 2, "foo", null]', true); +SELECT pgv_set_jsonb('vars2', 'j2', '{"bar": "baz", "balance": 7.77, "active": false}'); + +-- Check values before rollback to savepoint +SELECT pgv_get('vars', 'any1',NULL::text); +SELECT pgv_get('vars', 'any2',NULL::text); +SELECT pgv_get_int('vars', 'int1'); +SELECT pgv_get_int('vars', 'int2'); +SELECT pgv_get_int('vars', 'intNULL'); +SELECT pgv_get_text('vars', 'str1'); +SELECT pgv_get_text('vars', 'str2'); +SELECT pgv_get_numeric('vars', 'num1'); +SELECT pgv_get_numeric('vars', 'num2'); +SELECT pgv_get_timestamp('vars', 'ts1'); +SELECT pgv_get_timestamp('vars', 'ts2'); +SELECT pgv_get_timestamptz('vars', 'tstz1'); +SELECT pgv_get_timestamptz('vars', 'tstz2'); +SELECT pgv_get_date('vars', 'd1'); +SELECT pgv_get_date('vars', 'd2'); +SELECT pgv_get_jsonb('vars2', 'j1'); +SELECT pgv_get_jsonb('vars2', 'j2'); + +-- Check values after rollback to savepoint +ROLLBACK TO comm2; +SELECT pgv_get('vars', 'any1',NULL::text); +SELECT pgv_get('vars', 'any2',NULL::text); +SELECT pgv_get_int('vars', 'int1'); +SELECT pgv_get_int('vars', 'int2'); +SELECT pgv_get_int('vars', 'intNULL'); +SELECT pgv_get_text('vars', 'str1'); +SELECT pgv_get_text('vars', 'str2'); +SELECT pgv_get_numeric('vars', 'num1'); +SELECT pgv_get_numeric('vars', 'num2'); +SELECT pgv_get_timestamp('vars', 'ts1'); +SELECT pgv_get_timestamp('vars', 'ts2'); +SELECT pgv_get_timestamptz('vars', 'tstz1'); +SELECT pgv_get_timestamptz('vars', 'tstz2'); +SELECT pgv_get_date('vars', 'd1'); +SELECT pgv_get_date('vars', 'd2'); +SELECT pgv_get_jsonb('vars2', 'j1'); +SELECT pgv_get_jsonb('vars2', 'j2'); +COMMIT; + + +-- Record variables +BEGIN; +SAVEPOINT comm2; +SELECT pgv_delete('vars3', 'r1', 5); +SELECT pgv_delete('vars3', 'r2', 5); +SELECT pgv_select('vars3', 'r1'); +SELECT pgv_select('vars3', 'r2'); +ROLLBACK to comm2; +SELECT pgv_select('vars3', 'r1'); +SELECT pgv_select('vars3', 'r2'); +COMMIT; + + +-- TRYING TO CHANGE FLAG 'IS_TRANSACTIONAL' +SELECT pgv_set('vars', 'any1', 'value'::text); +SELECT pgv_set('vars', 'any2', 'value'::text, true); +SELECT pgv_set_int('vars', 'int1', 301); +SELECT pgv_set_int('vars', 'int2', 302, true); +SELECT pgv_set_text('vars', 'str1', 's301'); +SELECT pgv_set_text('vars', 'str2', 's302', true); +SELECT pgv_set_numeric('vars', 'num1', 3.01); +SELECT pgv_set_numeric('vars', 'num2', 3.02, true); +SELECT pgv_set_timestamp('vars', 'ts1', '2016-03-30 20:00:00'); +SELECT pgv_set_timestamp('vars', 'ts2', '2016-03-30 21:00:00', true); +SELECT pgv_set_timestamptz('vars', 'tstz1', '2016-03-30 20:00:00 GMT+01'); +SELECT pgv_set_timestamptz('vars', 'tstz2', '2016-03-30 21:00:00 GMT+02', true); +SELECT pgv_set_date('vars', 'd1', '2016-04-29'); +SELECT pgv_set_date('vars', 'd2', '2016-04-30', true); +SELECT pgv_set_jsonb('vars2', 'j1', '[1, 2, "foo2", null]'); +SELECT pgv_set_jsonb('vars2', 'j2', '{"bar": "baz2", "balance": 7.77, "active": true}', true); +SELECT pgv_insert('vars3', 'r1', row(6 :: integer, 'str66' :: varchar)); +SELECT pgv_insert('vars3', 'r2', row(6 :: integer, 'str66' :: varchar),true); + +-- CHECK pgv_list() WHILE WE HAVE A LOT OF MISCELLANEOUS VARIABLES +SELECT * FROM pgv_list() order by package, name; + +SELECT pgv_free(); + +-- 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); +SELECT pgv_set('vars', 'any2', 'text value'::text); +SELECT pgv_insert('vars3', 'r1', row(6 :: integer, 'str44' :: varchar), true); +SELECT pgv_insert('vars3', 'r2', row(6 :: integer, 'str44' :: varchar)); +ROLLBACK TO sp_to_rollback; +COMMIT; +SELECT pgv_get('vars', 'any1',NULL::text); +SELECT pgv_get('vars', 'any2',NULL::text); +SELECT pgv_select('vars3', 'r1'); +SELECT pgv_select('vars3', 'r2'); + +SELECT pgv_free(); + + +-- CHECK ROLLBACK AFTER COMMITTING SUBTRANSACTION +BEGIN; +SELECT pgv_set('vars', 'any1', 'before savepoint sp1'::text, true); +SAVEPOINT sp1; +SELECT pgv_set('vars', 'any1', 'after savepoint sp1'::text, true); +SAVEPOINT sp2; +SELECT pgv_set('vars', 'any1', 'after savepoint sp2'::text, true); +RELEASE sp2; +SELECT pgv_get('vars', 'any1',NULL::text); +ROLLBACK TO sp1; +SELECT pgv_get('vars', 'any1',NULL::text); +COMMIT; + +BEGIN; +SAVEPOINT sp1; +SAVEPOINT sp2; +SELECT pgv_set('vars2', 'any1', 'variable exists'::text, true); +RELEASE sp2; +SELECT pgv_get('vars2', 'any1',NULL::text); +ROLLBACK TO sp1; +COMMIT; +SELECT pgv_get('vars2', 'any1',NULL::text); +SELECT pgv_free(); + +--CHECK TRANSACTION COMMIT +-- Declare variables +SELECT pgv_set('vars', 'any1', 'some value'::text, true); +SELECT pgv_set('vars', 'any2', 'some value'::text); + +BEGIN; +-- Set new values +SELECT pgv_set('vars', 'any1', 'another value'::text, true); +SELECT pgv_set('vars', 'any2', 'another value'::text); +-- Check values before committing transaction +SELECT pgv_get('vars', 'any1',NULL::text); +SELECT pgv_get('vars', 'any2',NULL::text); +-- Check values after committing transaction +COMMIT; +SELECT pgv_get('vars', 'any1',NULL::text); +SELECT pgv_get('vars', 'any2',NULL::text); + + +SELECT pgv_insert('vars3', 'r1', tab, true) FROM tab; +SELECT pgv_insert('vars3', 'r2', tab) FROM tab; +BEGIN; +SELECT pgv_insert('vars3', 'r1', row(5 :: integer, 'str55' :: varchar),true); +SELECT pgv_insert('vars3', 'r2', row(5 :: integer, 'str55' :: varchar)); +SELECT pgv_select('vars3', 'r1'); +SELECT pgv_select('vars3', 'r2'); +COMMIT; +SELECT pgv_select('vars3', 'r1'); +SELECT pgv_select('vars3', 'r2'); + + +-- CHECK TRANSACTION ROLLBACK +-- Variables are already declared +BEGIN; +-- Set new values +SELECT pgv_set('vars', 'any1', 'one more value'::text, true); +SELECT pgv_set('vars', 'any2', 'one more value'::text); + +-- Check values before rollback +SELECT pgv_get('vars', 'any1',NULL::text); +SELECT pgv_get('vars', 'any2',NULL::text); + +-- Check values after rollback +ROLLBACK; +SELECT pgv_get('vars', 'any1',NULL::text); +SELECT pgv_get('vars', 'any2',NULL::text); + +-- Record variables +BEGIN; +SELECT pgv_delete('vars3', 'r1', 5); +SELECT pgv_delete('vars3', 'r2', 5); +SELECT pgv_select('vars3', 'r1'); +SELECT pgv_select('vars3', 'r2'); +ROLLBACK; +SELECT pgv_select('vars3', 'r1'); +SELECT pgv_select('vars3', 'r2'); + +SELECT pgv_free(); + + +-- VARIABLES DECLARED IN TRANSACTION SHOULD BE DESTROYED AFTER ROLLBACK +BEGIN; +SELECT pgv_set('vars', 'any1', 'text value'::text, true); +SELECT pgv_set('vars', 'any2', 'text value'::text); +SELECT pgv_insert('vars', 'r1', row(6 :: integer, 'str44' :: varchar), true); +SELECT pgv_insert('vars', 'r2', row(6 :: integer, 'str44' :: varchar)); +ROLLBACK; +SELECT pgv_get('vars', 'any1',NULL::text); +SELECT pgv_get('vars', 'any2',NULL::text); +SELECT pgv_select('vars', 'r1'); +SELECT pgv_select('vars', 'r2'); + +SELECT pgv_remove('vars'); + + +-- CHECK ROLLBACK AFTER COMMITTING SUBTRANSACTION +SELECT pgv_set('vars', 'any1', 'before transaction block'::text, true); +BEGIN; +SELECT pgv_set('vars', 'any1', 'before savepoint sp1'::text, true); +SAVEPOINT sp1; +SELECT pgv_set('vars', 'any1', 'after savepoint sp1'::text, true); +SAVEPOINT sp2; +SELECT pgv_set('vars', 'any1', 'after savepoint sp2'::text, true); +RELEASE sp2; +SELECT pgv_get('vars', 'any1',NULL::text); +ROLLBACK TO sp1; +SELECT pgv_get('vars', 'any1',NULL::text); +ROLLBACK; +SELECT pgv_get('vars', 'any1',NULL::text); + +BEGIN; +SAVEPOINT sp1; +SELECT pgv_set('vars2', 'any1', 'variable exists'::text, true); +RELEASE sp1; +SELECT pgv_get('vars2', 'any1',NULL::text); +ROLLBACK; +SELECT pgv_get('vars2', 'any1',NULL::text); + +SELECT pgv_free(); + +-- Additional tests +SELECT pgv_insert('vars3', 'r1', tab, true) FROM tab; +BEGIN; +SELECT pgv_insert('vars3', 'r1', row(5 :: integer, 'before savepoint sp1' :: varchar),true); +SAVEPOINT sp1; +SELECT pgv_update('vars3', 'r1', row(5 :: integer, 'after savepoint sp1' :: varchar)); +SAVEPOINT sp2; +SELECT pgv_insert('vars3', 'r1', row(7 :: integer, 'row after sp2 to remove in sp4' :: varchar),true); +SAVEPOINT sp3; +SAVEPOINT sp4; +SELECT pgv_delete('vars3', 'r1', 7); +SAVEPOINT sp5; +SELECT pgv_select('vars3', 'r1'); +ROLLBACK TO sp5; +SELECT pgv_select('vars3', 'r1'); +RELEASE sp4; +SELECT pgv_select('vars3', 'r1'); +ROLLBACK TO sp3; +SELECT pgv_select('vars3', 'r1'); +RELEASE sp2; +SELECT pgv_select('vars3', 'r1'); +ROLLBACK TO sp1; +SELECT pgv_select('vars3', 'r1'); +COMMIT; +SELECT pgv_select('vars3', 'r1'); + +SELECT pgv_set('vars', 'any1', 'outer'::text, true); +BEGIN; +SELECT pgv_set('vars', 'any1', 'begin'::text, true); +SAVEPOINT sp1; +SELECT pgv_set('vars', 'any1', 'sp1'::text, true); +SAVEPOINT sp2; +SELECT pgv_set('vars', 'any1', 'sp2'::text, true); +SAVEPOINT sp3; +SAVEPOINT sp4; +SELECT pgv_set('vars', 'any1', 'sp4'::text, true); +SAVEPOINT sp5; +SELECT pgv_get('vars', 'any1',NULL::text); +ROLLBACK TO sp5; +SELECT pgv_get('vars', 'any1',NULL::text); +RELEASE sp4; +SELECT pgv_get('vars', 'any1',NULL::text); +ROLLBACK TO sp3; +SELECT pgv_get('vars', 'any1',NULL::text); +RELEASE sp2; +SELECT pgv_get('vars', 'any1',NULL::text); +ROLLBACK TO sp1; +SELECT pgv_get('vars', 'any1',NULL::text); +ROLLBACK; +SELECT pgv_get('vars', 'any1',NULL::text); + +BEGIN; +SELECT pgv_set('vars', 'any1', 'wrong type'::varchar, true); +COMMIT; + +-- THE REMOVAL OF THE VARIABLE MUST BE CANCELED ON ROLLBACK +SELECT pgv_set('vars', 'any1', 'variable exists'::text, true); +BEGIN; +SELECT pgv_remove('vars', 'any1'); +SELECT pgv_exists('vars', 'any1'); +ROLLBACK; +SELECT pgv_exists('vars', 'any1'); +SELECT pgv_get('vars', 'any1',NULL::text); + +BEGIN; +SELECT pgv_remove('vars', 'any1'); +SELECT pgv_exists('vars', 'any1'); +COMMIT; +SELECT pgv_exists('vars', 'any1'); +SELECT pgv_get('vars', 'any1',NULL::text); + +SELECT * FROM pgv_list() ORDER BY package, name; +BEGIN; +SELECT pgv_free(); +ROLLBACK; +SELECT * FROM pgv_list() ORDER BY package, name; + +BEGIN; +SELECT pgv_free(); +COMMIT; +SELECT * FROM pgv_list() ORDER BY package, name; + +SELECT pgv_set('vars', 'regular', 'regular variable exists'::text); +SELECT pgv_set('vars', 'trans1', 'trans1 variable exists'::text, true); +BEGIN; +SELECT pgv_free(); +SELECT pgv_free(); -- Check sequential package removal in one subtransaction +SELECT * FROM pgv_list() ORDER BY package, name; +SELECT pgv_set('vars', 'trans2', 'trans2 variable exists'::text, true); +SELECT * FROM pgv_list() ORDER BY package, name; +SELECT pgv_remove('vars'); +SELECT * FROM pgv_list() ORDER BY package, name; +ROLLBACK; +SELECT * FROM pgv_list() ORDER BY package, name; + +BEGIN; +SAVEPOINT sp1; +SAVEPOINT sp2; +SAVEPOINT sp3; +SELECT pgv_set('vars2', 'trans2', 'trans2 variable exists'::text, true); +SAVEPOINT sp4; +SAVEPOINT sp5; +SELECT pgv_free(); +SELECT package FROM pgv_stats() ORDER BY package; +SELECT * FROM pgv_list() ORDER BY package, name; +RELEASE sp5; +SELECT package FROM pgv_stats() ORDER BY package; +SELECT * FROM pgv_list() ORDER BY package, name; +RELEASE sp4; +SELECT package FROM pgv_stats() ORDER BY package; +SELECT * FROM pgv_list() ORDER BY package, name; +COMMIT; +SELECT package FROM pgv_stats() ORDER BY package; + +BEGIN; +SELECT pgv_set('vars', 'trans1', 'package created'::text, true); +SELECT pgv_remove('vars'); +SELECT * FROM pgv_list() ORDER BY package, name; +SELECT pgv_set('vars', 'trans1', 'package restored'::text, true); +SELECT * FROM pgv_list() ORDER BY package, name; +COMMIT; +SELECT pgv_remove('vars'); + +-- 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; +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'::text, 'str1'::text))); +SELECT pgv_select('vars4', 'r1', 0); + +-- 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); +SAVEPOINT comm; +SELECT pgv_remove('vars', 'any1'); +RELEASE comm; +SELECT pgv_get('vars', 'any1',NULL::text); +COMMIT; + +-- Tests for PGPRO-2440 +SELECT pgv_insert('vars3', 'r3', row(1 :: integer, NULL::varchar), true); +BEGIN; +SELECT pgv_insert('vars3', 'r3', row(2 :: integer, NULL::varchar), true); +SAVEPOINT comm; +SELECT pgv_insert('vars3', 'r3', row(3 :: integer, NULL::varchar), true); +COMMIT; +SELECT pgv_delete('vars3', 'r3', 3); + +BEGIN; +SELECT pgv_set('vars1', 't1', ''::text); +SELECT pgv_set('vars2', 't2', ''::text, true); +SAVEPOINT sp1; +SAVEPOINT sp2; +SELECT pgv_free(); +ERROR; +COMMIT; + +BEGIN; +SELECT pgv_set('vars', 'any1', 'some value'::text, true); +SELECT pgv_free(); +SAVEPOINT sp_to_rollback; +SELECT pgv_set('vars', 'any1', 'some value'::text, true); +ROLLBACK TO sp_to_rollback; +COMMIT; +SELECT package FROM pgv_stats() ORDER BY package; + +-- Package should exist after rollback if it contains regular variable +BEGIN; +SELECT pgv_set('vars', 'any1', 'some value'::text); +ROLLBACK; +SELECT package FROM pgv_stats() ORDER BY package; + +-- Package should not exist if it becomes empty in rolled back transaction +BEGIN; +SAVEPOINT comm2; +SELECT pgv_remove('vars'); +ROLLBACK TO comm2; +SELECT pgv_exists('vars'); +SELECT package FROM pgv_stats() ORDER BY package; +COMMIT; +SELECT package FROM pgv_stats() ORDER BY package; + +SELECT pgv_set('vars', 'any1', 'some value'::text); +BEGIN; +SELECT pgv_remove('vars'); +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; diff --git a/valgrind.supp b/valgrind.supp new file mode 100644 index 0000000..e69de29