diff --git a/.gitignore b/.gitignore index 965c36b..0f55eec 100644 --- a/.gitignore +++ b/.gitignore @@ -38,5 +38,5 @@ lib*.pc /Debug/ /Release/ /tmp_install/ - Dockerfile +pg_variables--1.1.sql diff --git a/Makefile b/Makefile index 49592e0..2f47be0 100644 --- a/Makefile +++ b/Makefile @@ -4,10 +4,13 @@ MODULE_big = pg_variables OBJS = pg_variables.o pg_variables_record.o $(WIN32RES) EXTENSION = pg_variables -DATA = pg_variables--1.0.sql +EXTVERSION = 1.1 +DATA = pg_variables--1.0.sql pg_variables--1.0--1.1.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 ifdef USE_PGXS PG_CONFIG = pg_config @@ -19,3 +22,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 39a008d..d017a32 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,9 @@ 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; @@ -19,13 +18,29 @@ SELECT pgv_set('vars', 'int2', 102); ROLLBACK; SELECT * FROM pgv_list() order by package, name; - package | name ----------+------ - vars | int1 - vars | int2 + package | name | is_transactional +---------+------+------------------ + vars | int1 | f + vars | int2 | f (2 rows) ``` +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 +(1 row) +``` + ## License This module available under the same license as @@ -76,7 +91,7 @@ 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` ## **Deprecated** scalar variables functions @@ -85,49 +100,49 @@ Function | Returns 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 @@ -146,7 +161,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. @@ -162,7 +177,7 @@ 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_list()` | `table(package text, name text, is_transactional bool)` | 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. Note that **pgv_stats()** works only with the PostgreSQL 9.6 and newer. @@ -176,13 +191,13 @@ SELECT pgv_set('vars', 'int1', 101); SELECT pgv_set('vars', 'int2', 102); 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 + pgv_get_int ------------- 102 (1 row) @@ -239,11 +254,11 @@ You can list packages and variables: ```sql SELECT * FROM pgv_list() order by package, name; - package | name ----------+------ - vars | int1 - vars | int2 - vars | r1 + package | name | is_transactional +---------+------+------------------ + vars | int1 | f + vars | int2 | f + vars | r1 | f (3 rows) ``` @@ -257,7 +272,7 @@ SELECT * FROM pgv_stats() order by package; (1 row) ``` -You can delete variables or hole packages: +You can delete variables or whole packages: ```sql SELECT pgv_remove('vars', 'int1'); @@ -268,3 +283,70 @@ 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 +(1 row) + +ROLLBACK; +SELECT pgv_get('pack', 'var_text', NULL::text); + pgv_get +-------------------------- + before transaction block + +``` +If you create variable after `BEGIN` or `SAVEPOINT` statements and than 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 +(1 row) + +ROLLBACK TO sp1; +SELECT pgv_get('pack','var_int', NULL::int); +ERROR: unrecognized variable "var_int" +COMMIT; +``` +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); + pgv_insert +------------ + +(1 row) + +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/expected/pg_variables.out b/expected/pg_variables.out index 326b3f1..7293c6c 100644 --- a/expected/pg_variables.out +++ b/expected/pg_variables.out @@ -645,32 +645,40 @@ SELECT pgv_select('vars2', 'j1'); ERROR: variable "j1" requires "jsonb" value -- 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 + 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 (22 rows) +SELECT package FROM pgv_stats() order by package; + package +--------- + vars + vars2 + vars3 +(3 rows) + SELECT pgv_remove('vars', 'int3'); ERROR: unrecognized variable "int3" SELECT pgv_remove('vars', 'int1'); @@ -702,27 +710,27 @@ 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 + 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 (19 rows) SELECT pgv_free(); @@ -738,7 +746,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_any.out b/expected/pg_variables_any.out index b7b6cb9..12987b0 100644 --- a/expected/pg_variables_any.out +++ b/expected/pg_variables_any.out @@ -532,29 +532,29 @@ SELECT pgv_get('vars', 'jNULL', NULL::jsonb); -- 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 + 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 (21 rows) SELECT pgv_remove('vars', 'int3'); @@ -588,26 +588,26 @@ 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 + 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 (18 rows) SELECT pgv_free(); @@ -623,7 +623,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_trans.out b/expected/pg_variables_trans.out new file mode 100644 index 0000000..8d83c6a --- /dev/null +++ b/expected/pg_variables_trans.out @@ -0,0 +1,1600 @@ +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; +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) + (2,) + (1,str33) + (5,str55) + (0,str00) +(5 rows) + +SELECT pgv_select('vars3', 'r2'); + pgv_select +------------ + (,strNULL) + (2,) + (1,str33) + (5,str55) + (0,str00) +(5 rows) + +RELEASE comm; +SELECT pgv_select('vars3', 'r1'); + pgv_select +------------ + (,strNULL) + (2,) + (1,str33) + (5,str55) + (0,str00) +(5 rows) + +SELECT pgv_select('vars3', 'r2'); + pgv_select +------------ + (,strNULL) + (2,) + (1,str33) + (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) + (2,) + (1,str33) + (0,str00) +(4 rows) + +SELECT pgv_select('vars3', 'r2'); + pgv_select +------------ + (,strNULL) + (2,) + (1,str33) + (0,str00) +(4 rows) + +ROLLBACK to comm2; +SELECT pgv_select('vars3', 'r1'); + pgv_select +------------ + (,strNULL) + (2,) + (1,str33) + (5,str55) + (0,str00) +(5 rows) + +SELECT pgv_select('vars3', 'r2'); + pgv_select +------------ + (,strNULL) + (2,) + (1,str33) + (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 variable "any1" +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) + (2,) + (1,str33) + (5,str55) + (0,str00) +(5 rows) + +SELECT pgv_select('vars3', 'r2'); + pgv_select +------------ + (,strNULL) + (2,) + (1,str33) + (5,str55) + (0,str00) +(5 rows) + +COMMIT; +SELECT pgv_select('vars3', 'r1'); + pgv_select +------------ + (,strNULL) + (2,) + (1,str33) + (5,str55) + (0,str00) +(5 rows) + +SELECT pgv_select('vars3', 'r2'); + pgv_select +------------ + (,strNULL) + (2,) + (1,str33) + (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) + (2,) + (1,str33) + (0,str00) +(4 rows) + +SELECT pgv_select('vars3', 'r2'); + pgv_select +------------ + (,strNULL) + (2,) + (1,str33) + (0,str00) +(4 rows) + +ROLLBACK; +SELECT pgv_select('vars3', 'r1'); + pgv_select +------------ + (,strNULL) + (2,) + (1,str33) + (5,str55) + (0,str00) +(5 rows) + +SELECT pgv_select('vars3', 'r2'); + pgv_select +------------ + (,strNULL) + (2,) + (1,str33) + (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 variable "any1" +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) + (2,) + (1,str33) + (5,"after savepoint sp1") + (0,str00) +(5 rows) + +ROLLBACK TO sp5; +SELECT pgv_select('vars3', 'r1'); + pgv_select +--------------------------- + (,strNULL) + (2,) + (1,str33) + (5,"after savepoint sp1") + (0,str00) +(5 rows) + +RELEASE sp4; +SELECT pgv_select('vars3', 'r1'); + pgv_select +--------------------------- + (,strNULL) + (2,) + (1,str33) + (5,"after savepoint sp1") + (0,str00) +(5 rows) + +ROLLBACK TO sp3; +SELECT pgv_select('vars3', 'r1'); + pgv_select +-------------------------------------- + (,strNULL) + (2,) + (1,str33) + (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) + (2,) + (1,str33) + (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) + (2,) + (1,str33) + (5,"before savepoint sp1") + (0,str00) +(5 rows) + +COMMIT; +SELECT pgv_select('vars3', 'r1'); + pgv_select +---------------------------- + (,strNULL) + (2,) + (1,str33) + (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; 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.c b/pg_variables.c old mode 100644 new mode 100755 index 975f826..329f73e --- a/pg_variables.c +++ b/pg_variables.c @@ -12,6 +12,7 @@ #include "funcapi.h" #include "access/htup_details.h" +#include "access/xact.h" #include "catalog/pg_type.h" #include "parser/scansup.h" #include "utils/builtins.h" @@ -63,15 +64,23 @@ 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); +extern void _PG_fini(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 HashVariableEntry * +getVariableInternal(HTAB *variables, text *name, Oid typid, bool strict); +static HashVariableEntry * +createVariableInternal(HashPackageEntry *package, text *name, Oid typid, + bool is_transactional); +static void +createSavepoint(HashPackageEntry *package, HashVariableEntry *variable); +static bool +isVarChangedInTrans(HashVariableEntry *variable); +static void +addToChangedVars(HashPackageEntry *package, HashVariableEntry *variable); #define CHECK_ARGS_FOR_NULL() \ do { \ @@ -93,23 +102,35 @@ static HashPackageEntry *LastPackage = NULL; /* Recent variable */ static HashVariableEntry *LastVariable = NULL; +/* + * List of variables, changed in top level transaction. Used to limit + * number of proceeded variables on start of transaction. + */ +static dlist_head *changedVars = NULL; +static MemoryContext changedVarsContext = NULL; +static dlist_head *changedVarsStack = NULL; +#define get_actual_changed_vars_list() \ + ((dlist_head_element(ChangedVarsStackNode, node, changedVarsStack))-> \ + changedVarsList) + + /* * 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; + MemoryContext oldcxt; package = getPackageByName(package_name, true, false); - variable = getVariableByNameWithType(package->variablesHash, - var_name, typid, true, false); - - scalar = &variable->value.scalar; + variable = createVariableInternal(package, var_name, typid, + is_transactional); + scalar = get_actual_value_scalar(variable); /* Release memory for variable */ if (scalar->typbyval == false && scalar->is_null == false) pfree(DatumGetPointer(scalar->value)); @@ -117,11 +138,8 @@ variable_set(text *package_name, text *var_name, scalar->is_null = is_null; if (!scalar->is_null) { - MemoryContext oldcxt; oldcxt = MemoryContextSwitchTo(package->hctx); - scalar->value = datumCopy(value, scalar->typbyval, scalar->typlen); - MemoryContextSwitchTo(oldcxt); } else @@ -143,17 +161,15 @@ variable_get(text *package_name, text *var_name, return 0; } - variable = getVariableByNameWithType(package->variablesHash, - var_name, typid, false, strict); + variable = getVariableInternal(package->variablesHash, + var_name, typid, strict); if (variable == NULL) { *is_null = true; return 0; } - - scalar = &(variable->value.scalar); - + scalar = get_actual_value_scalar(variable); *is_null = scalar->is_null; return scalar->value; } @@ -163,15 +179,17 @@ variable_set_any(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, get_fn_expr_argtype(fcinfo->flinfo, 2), PG_ARGISNULL(2) ? 0 : PG_GETARG_DATUM(2), - PG_ARGISNULL(2)); + PG_ARGISNULL(2), is_transactional); PG_FREE_IF_COPY(package_name, 0); PG_FREE_IF_COPY(var_name, 1); @@ -210,15 +228,17 @@ variable_set_int(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, INT4OID, PG_ARGISNULL(2) ? 0 : PG_GETARG_DATUM(2), - PG_ARGISNULL(2)); + PG_ARGISNULL(2), is_transactional); PG_FREE_IF_COPY(package_name, 0); PG_FREE_IF_COPY(var_name, 1); @@ -256,15 +276,17 @@ variable_set_text(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, TEXTOID, PG_ARGISNULL(2) ? 0 : PG_GETARG_DATUM(2), - PG_ARGISNULL(2)); + PG_ARGISNULL(2), is_transactional); PG_FREE_IF_COPY(package_name, 0); PG_FREE_IF_COPY(var_name, 1); @@ -302,15 +324,17 @@ variable_set_numeric(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, NUMERICOID, PG_ARGISNULL(2) ? 0 : PG_GETARG_DATUM(2), - PG_ARGISNULL(2)); + PG_ARGISNULL(2), is_transactional); PG_FREE_IF_COPY(package_name, 0); PG_FREE_IF_COPY(var_name, 1); @@ -348,15 +372,17 @@ variable_set_timestamp(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, TIMESTAMPOID, PG_ARGISNULL(2) ? 0 : PG_GETARG_DATUM(2), - PG_ARGISNULL(2)); + PG_ARGISNULL(2), is_transactional); PG_FREE_IF_COPY(package_name, 0); PG_FREE_IF_COPY(var_name, 1); @@ -394,15 +420,17 @@ variable_set_timestamptz(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, TIMESTAMPTZOID, PG_ARGISNULL(2) ? 0 : PG_GETARG_DATUM(2), - PG_ARGISNULL(2)); + PG_ARGISNULL(2), is_transactional); PG_FREE_IF_COPY(package_name, 0); PG_FREE_IF_COPY(var_name, 1); @@ -440,15 +468,17 @@ variable_set_date(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, DATEOID, PG_ARGISNULL(2) ? 0 : PG_GETARG_DATUM(2), - PG_ARGISNULL(2)); + PG_ARGISNULL(2), is_transactional); PG_FREE_IF_COPY(package_name, 0); PG_FREE_IF_COPY(var_name, 1); @@ -486,15 +516,17 @@ variable_set_jsonb(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, JSONBOID, PG_ARGISNULL(2) ? 0 : PG_GETARG_DATUM(2), - PG_ARGISNULL(2)); + PG_ARGISNULL(2), is_transactional); PG_FREE_IF_COPY(package_name, 0); PG_FREE_IF_COPY(var_name, 1); @@ -535,6 +567,7 @@ variable_insert(PG_FUNCTION_ARGS) HeapTupleHeader rec; HashPackageEntry *package; HashVariableEntry *variable; + bool is_transactional; Oid tupType; int32 tupTypmod; @@ -552,6 +585,7 @@ variable_insert(PG_FUNCTION_ARGS) package_name = PG_GETARG_TEXT_PP(0); var_name = PG_GETARG_TEXT_PP(1); rec = PG_GETARG_HEAPTUPLEHEADER(2); + is_transactional = PG_GETARG_BOOL(3); /* Get cached package */ if (LastPackage == NULL || @@ -572,19 +606,37 @@ variable_insert(PG_FUNCTION_ARGS) strncmp(VARDATA_ANY(var_name), LastVariable->name, VARSIZE_ANY_EXHDR(var_name)) != 0) { - variable = getVariableByNameWithType(package->variablesHash, - var_name, RECORDOID, true, false); + variable = createVariableInternal(package, var_name, RECORDOID, + is_transactional); LastVariable = variable; } else - variable = LastVariable; + { + if (LastVariable->is_transactional == is_transactional) + variable = LastVariable; + else + { + char key[NAMEDATALEN]; + getKeyFromName(var_name, key); + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg( + "variable \"%s\" already created as %sTRANSACTIONAL", + key, LastVariable->is_transactional?"":"NOT "))); + } + if (!isVarChangedInTrans(variable) && variable->is_transactional) + { + createSavepoint(package, variable); + addToChangedVars(package, variable); + } + } /* Insert a record */ tupType = HeapTupleHeaderGetTypeId(rec); tupTypmod = HeapTupleHeaderGetTypMod(rec); tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); - if (!variable->value.record.tupdesc) + if (!(get_actual_value_record(variable))->tupdesc) { /* * This is the first record for the var_name. Initialize attributes. @@ -651,13 +703,19 @@ variable_update(PG_FUNCTION_ARGS) strncmp(VARDATA_ANY(var_name), LastVariable->name, VARSIZE_ANY_EXHDR(var_name)) != 0) { - variable = getVariableByNameWithType(package->variablesHash, - var_name, RECORDOID, false, true); + variable = getVariableInternal(package->variablesHash, + var_name, RECORDOID, true); LastVariable = variable; } else variable = LastVariable; + if (variable->is_transactional && !isVarChangedInTrans(variable)) + { + createSavepoint(package, variable); + addToChangedVars(package, variable); + } + /* Update a record */ tupType = HeapTupleHeaderGetTypeId(rec); tupTypmod = HeapTupleHeaderGetTypMod(rec); @@ -723,12 +781,17 @@ variable_delete(PG_FUNCTION_ARGS) strncmp(VARDATA_ANY(var_name), LastVariable->name, VARSIZE_ANY_EXHDR(var_name)) != 0) { - variable = getVariableByNameWithType(package->variablesHash, var_name, - RECORDOID, false, true); + variable = getVariableInternal(package->variablesHash, + var_name, RECORDOID, true); LastVariable = variable; } else variable = LastVariable; + if (variable->is_transactional && !isVarChangedInTrans(variable)) + { + createSavepoint(package, variable); + addToChangedVars(package, variable); + } /* Delete a record */ if (!value_is_null) @@ -765,10 +828,10 @@ variable_select(PG_FUNCTION_ARGS) var_name = PG_GETARG_TEXT_PP(1); package = getPackageByName(package_name, false, true); - variable = getVariableByNameWithType(package->variablesHash, - var_name, RECORDOID, false, true); + variable = getVariableInternal(package->variablesHash, + var_name, RECORDOID, true); - record = &(variable->value.record); + record = get_actual_value_record(variable); funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); @@ -838,13 +901,13 @@ variable_select_by_value(PG_FUNCTION_ARGS) } package = getPackageByName(package_name, false, true); - variable = getVariableByNameWithType(package->variablesHash, - var_name, RECORDOID, false, true); + variable = getVariableInternal(package->variablesHash, + var_name, RECORDOID, true); if (!value_is_null) check_record_key(variable, value_type); - record = &(variable->value.record); + record = get_actual_value_record(variable); /* Search a record */ k.value = value; @@ -908,15 +971,16 @@ variable_select_by_values(PG_FUNCTION_ARGS) var_name = PG_GETARG_TEXT_PP(1); package = getPackageByName(package_name, false, true); - variable = getVariableByNameWithType(package->variablesHash, - var_name, RECORDOID, false, true); + variable = getVariableInternal(package->variablesHash, + var_name, RECORDOID, 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); + funcctx->tuple_desc = CreateTupleDescCopy( + get_actual_value_record(variable)->tupdesc); var = (VariableIteratorRec *) palloc(sizeof(VariableIteratorRec)); var->iterator = array_create_iterator(values, 0, NULL); @@ -938,7 +1002,7 @@ variable_select_by_values(PG_FUNCTION_ARGS) bool found; RecordVar *record; - record = &(var->variable->value.record); + record = get_actual_value_record(var->variable); /* Search a record */ k.value = value; k.is_null = isnull; @@ -946,7 +1010,7 @@ variable_select_by_values(PG_FUNCTION_ARGS) k.cmp_proc = &record->cmp_proc; item = (HashRecordEntry *) hash_search(record->rhash, &k, - HASH_FIND, &found); + HASH_FIND, &found); if (found) { Datum result; @@ -962,14 +1026,38 @@ variable_select_by_values(PG_FUNCTION_ARGS) SRF_RETURN_DONE(funcctx); } +/* + * Remove one entry from history of states of arg 'variable' + */ static void -clean_variable(HashVariableEntry *variable) +cleanVariableCurrentState(HashVariableEntry *variable) { + ValueHistory *history; + ValueHistoryEntry *historyEntryToDelete; 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)); + else + { + ScalarVar *scalar = get_actual_value_scalar(variable); + if (scalar->typbyval == false && scalar->is_null == false) + pfree(DatumGetPointer(scalar->value)); + } + history = &variable->data; + historyEntryToDelete = get_history_entry(dlist_pop_head_node(history)); + pfree(historyEntryToDelete); +} + +/* + * Remove all entries from history of states of arg 'variable'. + * DOES NOT remove 'variable' itself. + */ +static void +cleanVariableAllStates(HashVariableEntry *variable) +{ + while(!dlist_is_empty(&variable->data)) + { + cleanVariableCurrentState(variable); + } } /* @@ -1061,7 +1149,7 @@ remove_variable(PG_FUNCTION_ARGS) /* Remove variable from cache */ LastVariable = NULL; - clean_variable(variable); + cleanVariableAllStates(variable); PG_FREE_IF_COPY(package_name, 0); PG_FREE_IF_COPY(var_name, 1); @@ -1104,7 +1192,6 @@ remove_package(PG_FUNCTION_ARGS) LastPackage = NULL; LastVariable = NULL; - hash_destroy(package->variablesHash); /* All variables will be freed */ MemoryContextDelete(package->hctx); @@ -1127,12 +1214,12 @@ remove_packages(PG_FUNCTION_ARGS) LastPackage = NULL; LastVariable = NULL; - hash_destroy(packagesHash); /* All packages and variables will be freed */ MemoryContextDelete(ModuleContext); - packagesHash = NULL; + packagesHash = NULL; ModuleContext = NULL; + changedVars = NULL; PG_RETURN_VOID(); } @@ -1144,6 +1231,7 @@ typedef struct { char *package; char *variable; + bool is_transactional; } VariableRec; /* @@ -1208,6 +1296,7 @@ get_packages_and_variables(PG_FUNCTION_ARGS) recs[nRecs].package = package->name; recs[nRecs].variable = variable->name; + recs[nRecs].is_transactional = variable->is_transactional; nRecs++; } } @@ -1228,8 +1317,8 @@ 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; @@ -1240,6 +1329,7 @@ get_packages_and_variables(PG_FUNCTION_ARGS) 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); @@ -1263,7 +1353,11 @@ getMemoryTotalSpace(MemoryContext context, int level, Size *totalspace) /* Examine the context itself */ memset(&totals, 0, sizeof(totals)); +# if PG_VERSION_NUM >= 110000 + (*context->methods->stats) (context, NULL, NULL, &totals); +# else (*context->methods->stats) (context, level, false, &totals); +# endif *totalspace += totals.totalspace; /* @@ -1449,7 +1543,7 @@ getPackageByName(text* name, bool create, bool strict) #if PG_VERSION_NUM >= 110000 package->hctx = AllocSetContextCreateExtended(ModuleContext, - hash_name, 0, + hash_name, ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); @@ -1481,9 +1575,13 @@ getPackageByName(text* name, bool create, bool strict) 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 HashVariableEntry * -getVariableByNameWithType(HTAB *variables, text *name, Oid typid, - bool create, bool strict) +getVariableInternal(HTAB *variables, text *name, Oid typid, bool strict) { HashVariableEntry *variable; char key[NAMEDATALEN]; @@ -1491,11 +1589,7 @@ getVariableByNameWithType(HTAB *variables, text *name, Oid typid, getKeyFromName(name, key); - if (create) - variable = (HashVariableEntry *) hash_search(variables, - key, HASH_ENTER, &found); - else - variable = (HashVariableEntry *) hash_search(variables, + variable = (HashVariableEntry *) hash_search(variables, key, HASH_FIND, &found); /* Check variable type */ @@ -1503,7 +1597,7 @@ getVariableByNameWithType(HTAB *variables, text *name, Oid typid, { if (variable->typid != typid) { - char *var_type = DatumGetCString(DirectFunctionCall1( + char *var_type = DatumGetCString(DirectFunctionCall1( regtypeout, ObjectIdGetDatum(variable->typid))); ereport(ERROR, @@ -1513,25 +1607,454 @@ getVariableByNameWithType(HTAB *variables, text *name, Oid typid, } } 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 HashVariableEntry * +createVariableInternal(HashPackageEntry *package, text *name, Oid typid, + bool is_transactional) +{ + HashVariableEntry *variable; + char key[NAMEDATALEN]; + bool found; + + getKeyFromName(name, key); + + variable = (HashVariableEntry *) hash_search(package->variablesHash, + key, HASH_ENTER, &found); + + /* 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_transactional!=is_transactional) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg( + "variable \"%s\" already created as %sTRANSACTIONAL", + key, variable->is_transactional?"":"NOT "))); + } + + /* + * Savepoint must be created when variable changed in current + * transaction. + * For each transaction level there should be own savepoint. + * New value should be stored in a last state. + */ + if (variable->is_transactional && !isVarChangedInTrans(variable)) + { + createSavepoint(package, variable); + } + } + else { /* Variable entry was created, so initialize new variable. */ if (variable) { - memset(&variable->value, 0, sizeof(variable->value)); + ValueHistoryEntry *historyEntry; variable->typid = typid; + variable->is_transactional = is_transactional; + dlist_init(&(variable->data)); + historyEntry = MemoryContextAllocZero(package->hctx, + sizeof(ValueHistoryEntry)); + dlist_push_head(&variable->data, &historyEntry->node); if (typid != RECORDOID) { - get_typlenbyval(variable->typid, - &variable->value.scalar.typlen, - &variable->value.scalar.typbyval); - variable->value.scalar.is_null = true; + ScalarVar *scalar = get_actual_value_scalar(variable); + get_typlenbyval(variable->typid, &scalar->typlen, + &scalar->typbyval); + scalar->is_null = true; } } - else if (strict) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("unrecognized variable \"%s\"", key))); } + /* If it is necessary, put variable to changedVars */ + if (is_transactional) + addToChangedVars(package, variable); return variable; } + +/* + * Create a new history point of variable and copy value from + * previous state + */ +static void +createSavepoint(HashPackageEntry *package, HashVariableEntry *variable) +{ + + if(variable->typid == RECORDOID) + { + insert_savepoint(variable, package->hctx); + } + else + { + ScalarVar *scalar; + ValueHistory *history; + ValueHistoryEntry *history_entry_new, + *history_entry_prev; + MemoryContext oldcxt; + + oldcxt = MemoryContextSwitchTo(package->hctx); + history = &variable->data; + /* Release memory for variable */ + history_entry_new = palloc0(sizeof(ValueHistoryEntry)); + history_entry_prev = dlist_head_element(ValueHistoryEntry, node, history); + scalar = &history_entry_new->value.scalar; + *scalar = history_entry_prev->value.scalar; + + if (!scalar->is_null) + { + scalar->value = datumCopy( + history_entry_prev->value.scalar.value, + scalar->typbyval, + scalar->typlen); + } + else + scalar->value = 0; + dlist_push_head(history, &history_entry_new->node); + MemoryContextSwitchTo(oldcxt); + } +} + +/* + * Remove previous state of variable + */ +static void +releaseSavepoint(HashVariableEntry *variable) +{ + ValueHistory *history; + + history = &variable->data; + if (dlist_has_next(history, dlist_head_node(history))) + { + ValueHistoryEntry *historyEntryToDelete; + dlist_node *nodeToDelete; + + nodeToDelete = dlist_next_node(history, dlist_head_node(history)); + historyEntryToDelete = get_history_entry(nodeToDelete); + + if (variable->typid == RECORDOID) + { + /* All records will be freed */ + MemoryContextDelete(historyEntryToDelete->value.record.hctx); + } + else if (historyEntryToDelete->value.scalar.typbyval == false && + historyEntryToDelete->value.scalar.is_null == false) + pfree(DatumGetPointer(historyEntryToDelete->value.scalar.value)); + + dlist_delete(nodeToDelete); + pfree(historyEntryToDelete); + } +} + +/* + * Rollback variable to previous state and remove current value + */ +static void +rollbackSavepoint(HashPackageEntry *package, HashVariableEntry *variable) +{ + cleanVariableCurrentState(variable); + /* Remove variable if it was created in rolled back transaction */ + if (dlist_is_empty(&variable->data)) + { + bool found; + hash_search(package->variablesHash, variable->name, HASH_REMOVE, &found); + } +} + +/* + * Check if variable was changed in current transaction level + */ +static bool +isVarChangedInTrans(HashVariableEntry *variable) +{ + dlist_iter iter; + dlist_head *changedVars; + if (!changedVarsStack) + return false; + changedVars = get_actual_changed_vars_list(); + dlist_foreach(iter, changedVars) + { + ChangedVarsNode *cvn; + cvn = dlist_container(ChangedVarsNode, node, iter.cur); + if (cvn->variable == variable) + return true; + } + return false; +} + +/* + * Create new list of variables, changed in current transaction level + */ +static void +pushChangedVarsStack() +{ + MemoryContext oldcxt; + ChangedVarsStackNode *cvsn; + char child_context_name[BUFSIZ]; + /* + * Initialize changedVarsStack and create MemoryContext for it + * if not done before. + */ + if(!changedVarsContext) + { + char top_context_name[BUFSIZ]; + sprintf(top_context_name, "Memory context for changedVarsStack"); +#if PG_VERSION_NUM >= 110000 + changedVarsContext = AllocSetContextCreateExtended(ModuleContext, + top_context_name, + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); +#else + changedVarsContext = AllocSetContextCreate(ModuleContext, + top_context_name, + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); +#endif + } + oldcxt = MemoryContextSwitchTo(changedVarsContext); + if (!changedVarsStack) + { + changedVarsStack = palloc0(sizeof(dlist_head)); + dlist_init(changedVarsStack); + } + cvsn = palloc0(sizeof(ChangedVarsStackNode)); + cvsn->changedVarsList = palloc0(sizeof(dlist_head)); + sprintf(child_context_name, + "Memory context for changedVars list in %d xact level", + GetCurrentTransactionNestLevel()); +#if PG_VERSION_NUM >= 110000 + cvsn->ctx = AllocSetContextCreateExtended(changedVarsContext, + child_context_name, + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); +#else + cvsn->ctx = AllocSetContextCreate(changedVarsContext, + child_context_name, + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); +#endif + dlist_init(cvsn->changedVarsList); + dlist_push_head(changedVarsStack, &cvsn->node); + + MemoryContextSwitchTo(oldcxt); +} + +/* + * Remove list of variables, changed in current transaction level + */ +static void +popChangedVarsStack() +{ + if (changedVarsStack) + { + ChangedVarsStackNode *cvse; + Assert(!dlist_is_empty(changedVarsStack)); + cvse = dlist_container(ChangedVarsStackNode, node, + dlist_pop_head_node(changedVarsStack)); + MemoryContextDelete(cvse->ctx); + if (dlist_is_empty(changedVarsStack)) + { + MemoryContextDelete(changedVarsContext); + changedVarsStack = NULL; + changedVarsContext = NULL; + } + } +} + +/* + * Add a variable to list of changed vars in current transaction level + */ +static void +addToChangedVars(HashPackageEntry *package, HashVariableEntry *variable) +{ + ChangedVarsStackNode *cvsn; + if (!changedVarsStack) + { + int level = GetCurrentTransactionNestLevel(); + while(level -- > 0) + { + pushChangedVarsStack(); + } + } + Assert(changedVarsStack && changedVarsContext); + + if (!isVarChangedInTrans(variable)) + { + ChangedVarsNode *cvn; + cvsn = dlist_head_element(ChangedVarsStackNode, node, changedVarsStack); + cvn = MemoryContextAllocZero(cvsn->ctx, sizeof(ChangedVarsNode)); + cvn->package = package; + cvn->variable = variable; + dlist_push_head(cvsn->changedVarsList, &cvn->node); + } +} + +/* + * If variable was chenged in some subtransaction, it is considered that it was + * changed in parent transaction. So it is important to add this variable to + * list of changes of parent transaction. But if var was already changed in + * upper level, it has savepoint there, so we need to release it. + */ +static void +levelUpOrRelease() +{ + if (changedVarsStack) + { + dlist_iter iter; + ChangedVarsStackNode *bottom_list; + /* List removed from stack but we still can use it */ + bottom_list = dlist_container(ChangedVarsStackNode, node, + dlist_pop_head_node(changedVarsStack)); + Assert(!dlist_is_empty(changedVarsStack)); + dlist_foreach(iter, bottom_list->changedVarsList) + { + ChangedVarsNode *cvn; + cvn = dlist_container(ChangedVarsNode, node, iter.cur); + if(isVarChangedInTrans(cvn->variable)) + releaseSavepoint(cvn->variable); + else + addToChangedVars(cvn->package, cvn->variable); + } + MemoryContextDelete(bottom_list->ctx); + } +} + +/* + * 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; + +/* + * Iterate variables from list of changes and + * apply corresponding action on them + */ +static void +applyActionOnChangedVars(Action action) +{ + dlist_mutable_iter miter; + dlist_head *changedVars; + Assert(changedVarsStack); + changedVars = get_actual_changed_vars_list(); + dlist_foreach_modify(miter, changedVars) + { + ChangedVarsNode *cvn = dlist_container(ChangedVarsNode, node, miter.cur); + switch(action) + { + case RELEASE_SAVEPOINT: + releaseSavepoint(cvn->variable); + break; + case ROLLBACK_TO_SAVEPOINT: + rollbackSavepoint(cvn->package, cvn->variable); + break; + } + } +} + +/* + * Intercept execution during subtransaction processing + */ +static void +pgvSubTransCallback(SubXactEvent event, SubTransactionId mySubid, + SubTransactionId parentSubid, void *arg) +{ + if (changedVarsStack) + { + switch (event) + { + case SUBXACT_EVENT_START_SUB: + pushChangedVarsStack(); + break; + case SUBXACT_EVENT_COMMIT_SUB: + levelUpOrRelease(); + break; + case SUBXACT_EVENT_ABORT_SUB: + applyActionOnChangedVars(ROLLBACK_TO_SAVEPOINT); + popChangedVarsStack(); + break; + case SUBXACT_EVENT_PRE_COMMIT_SUB: + break; + } + } +} + +/* + * Intercept execution during transaction processing + */ +static void +pgvTransCallback(XactEvent event, void *arg) +{ + if (changedVarsStack) + { + switch (event) + { + case XACT_EVENT_PRE_COMMIT: + applyActionOnChangedVars(RELEASE_SAVEPOINT); + popChangedVarsStack(); + break; + case XACT_EVENT_ABORT: + applyActionOnChangedVars(ROLLBACK_TO_SAVEPOINT); + popChangedVarsStack(); + break; + case XACT_EVENT_PARALLEL_PRE_COMMIT: + applyActionOnChangedVars(RELEASE_SAVEPOINT); + popChangedVarsStack(); + break; + case XACT_EVENT_PARALLEL_ABORT: + applyActionOnChangedVars(ROLLBACK_TO_SAVEPOINT); + popChangedVarsStack(); + break; + default: + break; + } + } +} + +/* + * Register callback function when module starts + */ +void _PG_init(void) +{ + RegisterXactCallback(pgvTransCallback, NULL); + RegisterSubXactCallback(pgvSubTransCallback, NULL); +} + +/* + * Unregister callback function when module unloads + */ +void _PG_fini(void) +{ + UnregisterXactCallback(pgvTransCallback, NULL); + UnregisterSubXactCallback(pgvSubTransCallback, NULL); +} diff --git a/pg_variables.control b/pg_variables.control index f44cf24..2776600 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.1' module_pathname = '$libdir/pg_variables' relocatable = true diff --git a/pg_variables.h b/pg_variables.h old mode 100644 new mode 100755 index abe69f8..fb67aa2 --- a/pg_variables.h +++ b/pg_variables.h @@ -19,6 +19,7 @@ #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 @@ -59,16 +60,30 @@ typedef struct ScalarVar int16 typlen; } ScalarVar; -typedef struct HashVariableEntry -{ - char name[NAMEDATALEN]; +/* List node that stores one of the variables states */ +typedef struct ValueHistoryEntry{ + dlist_node node; union { ScalarVar scalar; RecordVar record; } value; +} ValueHistoryEntry; + +typedef dlist_head ValueHistory; +/* Variable by itself */ +typedef struct HashVariableEntry +{ + char name[NAMEDATALEN]; + /* Entry point to list with states of value */ + ValueHistory data; Oid typid; + /* + * The flag determines the further behavior of the variable. + * Can be specified only when creating a variable. + */ + bool is_transactional; } HashVariableEntry; typedef struct HashRecordKey @@ -87,6 +102,22 @@ typedef struct HashRecordEntry HeapTuple tuple; } HashRecordEntry; +/* Element of list with variables, changed within transaction */ +typedef struct ChangedVarsNode +{ + dlist_node node; + HashPackageEntry *package; + HashVariableEntry *variable; +} ChangedVarsNode; + +/* Element of stack with 'changedVars' list heads*/ +typedef struct ChangedVarsStackNode +{ + dlist_node node; + dlist_head *changedVarsList; + MemoryContext ctx; +} ChangedVarsStackNode; + extern void init_attributes(HashVariableEntry* variable, TupleDesc tupdesc, MemoryContext topctx); extern void check_attributes(HashVariableEntry *variable, TupleDesc tupdesc); @@ -100,4 +131,15 @@ extern bool delete_record(HashVariableEntry* variable, Datum value, bool is_null); extern void clean_records(HashVariableEntry *variable); +extern void insert_savepoint(HashVariableEntry *variable, + MemoryContext packageContext); + +/* Internal macros to manage with dlist structure */ +#define get_actual_value_scalar(variable) \ + (&((dlist_head_element(ValueHistoryEntry, node, &variable->data))->value.scalar)) +#define get_actual_value_record(variable) \ + (&((dlist_head_element(ValueHistoryEntry, node, &variable->data))->value.record)) +#define get_history_entry(node_ptr) \ + dlist_container(ValueHistoryEntry, node, node_ptr) + #endif /* __PG_VARIABLES_H__ */ diff --git a/pg_variables_record.c b/pg_variables_record.c index 0c91b6d..bd9fff1 100644 --- a/pg_variables_record.c +++ b/pg_variables_record.c @@ -78,11 +78,11 @@ init_attributes(HashVariableEntry *variable, TupleDesc tupdesc, sprintf(hash_name, "Records hash for variable \"%s\"", variable->name); - record = &(variable->value.record); + record = get_actual_value_record(variable); #if PG_VERSION_NUM >= 110000 record->hctx = AllocSetContextCreateExtended(topctx, - hash_name, 0, + hash_name, ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); @@ -139,11 +139,13 @@ void check_attributes(HashVariableEntry *variable, TupleDesc tupdesc) { int i; + RecordVar *record; Assert(variable->typid == RECORDOID); + record = get_actual_value_record(variable); /* 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\" " @@ -152,7 +154,7 @@ check_attributes(HashVariableEntry *variable, TupleDesc tupdesc) /* Second, check columns type. */ for (i = 0; i < tupdesc->natts; i++) { - Form_pg_attribute attr1 = GetTupleDescAttr(variable->value.record.tupdesc, i), + Form_pg_attribute attr1 = GetTupleDescAttr(record->tupdesc, i), attr2 = GetTupleDescAttr(tupdesc, i); if ((attr1->atttypid != attr2->atttypid) @@ -171,9 +173,11 @@ check_attributes(HashVariableEntry *variable, TupleDesc tupdesc) void check_record_key(HashVariableEntry *variable, Oid typid) { + RecordVar *record; Assert(variable->typid == RECORDOID); + record = get_actual_value_record(variable); - if (GetTupleDescAttr(variable->value.record.tupdesc, 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\" " @@ -199,7 +203,7 @@ insert_record(HashVariableEntry *variable, HeapTupleHeader tupleHeader) Assert(variable->typid == RECORDOID); - record = &(variable->value.record); + record = get_actual_value_record(variable); oldcxt = MemoryContextSwitchTo(record->hctx); @@ -259,7 +263,7 @@ update_record(HashVariableEntry* variable, HeapTupleHeader tupleHeader) Assert(variable->typid == RECORDOID); - record = &(variable->value.record); + record = get_actual_value_record(variable); oldcxt = MemoryContextSwitchTo(record->hctx); @@ -309,7 +313,7 @@ delete_record(HashVariableEntry *variable, Datum value, bool is_null) Assert(variable->typid == RECORDOID); - record = &(variable->value.record); + record = get_actual_value_record(variable); /* Delete a record */ k.value = value; @@ -331,11 +335,50 @@ delete_record(HashVariableEntry *variable, Datum value, bool is_null) void clean_records(HashVariableEntry *variable) { + RecordVar *record; Assert(variable->typid == RECORDOID); - hash_destroy(variable->value.record.rhash); - FreeTupleDesc(variable->value.record.tupdesc); - + record = get_actual_value_record(variable); /* All records will be freed */ - MemoryContextDelete(variable->value.record.hctx); + MemoryContextDelete(record->hctx); +} + +/* + * Create a new history point of record variable and copy all tulpes from + * previous state + */ +void +insert_savepoint(HashVariableEntry *variable, MemoryContext packageContext) +{ + RecordVar *record_prev, + *record_new; + HashRecordEntry *item_prev, + *item_new; + ValueHistoryEntry *history_entry_new; + HASH_SEQ_STATUS *rstat; + bool found; + MemoryContext oldcxt; + + Assert(variable->typid == RECORDOID); + + /* Create new history entry */ + record_prev = get_actual_value_record(variable); + oldcxt = MemoryContextSwitchTo(packageContext); + history_entry_new = palloc0(sizeof(ValueHistoryEntry)); + record_new = &(history_entry_new->value.record); + dlist_push_head(&variable->data, &history_entry_new->node); + init_attributes(variable, record_prev->tupdesc, packageContext); + + /* Copy previous history entry into the new one*/ + rstat = (HASH_SEQ_STATUS *) palloc0(sizeof(HASH_SEQ_STATUS)); + hash_seq_init(rstat, record_prev->rhash); + while((item_prev = (HashRecordEntry *) hash_seq_search(rstat)) !=NULL) + { + HashRecordKey k; + k = item_prev->key; + item_new = (HashRecordEntry *) hash_search(record_new->rhash, &k, + HASH_ENTER, &found); + item_new->tuple = heap_copytuple(item_prev->tuple); + } + MemoryContextSwitchTo(oldcxt); } diff --git a/sql/pg_variables.sql b/sql/pg_variables.sql index 12155e0..36778ad 100644 --- a/sql/pg_variables.sql +++ b/sql/pg_variables.sql @@ -174,6 +174,7 @@ SELECT pgv_select('vars2', 'j1'); -- 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'); diff --git a/sql/pg_variables_trans.sql b/sql/pg_variables_trans.sql new file mode 100644 index 0000000..7a7caeb --- /dev/null +++ b/sql/pg_variables_trans.sql @@ -0,0 +1,395 @@ +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; + + +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;