pax_global_header00006660000000000000000000000064135545550650014527gustar00rootroot0000000000000052 comment=34f7aecfe7e7c0e25cdb9e6892a0b178cea33d33 pg_dirtyread-2.2/000077500000000000000000000000001355455506500140475ustar00rootroot00000000000000pg_dirtyread-2.2/.gitignore000066400000000000000000000000671355455506500160420ustar00rootroot00000000000000regression.diffs regression.out results/ *.bc *.o *.so pg_dirtyread-2.2/.travis.yml000066400000000000000000000024501355455506500161610ustar00rootroot00000000000000# run the testsuite on travis-ci.com --- # versions to run on env: - PG_SUPPORTED_VERSIONS=9.2 - PG_SUPPORTED_VERSIONS=9.3 - PG_SUPPORTED_VERSIONS=9.4 - PG_SUPPORTED_VERSIONS=9.5 - PG_SUPPORTED_VERSIONS=9.6 - PG_SUPPORTED_VERSIONS=10 - PG_SUPPORTED_VERSIONS=11 - PG_SUPPORTED_VERSIONS=12 COMPONENT=12 language: C # we stay on xenial for now because bionic doesn't have PG9.2 dist: xenial sudo: required before_install: # apt.postgresql.org is already configured, we just need to add devel - | if [ "$COMPONENT" ]; then sudo sed -i -e "s/main/main $COMPONENT/" /etc/apt/sources.list.d/pgdg*.list fi - sudo apt-get -qq update install: - export DEBIAN_FRONTEND=noninteractive # suppress warnings about deprecated PostgreSQL versions - sudo apt-get purge -y postgresql-client-common - sudo apt-get install -y debhelper fakeroot postgresql-$PG_SUPPORTED_VERSIONS postgresql-contrib-$PG_SUPPORTED_VERSIONS postgresql-server-dev-$PG_SUPPORTED_VERSIONS postgresql-server-dev-all - pg_lsclusters - dpkg -l postgresql\* | cat script: - pg_buildext updatecontrol - dpkg-buildpackage -us -uc -rfakeroot -b - for deb in ../*.deb; do echo "$deb:"; dpkg-deb --info $deb; dpkg-deb --contents $deb; done - sudo dpkg -i ../*.deb - pg_buildext -i '--locale=C.UTF-8' installcheck pg_dirtyread-2.2/.vimrc000066400000000000000000000000161355455506500151650ustar00rootroot00000000000000set ts=4 sw=4 pg_dirtyread-2.2/LICENSE000066400000000000000000000031671355455506500150630ustar00rootroot00000000000000Copyright (c) 1996-2017, PostgreSQL Global Development Group Copyright (c) 2012, OmniTI Computer Consulting, Inc. Portions Copyright (c) 1994, The Regents of the University of California All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name OmniTI Computer Consulting, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. pg_dirtyread-2.2/Makefile000066400000000000000000000005371355455506500155140ustar00rootroot00000000000000MODULE_big = pg_dirtyread OBJS = pg_dirtyread.o dirtyread_tupconvert.o EXTENSION = pg_dirtyread DATA = pg_dirtyread--1.0.sql \ pg_dirtyread--1.0--2.sql pg_dirtyread--2.sql REGRESS = extension dirtyread oid PG_CONFIG = pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) include $(PGXS) pg_dirtyread.o dirtyread_tupconvert.o: dirtyread_tupconvert.h pg_dirtyread-2.2/README.md000066400000000000000000000140611355455506500153300ustar00rootroot00000000000000pg_dirtyread ============ The pg_dirtyread extension provides the ability to read dead but unvacuumed rows from a relation. Supports PostgreSQL 9.2 and later. [![Build Status](https://travis-ci.org/df7cb/pg_dirtyread.svg?branch=master)](https://travis-ci.org/df7cb/pg_dirtyread) Building -------- To build pg_dirtyread, just do this: make make install If you encounter an error such as: make: pg_config: Command not found Be sure that you have `pg_config` installed and in your path. If you used a package management system such as RPM to install PostgreSQL, be sure that the `-devel` package is also installed. If necessary tell the build process where to find it: make PG_CONFIG=/path/to/pg_config make install PG_CONFIG=/path/to/pg_config Loading and Using ------- Once pg_dirtyread is built and installed, you can add it to a database. Loading pg_dirtyread is as simple as connecting to a database as a super user and running: ```sql CREATE EXTENSION pg_dirtyread; SELECT * FROM pg_dirtyread('tablename') AS t(col1 type1, col2 type2, ...); ``` The `pg_dirtyread()` function returns RECORD, therefore it is necessary to attach a table alias clause that describes the table schema. Columns are matched by name, so it is possible to omit some columns in the alias, or rearrange columns. Example: ```sql CREATE EXTENSION pg_dirtyread; -- Create table and disable autovacuum CREATE TABLE foo (bar bigint, baz text); ALTER TABLE foo SET ( autovacuum_enabled = false, toast.autovacuum_enabled = false ); INSERT INTO foo VALUES (1, 'Test'), (2, 'New Test'); DELETE FROM foo WHERE bar = 1; SELECT * FROM pg_dirtyread('foo') as t(bar bigint, baz text); bar │ baz ─────┼────────── 1 │ Test 2 │ New Test ``` Dropped Columns --------------- The content of dropped columns can be retrieved as long as the table has not been rewritten (e.g. via `VACUUM FULL` or `CLUSTER`). Use `dropped_N` to access the Nth column, counting from 1. PostgreSQL deletes the type information of the original column, so only a few sanity checks can be done if the correct type was specified in the table alias; checked are type length, type alignment, type modifier, and pass-by-value. ```sql CREATE TABLE ab(a text, b text); INSERT INTO ab VALUES ('Hello', 'World'); ALTER TABLE ab DROP COLUMN b; DELETE FROM ab; SELECT * FROM pg_dirtyread('ab') ab(a text, dropped_2 text); a │ dropped_2 ───────┼─────────── Hello │ World ``` System Columns -------------- System columns such as `xmax` and `ctid` can be retrieved by including them in the table alias attached to the `pg_dirtyread()` call. A special column `dead` of type boolean is available to report dead rows (as by `HeapTupleIsSurelyDead`). The `dead` column is not usable during recovery, i.e. most notably not on standby servers. The `oid` column is only available in PostgreSQL version 11 and earlier. ```sql SELECT * FROM pg_dirtyread('foo') AS t(tableoid oid, ctid tid, xmin xid, xmax xid, cmin cid, cmax cid, dead boolean, bar bigint, baz text); tableoid │ ctid │ xmin │ xmax │ cmin │ cmax │ dead │ bar │ baz ──────────┼───────┼──────┼──────┼──────┼──────┼──────┼─────┼─────────────────── 41823 │ (0,1) │ 1484 │ 1485 │ 0 │ 0 │ t │ 1 │ Delete 41823 │ (0,2) │ 1484 │ 0 │ 0 │ 0 │ f │ 2 │ Insert 41823 │ (0,3) │ 1484 │ 1486 │ 0 │ 0 │ t │ 3 │ Update 41823 │ (0,4) │ 1484 │ 1488 │ 0 │ 0 │ f │ 4 │ Not deleted 41823 │ (0,5) │ 1484 │ 1489 │ 1 │ 1 │ f │ 5 │ Not updated 41823 │ (0,6) │ 1486 │ 0 │ 0 │ 0 │ f │ 3 │ Updated 41823 │ (0,7) │ 1489 │ 0 │ 1 │ 1 │ t │ 5 │ Not quite updated 41823 │ (0,8) │ 1490 │ 0 │ 2 │ 2 │ t │ 6 │ Not inserted ``` Authors ------- pg_dirtyread 1.0 was written by Phil Sorber in 2012. Christoph Berg added the ability to retrieve system columns in version 1.1, released 2017, and took over further maintenance. License ------- Copyright (c) 1996-2019, PostgreSQL Global Development Group Copyright (c) 2012, OmniTI Computer Consulting, Inc. Portions Copyright (c) 1994, The Regents of the University of California All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name OmniTI Computer Consulting, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. pg_dirtyread-2.2/contrib/000077500000000000000000000000001355455506500155075ustar00rootroot00000000000000pg_dirtyread-2.2/contrib/infomask.sql000066400000000000000000000061561355455506500200470ustar00rootroot00000000000000CREATE FUNCTION t_infomask(i IN integer, HASNULL OUT boolean, HASVARWIDTH OUT boolean, HASEXTERNAL OUT boolean, HASOID_OLD OUT boolean, XMAX_KEYSHR_LOCK OUT boolean, COMBOCID OUT boolean, XMAX_EXCL_LOCK OUT boolean, XMAX_LOCK_ONLY OUT boolean, XMIN_COMMITTED OUT boolean, XMIN_INVALID OUT boolean, XMAX_COMMITTED OUT boolean, XMAX_INVALID OUT boolean, XMAX_IS_MULTI OUT boolean, UPDATED OUT boolean, MOVED_OFF OUT boolean, MOVED_IN OUT boolean) LANGUAGE SQL AS $$SELECT /* HASNULL */ i & x'0001'::int <> 0, /* has null attribute(s) */ /* HASVARWIDTH */ i & x'0002'::int <> 0, /* has variable-width attribute(s) */ /* HASEXTERNAL */ i & x'0004'::int <> 0, /* has external stored attribute(s) */ /* HASOID_OLD */ i & x'0008'::int <> 0, /* has an object-id field */ /* XMAX_KEYSHR_LOCK */ i & x'0010'::int <> 0, /* xmax is a key-shared locker */ /* COMBOCID */ i & x'0020'::int <> 0, /* t_cid is a combo cid */ /* XMAX_EXCL_LOCK */ i & x'0040'::int <> 0, /* xmax is exclusive locker */ /* XMAX_LOCK_ONLY */ i & x'0080'::int <> 0, /* xmax, if valid, is only a locker */ /* XMIN_COMMITTED */ i & x'0100'::int <> 0, /* t_xmin committed */ /* XMIN_INVALID */ i & x'0200'::int <> 0, /* t_xmin invalid/aborted */ /* XMAX_COMMITTED */ i & x'0400'::int <> 0, /* t_xmax committed */ /* XMAX_INVALID */ i & x'0800'::int <> 0, /* t_xmax invalid/aborted */ /* XMAX_IS_MULTI */ i & x'1000'::int <> 0, /* t_xmax is a MultiXactId */ /* UPDATED */ i & x'2000'::int <> 0, /* this is UPDATEd version of row */ /* MOVED_OFF */ i & x'4000'::int <> 0, /* moved to another place by pre-9.0 */ /* MOVED_IN */ i & x'8000'::int <> 0 /* moved from another place by pre-9.0 */ $$; CREATE FUNCTION t_infomask2(i2 IN integer, NATTS OUT integer, KEYS_UPDATED OUT boolean, HOT_UPDATED OUT boolean, ONLY_TUPLE OUT boolean) LANGUAGE SQL AS $$SELECT /* NATTS_MASK */ i2 & x'07FF'::int, /* 11 bits for number of attributes */ /* bits 0x1800 are available */ /* KEYS_UPDATED */ i2 & x'2000'::int <> 0, /* tuple was updated and key cols modified, or tuple deleted */ /* HOT_UPDATED */ i2 & x'4000'::int <> 0, /* tuple was HOT-updated */ /* ONLY_TUPLE */ i2 & x'8000'::int <> 0 /* this is heap-only tuple */ $$; CREATE FUNCTION t_infomask(i IN integer, i2 IN integer, HASNULL OUT boolean, HASVARWIDTH OUT boolean, HASEXTERNAL OUT boolean, HASOID_OLD OUT boolean, XMAX_KEYSHR_LOCK OUT boolean, COMBOCID OUT boolean, XMAX_EXCL_LOCK OUT boolean, XMAX_LOCK_ONLY OUT boolean, XMIN_COMMITTED OUT boolean, XMIN_INVALID OUT boolean, XMAX_COMMITTED OUT boolean, XMAX_INVALID OUT boolean, XMAX_IS_MULTI OUT boolean, UPDATED OUT boolean, MOVED_OFF OUT boolean, MOVED_IN OUT boolean, NATTS OUT integer, KEYS_UPDATED OUT boolean, HOT_UPDATED OUT boolean, ONLY_TUPLE OUT boolean) LANGUAGE SQL AS $$SELECT * FROM t_infomask(i), t_infomask2(i2)$$; pg_dirtyread-2.2/contrib/pg_xact.sql000066400000000000000000000022611355455506500176560ustar00rootroot00000000000000/* src/include/access/clog.h #define TRANSACTION_STATUS_IN_PROGRESS 0x00 #define TRANSACTION_STATUS_COMMITTED 0x01 #define TRANSACTION_STATUS_ABORTED 0x02 #define TRANSACTION_STATUS_SUB_COMMITTED 0x03 */ /* SLRU_PAGES_PER_SEGMENT*BLCKSZ*CLOG_XACTS_PER_BYTE = 1M transactions per file */ CREATE OR REPLACE FUNCTION pg_xact(start bigint, stop bigint, file text DEFAULT 'pg_xact/0000') RETURNS TABLE(xid bigint, status text) LANGUAGE SQL AS $$WITH xact(xact) AS (SELECT pg_read_binary_file(file)) SELECT i, CASE 2*get_bit(xact, 2*i::int+1) + get_bit(xact, 2*i::int) WHEN 0 THEN 'in progress' WHEN 1 THEN 'committed' WHEN 2 THEN 'aborted' WHEN 3 THEN 'subtransaction commited' END FROM xact, generate_series(start, stop) g(i) $$; CREATE OR REPLACE FUNCTION pg_xact(xid bigint) RETURNS text LANGUAGE SQL AS $$WITH xact(xact, off) AS (SELECT pg_read_binary_file('pg_xact/' || repeat('0', 4-length(to_hex(xid >> 20))) || to_hex(xid >> 20)), 2 * (xid % (1<<20))::int) SELECT CASE 2 * get_bit(xact, off + 1)::int + get_bit(xact, off)::int WHEN 0 THEN 'in progress' WHEN 1 THEN 'committed' WHEN 2 THEN 'aborted' WHEN 3 THEN 'subtransaction commited' END FROM xact$$; pg_dirtyread-2.2/contrib/read_table.sql000066400000000000000000000035741355455506500203230ustar00rootroot00000000000000-- read all good tuples from a table, skipping over all tuples that trigger an error create or replace function read_table(relname regclass) returns setof record as $$ declare pages int; page int := 0; item int; r record; sql_state text; error text; begin select pg_relation_size(relname) / current_setting('block_size')::int into pages; <> while page < pages loop item := 1; <> while true loop begin execute format('SELECT * FROM %I WHERE ctid=''(%s,%s)'' ', relname, page, item) into r; if r is null then exit itemloop; end if; return next r; exception when others then get stacked diagnostics sql_state := RETURNED_SQLSTATE; get stacked diagnostics error := MESSAGE_TEXT; raise notice 'Skipping ctid (%,%): %: %', page, item, sql_state, error; end; item := item + 1; end loop; page := page + 1; end loop; end; $$ language plpgsql; -- return ctids of all tuples in a table that trigger an error create or replace function bad_tuples(relname regclass) returns table (ctid tid, sqlstate text, sqlerrm text) as $$ declare pages int; page int := 0; item int; r record; begin SELECT pg_relation_size(relname) / current_setting('block_size')::int INTO pages; <> while page < pages loop item := 1; <> while true loop begin execute format('SELECT * FROM %I WHERE ctid=''(%s,%s)'' ', relname, page, item) into r; if r is null then exit itemloop; end if; exception when others then ctid := format('(%s,%s)', page, item); bad_tuples.sqlstate := sqlstate; bad_tuples.sqlerrm := sqlerrm; return next; end; item := item + 1; end loop; page := page + 1; end loop; end; $$ language plpgsql; pg_dirtyread-2.2/debian/000077500000000000000000000000001355455506500152715ustar00rootroot00000000000000pg_dirtyread-2.2/debian/.gitignore000066400000000000000000000000541355455506500172600ustar00rootroot00000000000000*debhelper* files postgresql-*/ *.substvars pg_dirtyread-2.2/debian/changelog000066400000000000000000000037301355455506500171460ustar00rootroot00000000000000pg-dirtyread (2.2-1) unstable; urgency=medium * New upstream version, upload for PostgreSQL 12. + regress: Try reading from an index. + Contrib: Add function to read all good tuples from a table, skipping over all tuples that trigger an error, add function to return ctids of all tuples in a table that trigger an error. + README: Fix instructions on setting PG_CONFIG. -- Christoph Berg Fri, 25 Oct 2019 13:18:47 +0200 pg-dirtyread (2.1-1) unstable; urgency=medium * Support PostgreSQL 12. -- Christoph Berg Fri, 14 Jun 2019 15:43:03 +0200 pg-dirtyread (2.0-3) unstable; urgency=medium * Update PostgreSQL Maintainers address. -- Christoph Berg Thu, 07 Feb 2019 11:26:25 +0100 pg-dirtyread (2.0-2) unstable; urgency=medium * Upload for PostgreSQL 11. * Update watch file to ignore debian/ tags. -- Christoph Berg Fri, 12 Oct 2018 12:54:36 +0200 pg-dirtyread (2.0-1) unstable; urgency=medium * Change pg_dirtyread to take regclass as argument. * Add watch file, change source format to 3.0 (quilt). -- Christoph Berg Mon, 23 Jul 2018 22:44:04 +0200 pg-dirtyread (1.3) unstable; urgency=medium * Upload for PostgreSQL 10. * Use TupleDescAttr to access tuple descriptor attributes. -- Christoph Berg Sat, 23 Sep 2017 22:59:08 +0200 pg-dirtyread (1.2) unstable; urgency=medium * Refuse to return the "dead" pseudo column during recovery, because GetOldestXmin() asserts !RecoveryInProgress(). Spotted by Andreas Seltenreich, thanks! -- Christoph Berg Sun, 06 Aug 2017 16:57:41 +0200 pg-dirtyread (1.1) unstable; urgency=medium * Initial release. * Changes from 1.0: + Fix some crashes. + Add ability to retrieve system columns such as xmax and ctid. + Add "dead" column to allow identification of removed rows -- Christoph Berg Sun, 23 Jul 2017 12:47:01 +0200 pg_dirtyread-2.2/debian/compat000066400000000000000000000000021355455506500164670ustar00rootroot000000000000009 pg_dirtyread-2.2/debian/control000066400000000000000000000012571355455506500167010ustar00rootroot00000000000000Source: pg-dirtyread Section: database Priority: optional Maintainer: Debian PostgreSQL Maintainers Uploaders: Christoph Berg Build-Depends: debhelper (>= 9), postgresql-server-dev-all (>= 153~) Standards-Version: 4.3.0 Vcs-Browser: https://github.com/ChristophBerg/pg_dirtyread Vcs-Git: https://github.com/ChristophBerg/pg_dirtyread.git Package: postgresql-12-dirtyread Architecture: any Depends: ${misc:Depends}, ${shlibs:Depends}, postgresql-12 Description: Read dead but unvacuumed tuples from a PostgreSQL relation The pg_dirtyread extension provides the ability to read dead but unvacuumed rows from a PostgreSQL relation. pg_dirtyread-2.2/debian/control.in000066400000000000000000000012751355455506500173060ustar00rootroot00000000000000Source: pg-dirtyread Section: database Priority: optional Maintainer: Debian PostgreSQL Maintainers Uploaders: Christoph Berg Build-Depends: debhelper (>= 9), postgresql-server-dev-all (>= 153~) Standards-Version: 4.3.0 Vcs-Browser: https://github.com/ChristophBerg/pg_dirtyread Vcs-Git: https://github.com/ChristophBerg/pg_dirtyread.git Package: postgresql-PGVERSION-dirtyread Architecture: any Depends: ${misc:Depends}, ${shlibs:Depends}, postgresql-PGVERSION Description: Read dead but unvacuumed tuples from a PostgreSQL relation The pg_dirtyread extension provides the ability to read dead but unvacuumed rows from a PostgreSQL relation. pg_dirtyread-2.2/debian/copyright000066400000000000000000000035461355455506500172340ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: pg_dirtyread Source: https://github.com/ChristophBerg/pg_dirtyread Files: * Copyright: Copyright (c) 1996-2017, PostgreSQL Global Development Group Copyright (c) 2012, OmniTI Computer Consulting, Inc. Portions Copyright (c) 1994, The Regents of the University of California License: BSD-like All rights reserved. . Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: . * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name OmniTI Computer Consulting, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. . THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. pg_dirtyread-2.2/debian/pgversions000066400000000000000000000000431355455506500174100ustar00rootroot00000000000000# needs HeapTupleIsSurelyDead 9.2+ pg_dirtyread-2.2/debian/rules000077500000000000000000000006531355455506500163550ustar00rootroot00000000000000#!/usr/bin/make -f include /usr/share/postgresql-common/pgxs_debian_control.mk override_dh_auto_build: +pg_buildext build build-%v override_dh_auto_test: # nothing to do here, see debian/tests/* instead override_dh_auto_install: +pg_buildext install build-%v postgresql-%v-dirtyread override_dh_installdocs: dh_installdocs --all README.* override_dh_auto_clean: dh_auto_clean +pg_buildext clean build-%v %: dh $@ pg_dirtyread-2.2/debian/source/000077500000000000000000000000001355455506500165715ustar00rootroot00000000000000pg_dirtyread-2.2/debian/source/format000066400000000000000000000000141355455506500177770ustar00rootroot000000000000003.0 (quilt) pg_dirtyread-2.2/debian/tests/000077500000000000000000000000001355455506500164335ustar00rootroot00000000000000pg_dirtyread-2.2/debian/tests/control000066400000000000000000000001251355455506500200340ustar00rootroot00000000000000Depends: @, postgresql-server-dev-all Tests: installcheck Restrictions: allow-stderr pg_dirtyread-2.2/debian/tests/installcheck000077500000000000000000000000431355455506500210220ustar00rootroot00000000000000#!/bin/sh pg_buildext installcheck pg_dirtyread-2.2/debian/watch000066400000000000000000000001251355455506500163200ustar00rootroot00000000000000version=4 https://github.com/ChristophBerg/pg_dirtyread/releases .*/([0-9.]*).tar.gz pg_dirtyread-2.2/dirtyread_tupconvert.c000066400000000000000000000270741355455506500205050ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * tupconvert.c * Tuple conversion support. * * These functions provide conversion between rowtypes that are logically * equivalent but might have columns in a different order or different sets * of dropped columns. There is some overlap of functionality with the * executor's "junkfilter" routines, but these functions work on bare * HeapTuples rather than TupleTableSlots. * * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * src/backend/access/common/tupconvert.c * *------------------------------------------------------------------------- */ #include "postgres.h" #if PG_VERSION_NUM >= 90300 #include "access/htup_details.h" #endif #include "access/tupconvert.h" #include "access/sysattr.h" #include "access/xlog.h" /* RecoveryInProgress */ #include "catalog/pg_type.h" /* *OID */ #include "utils/builtins.h" #if PG_VERSION_NUM >= 120000 #include "access/heapam.h" #else #include "utils/tqual.h" /* HeapTupleIsSurelyDead */ #endif #include "dirtyread_tupconvert.h" #if PG_VERSION_NUM < 100000 /* from src/include/access/tupdesc.h, introduced in 2cd708452 */ #define TupleDescAttr(tupdesc, i) ((tupdesc)->attrs[(i)]) #endif /* * The conversion setup routines have the following common API: * * The setup routine checks whether the given source and destination tuple * descriptors are logically compatible. If not, it throws an error. * If so, it returns NULL if they are physically compatible (ie, no conversion * is needed), else a TupleConversionMap that can be used by do_convert_tuple * to perform the conversion. * * The TupleConversionMap, if needed, is palloc'd in the caller's memory * context. Also, the given tuple descriptors are referenced by the map, * so they must survive as long as the map is needed. * * The caller must supply a suitable primary error message to be used if * a compatibility error is thrown. Recommended coding practice is to use * gettext_noop() on this string, so that it is translatable but won't * actually be translated unless the error gets thrown. * * * Implementation notes: * * The key component of a TupleConversionMap is an attrMap[] array with * one entry per output column. This entry contains the 1-based index of * the corresponding input column, or zero to force a NULL value (for * a dropped output column). The TupleConversionMap also contains workspace * arrays. */ /* * Set up for tuple conversion, matching input and output columns by name. * (Dropped columns are ignored in both input and output.) This is intended * for use when the rowtypes are related by inheritance, so we expect an exact * match of both type and typmod. The error messages will be a bit unhelpful * unless both rowtypes are named composite types. */ TupleConversionMap * dirtyread_convert_tuples_by_name(TupleDesc indesc, TupleDesc outdesc, const char *msg) { TupleConversionMap *map; AttrNumber *attrMap; int n = outdesc->natts; int i; bool same; /* Verify compatibility and prepare attribute-number map */ attrMap = dirtyread_convert_tuples_by_name_map(indesc, outdesc, msg); /* * Check to see if the map is one-to-one, in which case we need not do a * tuple conversion. We must also insist that both tupdescs either * specify or don't specify an OID column, else we need a conversion to * add/remove space for that. (For some callers, presence or absence of * an OID column perhaps would not really matter, but let's be safe.) */ if (indesc->natts == outdesc->natts #if PG_VERSION_NUM < 120000 && indesc->tdhasoid == outdesc->tdhasoid #endif ) { same = true; for (i = 0; i < n; i++) { Form_pg_attribute inatt; Form_pg_attribute outatt; if (attrMap[i] == (i + 1)) continue; /* * If it's a dropped column and the corresponding input column is * also dropped, we needn't convert. However, attlen and attalign * must agree. */ inatt = TupleDescAttr(indesc, i); outatt = TupleDescAttr(outdesc, i); if (attrMap[i] == 0 && inatt->attisdropped && inatt->attlen == outatt->attlen && inatt->attalign == outatt->attalign) continue; same = false; break; } } else same = false; if (same) { /* Runtime conversion is not needed */ elog(DEBUG1, "tuple conversion is not needed"); pfree(attrMap); return NULL; } /* Prepare the map structure */ map = (TupleConversionMap *) palloc(sizeof(TupleConversionMap)); map->indesc = indesc; map->outdesc = outdesc; map->attrMap = attrMap; /* preallocate workspace for Datum arrays */ map->outvalues = (Datum *) palloc(n * sizeof(Datum)); map->outisnull = (bool *) palloc(n * sizeof(bool)); n = indesc->natts + 1; /* +1 for NULL */ map->invalues = (Datum *) palloc(n * sizeof(Datum)); map->inisnull = (bool *) palloc(n * sizeof(bool)); map->invalues[0] = (Datum) 0; /* set up the NULL entry */ map->inisnull[0] = true; return map; } static const struct system_columns_t { char *attname; Oid atttypid; int32 atttypmod; int attnum; } system_columns[] = { { "ctid", TIDOID, -1, SelfItemPointerAttributeNumber }, #if PG_VERSION_NUM < 120000 { "oid", OIDOID, -1, ObjectIdAttributeNumber }, #endif { "xmin", XIDOID, -1, MinTransactionIdAttributeNumber }, { "cmin", CIDOID, -1, MinCommandIdAttributeNumber }, { "xmax", XIDOID, -1, MaxTransactionIdAttributeNumber }, { "cmax", CIDOID, -1, MaxCommandIdAttributeNumber }, { "tableoid", OIDOID, -1, TableOidAttributeNumber }, { "dead", BOOLOID, -1, DeadFakeAttributeNumber }, /* fake column to return HeapTupleIsSurelyDead */ { 0 }, }; /* * Return a palloc'd bare attribute map for tuple conversion, matching input * and output columns by name. (Dropped columns are ignored in both input and * output.) This is normally a subroutine for convert_tuples_by_name, but can * be used standalone. * * This version from dirtyread_tupconvert.c adds the ability to retrieve dropped * columns by requesting "dropped_N" as output column, where N is the attnum. */ AttrNumber * dirtyread_convert_tuples_by_name_map(TupleDesc indesc, TupleDesc outdesc, const char *msg) { AttrNumber *attrMap; int n; int i; n = outdesc->natts; attrMap = (AttrNumber *) palloc0(n * sizeof(AttrNumber)); for (i = 0; i < n; i++) { Form_pg_attribute outatt = TupleDescAttr(outdesc, i); char *attname; Oid atttypid; int32 atttypmod; int j; if (outatt->attisdropped) continue; /* attrMap[i] is already 0 */ attname = NameStr(outatt->attname); atttypid = outatt->atttypid; atttypmod = outatt->atttypmod; for (j = 0; j < indesc->natts; j++) { Form_pg_attribute inatt = TupleDescAttr(indesc, j); if (inatt->attisdropped) continue; if (strcmp(attname, NameStr(inatt->attname)) == 0) { /* Found it, check type */ if (atttypid != inatt->atttypid || atttypmod != inatt->atttypmod) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg_internal("%s", _(msg)), errdetail("Attribute \"%s\" has type %s in corresponding attribute of type %s.", attname, format_type_with_typemod(inatt->atttypid, inatt->atttypmod), format_type_be(indesc->tdtypeid)))); attrMap[i] = (AttrNumber) (j + 1); break; } } /* Check dropped columns */ if (attrMap[i] == 0) if (strncmp(attname, "dropped_", sizeof("dropped_") - 1) == 0) { Form_pg_attribute inatt; j = atoi(attname + sizeof("dropped_") - 1); if (j < 1 || j > indesc->natts) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg_internal("%s", _(msg)), errdetail("Attribute \"%s\" index is out of range 1 .. %d.", attname, indesc->natts))); inatt = TupleDescAttr(indesc, j - 1); if (! inatt->attisdropped) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg_internal("%s", _(msg)), errdetail("Attribute %d is not a dropped column.", j))); if (outatt->attlen != inatt->attlen) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg_internal("%s", _(msg)), errdetail("Type length of dropped column \"%s\" was %d.", attname, inatt->attlen))); if (outatt->attbyval != inatt->attbyval) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg_internal("%s", _(msg)), errdetail("\"By value\" of dropped column \"%s\" does not match.", attname))); if (outatt->attalign != inatt->attalign) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg_internal("%s", _(msg)), errdetail("Alignment of dropped column \"%s\" was %c.", attname, inatt->attalign))); inatt->atttypid = atttypid; if (atttypmod != inatt->atttypmod) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg_internal("%s", _(msg)), errdetail("Type modifier of dropped column \"%s\" was %s.", attname, format_type_with_typemod(inatt->atttypid, inatt->atttypmod)))); attrMap[i] = (AttrNumber) j; } /* Check system columns */ if (attrMap[i] == 0) for (j = 0; system_columns[j].attname; j++) if (strcmp(attname, system_columns[j].attname) == 0) { /* Found it, check type */ if (atttypid != system_columns[j].atttypid || atttypmod != system_columns[j].atttypmod) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg_internal("%s", _(msg)), errdetail("Attribute \"%s\" has type %s in corresponding attribute of type %s.", attname, format_type_be(system_columns[j].atttypid), format_type_be(indesc->tdtypeid)))); /* GetOldestXmin() is not available during recovery */ if (system_columns[j].attnum == DeadFakeAttributeNumber && RecoveryInProgress()) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Cannot use \"dead\" column during recovery"))); attrMap[i] = system_columns[j].attnum; break; } if (attrMap[i] == 0) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg_internal("%s", _(msg)), errdetail("Attribute \"%s\" does not exist in type %s.", attname, format_type_be(indesc->tdtypeid)))); } return attrMap; } /* * Perform conversion of a tuple according to the map. */ HeapTuple dirtyread_do_convert_tuple(HeapTuple tuple, TupleConversionMap *map, TransactionId oldest_xmin) { AttrNumber *attrMap = map->attrMap; Datum *invalues = map->invalues; bool *inisnull = map->inisnull; Datum *outvalues = map->outvalues; bool *outisnull = map->outisnull; int outnatts = map->outdesc->natts; int i; /* * Extract all the values of the old tuple, offsetting the arrays so that * invalues[0] is left NULL and invalues[1] is the first source attribute; * this exactly matches the numbering convention in attrMap. */ heap_deform_tuple(tuple, map->indesc, invalues + 1, inisnull + 1); /* * Transpose into proper fields of the new tuple. */ for (i = 0; i < outnatts; i++) { int j = attrMap[i]; if (j == DeadFakeAttributeNumber) { outvalues[i] = HeapTupleIsSurelyDead(tuple #if PG_VERSION_NUM < 90400 ->t_data #endif , oldest_xmin); outisnull[i] = false; } else if (j < 0) outvalues[i] = heap_getsysattr(tuple, j, map->indesc, &outisnull[i]); else { outvalues[i] = invalues[j]; outisnull[i] = inisnull[j]; } } /* * Now form the new tuple. */ return heap_form_tuple(map->outdesc, outvalues, outisnull); } pg_dirtyread-2.2/dirtyread_tupconvert.h000066400000000000000000000017121355455506500205010ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * tupconvert.h * Tuple conversion support. * * * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * src/include/access/tupconvert.h * *------------------------------------------------------------------------- */ #ifndef DIRTYREAD_TUPCONVERT_H #define DIRTYREAD_TUPCONVERT_H #include "access/tupconvert.h" extern TupleConversionMap *dirtyread_convert_tuples_by_name(TupleDesc indesc, TupleDesc outdesc, const char *msg); extern AttrNumber *dirtyread_convert_tuples_by_name_map(TupleDesc indesc, TupleDesc outdesc, const char *msg); extern HeapTuple dirtyread_do_convert_tuple(HeapTuple tuple, TupleConversionMap *map, TransactionId oldest_xmin); #define DeadFakeAttributeNumber FirstLowInvalidHeapAttributeNumber #endif /* TUPCONVERT_H */ pg_dirtyread-2.2/expected/000077500000000000000000000000001355455506500156505ustar00rootroot00000000000000pg_dirtyread-2.2/expected/dirtyread.out000066400000000000000000000223251355455506500203740ustar00rootroot00000000000000-- Create table and disable autovacuum CREATE TABLE foo (bar bigint, baz text); ALTER TABLE foo SET ( autovacuum_enabled = false, toast.autovacuum_enabled = false ); -- single row INSERT INTO foo VALUES (1, 'Hello world'); SELECT * FROM pg_dirtyread('foo') as t(bar bigint, baz text); bar | baz -----+------------- 1 | Hello world (1 row) DELETE FROM foo; SELECT * FROM pg_dirtyread('foo') as t(bar bigint, baz text); bar | baz -----+------------- 1 | Hello world (1 row) VACUUM foo; -- multiple rows INSERT INTO foo VALUES (1, 'Delete'), (2, 'Insert'), (3, 'Update'), (4, 'Not deleted'), (5, 'Not updated'); DELETE FROM foo WHERE bar = 1; UPDATE foo SET baz = 'Updated' WHERE bar = 3; BEGIN; DELETE FROM foo WHERE bar = 4; UPDATE foo SET baz = 'Not quite updated' where bar = 5; INSERT INTO foo VALUES (6, 'Not inserted'); ROLLBACK; SELECT * FROM foo; bar | baz -----+------------- 2 | Insert 4 | Not deleted 5 | Not updated 3 | Updated (4 rows) SELECT * FROM pg_dirtyread('foo') as t(bar bigint, baz text); bar | baz -----+------------------- 1 | Delete 2 | Insert 3 | Update 4 | Not deleted 5 | Not updated 3 | Updated 5 | Not quite updated 6 | Not inserted (8 rows) -- system columns (don't show tableoid and xmin, but make sure they are numbers) SELECT CASE WHEN tableoid >= 0 THEN 0 END AS tableoid, ctid, CASE WHEN xmin::text::int >= 0 THEN 0 END AS xmin, CASE WHEN xmax::text <> '0' THEN xmax::text::int - xmin::text::int END AS xmax, cmin, cmax, dead, bar, baz FROM pg_dirtyread('foo') AS t(tableoid oid, ctid tid, xmin xid, xmax xid, cmin cid, cmax cid, dead boolean, bar bigint, baz text); tableoid | ctid | xmin | xmax | cmin | cmax | dead | bar | baz ----------+-------+------+------+------+------+------+-----+------------------- 0 | (0,1) | 0 | 1 | 0 | 0 | t | 1 | Delete 0 | (0,2) | 0 | | 0 | 0 | f | 2 | Insert 0 | (0,3) | 0 | 2 | 0 | 0 | t | 3 | Update 0 | (0,4) | 0 | 3 | 0 | 0 | f | 4 | Not deleted 0 | (0,5) | 0 | 3 | 1 | 1 | f | 5 | Not updated 0 | (0,6) | 0 | | 0 | 0 | f | 3 | Updated 0 | (0,7) | 0 | | 1 | 1 | t | 5 | Not quite updated 0 | (0,8) | 0 | | 2 | 2 | t | 6 | Not inserted (8 rows) -- error cases SELECT pg_dirtyread('foo'); ERROR: function returning record called in context that cannot accept type record SELECT * FROM pg_dirtyread(0) as t(bar bigint, baz text); ERROR: invalid relation oid "0" SELECT * FROM pg_dirtyread('foo') as t(bar int, baz text); ERROR: Error converting tuple descriptors! DETAIL: Attribute "bar" has type bigint in corresponding attribute of type foo. SELECT * FROM pg_dirtyread('foo') as t(moo bigint); ERROR: Error converting tuple descriptors! DETAIL: Attribute "moo" does not exist in type foo. SELECT * FROM pg_dirtyread('foo') as t(tableoid bigint); ERROR: Error converting tuple descriptors! DETAIL: Attribute "tableoid" has type oid in corresponding attribute of type foo. SELECT * FROM pg_dirtyread('foo') as t(ctid bigint); ERROR: Error converting tuple descriptors! DETAIL: Attribute "ctid" has type tid in corresponding attribute of type foo. SELECT * FROM pg_dirtyread('foo') as t(xmin bigint); ERROR: Error converting tuple descriptors! DETAIL: Attribute "xmin" has type xid in corresponding attribute of type foo. SELECT * FROM pg_dirtyread('foo') as t(xmax bigint); ERROR: Error converting tuple descriptors! DETAIL: Attribute "xmax" has type xid in corresponding attribute of type foo. SELECT * FROM pg_dirtyread('foo') as t(cmin bigint); ERROR: Error converting tuple descriptors! DETAIL: Attribute "cmin" has type cid in corresponding attribute of type foo. SELECT * FROM pg_dirtyread('foo') as t(cmax bigint); ERROR: Error converting tuple descriptors! DETAIL: Attribute "cmax" has type cid in corresponding attribute of type foo. SELECT * FROM pg_dirtyread('foo') as t(dead bigint); ERROR: Error converting tuple descriptors! DETAIL: Attribute "dead" has type boolean in corresponding attribute of type foo. SET ROLE luser; SELECT * FROM pg_dirtyread('foo') as t(bar bigint, baz text); ERROR: must be superuser to use pg_dirtyread RESET ROLE; CREATE INDEX ON foo(bar); SELECT * FROM pg_dirtyread('foo_bar_idx') as t(bar bigint); ERROR: "foo_bar_idx" is an index -- reading from dropped columns CREATE TABLE bar ( id int, a int, b bigint, c text, d varchar(10), e boolean, f bigint[], z int ); ALTER TABLE bar SET ( autovacuum_enabled = false, toast.autovacuum_enabled = false ); INSERT INTO bar VALUES (1, 2, 3, '4', '5', true, '{7}', 8); ALTER TABLE bar DROP COLUMN a, DROP COLUMN b, DROP COLUMN c, DROP COLUMN d, DROP COLUMN e, DROP COLUMN f; INSERT INTO bar VALUES (2, 8); SELECT * FROM bar; id | z ----+--- 1 | 8 2 | 8 (2 rows) SELECT * FROM pg_dirtyread('bar') bar(id int, dropped_2 int, dropped_3 bigint, dropped_4 text, dropped_5 varchar(10), dropped_6 boolean, dropped_7 bigint[], z int); id | dropped_2 | dropped_3 | dropped_4 | dropped_5 | dropped_6 | dropped_7 | z ----+-----------+-----------+-----------+-----------+-----------+-----------+--- 1 | 2 | 3 | 4 | 5 | t | {7} | 8 2 | | | | | | | 8 (2 rows) -- errors SELECT * FROM pg_dirtyread('bar') bar(id int, dropped_0 int, dropped_3 bigint, dropped_4 text, dropped_5 varchar(10), dropped_6 boolean, dropped_7 bigint[], z int); ERROR: Error converting tuple descriptors! DETAIL: Attribute "dropped_0" index is out of range 1 .. 8. SELECT * FROM pg_dirtyread('bar') bar(id int, dropped_9 int, dropped_3 bigint, dropped_4 text, dropped_5 varchar(10), dropped_6 boolean, dropped_7 bigint[], z int); ERROR: Error converting tuple descriptors! DETAIL: Attribute "dropped_9" index is out of range 1 .. 8. SELECT * FROM pg_dirtyread('bar') bar(id int, dropped_2 bigint, dropped_3 bigint, dropped_4 text, dropped_5 varchar(10), dropped_6 boolean, dropped_7 bigint[], z int); ERROR: Error converting tuple descriptors! DETAIL: Type length of dropped column "dropped_2" was 4. SELECT * FROM pg_dirtyread('bar') bar(id int, dropped_2 int, dropped_3 int, dropped_4 text, dropped_5 varchar(10), dropped_6 boolean, dropped_7 bigint[], z int); ERROR: Error converting tuple descriptors! DETAIL: Type length of dropped column "dropped_3" was 8. -- mismatch not catched: SELECT * FROM pg_dirtyread('bar') bar(id int, dropped_2 int, dropped_3 timestamptz, dropped_4 text, dropped_5 varchar(10), dropped_6 boolean, dropped_7 bigint[], z int); id | dropped_2 | dropped_3 | dropped_4 | dropped_5 | dropped_6 | dropped_7 | z ----+-----------+-------------------------------------+-----------+-----------+-----------+-----------+--- 1 | 2 | Fri Dec 31 16:00:00.000003 1999 PST | 4 | 5 | t | {7} | 8 2 | | | | | | | 8 (2 rows) SELECT * FROM pg_dirtyread('bar') bar(id int, dropped_2 int, dropped_3 bigint, dropped_4 int, dropped_5 varchar(10), dropped_6 boolean, dropped_7 bigint[], z int); ERROR: Error converting tuple descriptors! DETAIL: Type length of dropped column "dropped_4" was -1. SELECT * FROM pg_dirtyread('bar') bar(id int, dropped_2 int, dropped_3 bigint, dropped_4 text, dropped_5 varchar(11), dropped_6 boolean, dropped_7 bigint[], z int); ERROR: Error converting tuple descriptors! DETAIL: Type modifier of dropped column "dropped_5" was character varying(10). SELECT * FROM pg_dirtyread('bar') bar(id int, dropped_2 int, dropped_3 bigint, dropped_4 text, dropped_5 varchar(10), dropped_6 text, dropped_7 bigint[], z int); ERROR: Error converting tuple descriptors! DETAIL: Type length of dropped column "dropped_6" was 1. SELECT * FROM pg_dirtyread('bar') bar(id int, dropped_2 int, dropped_3 bigint, dropped_4 text, dropped_5 varchar(10), dropped_6 boolean, dropped_7 int[], z int); ERROR: Error converting tuple descriptors! DETAIL: Alignment of dropped column "dropped_7" was d. -- mismatch not catched: SELECT * FROM pg_dirtyread('bar') bar(id int, dropped_2 int, dropped_3 bigint, dropped_4 text, dropped_5 varchar(10), dropped_6 boolean, dropped_7 timestamptz[], z int); id | dropped_2 | dropped_3 | dropped_4 | dropped_5 | dropped_6 | dropped_7 | z ----+-----------+-----------+-----------+-----------+-----------+-----------+--- 1 | 2 | 3 | 4 | 5 | t | {7} | 8 2 | | | | | | | 8 (2 rows) -- clean table VACUUM FULL bar; SELECT * FROM pg_dirtyread('bar') bar(id int, dropped_2 int, dropped_3 bigint, dropped_4 text, dropped_5 varchar(10), dropped_6 boolean, dropped_7 bigint[], z int); id | dropped_2 | dropped_3 | dropped_4 | dropped_5 | dropped_6 | dropped_7 | z ----+-----------+-----------+-----------+-----------+-----------+-----------+--- 1 | | | | | | | 8 2 | | | | | | | 8 (2 rows) pg_dirtyread-2.2/expected/extension.out000066400000000000000000000003141355455506500204130ustar00rootroot00000000000000CREATE EXTENSION pg_dirtyread; -- create a non-superuser role, ignoring any output/errors, it might already exist DO $$ BEGIN CREATE ROLE luser; EXCEPTION WHEN duplicate_object THEN NULL; END; $$; pg_dirtyread-2.2/expected/oid.out000066400000000000000000000007731355455506500171630ustar00rootroot00000000000000-- test oid columns (removed in PostgreSQL 12) SELECT setting::int >= 120000 AS is_pg_12 FROM pg_settings WHERE name = 'server_version_num'; is_pg_12 ---------- t (1 row) SELECT * FROM pg_dirtyread('foo') AS t(oid oid, bar bigint, baz text); ERROR: Error converting tuple descriptors! DETAIL: Attribute "oid" does not exist in type foo. -- error cases SELECT * FROM pg_dirtyread('foo') as t(oid bigint); ERROR: Error converting tuple descriptors! DETAIL: Attribute "oid" does not exist in type foo. pg_dirtyread-2.2/expected/oid_1.out000066400000000000000000000012711355455506500173750ustar00rootroot00000000000000-- test oid columns (removed in PostgreSQL 12) SELECT setting::int >= 120000 AS is_pg_12 FROM pg_settings WHERE name = 'server_version_num'; is_pg_12 ---------- f (1 row) SELECT * FROM pg_dirtyread('foo') AS t(oid oid, bar bigint, baz text); oid | bar | baz -----+-----+------------------- 0 | 1 | Delete 0 | 2 | Insert 0 | 3 | Update 0 | 4 | Not deleted 0 | 5 | Not updated 0 | 3 | Updated 0 | 5 | Not quite updated 0 | 6 | Not inserted (8 rows) -- error cases SELECT * FROM pg_dirtyread('foo') as t(oid bigint); ERROR: Error converting tuple descriptors! DETAIL: Attribute "oid" has type oid in corresponding attribute of type foo. pg_dirtyread-2.2/pg_dirtyread--1.0--2.sql000066400000000000000000000002021355455506500200240ustar00rootroot00000000000000DROP FUNCTION pg_dirtyread(oid); CREATE FUNCTION pg_dirtyread(regclass) RETURNS SETOF record AS 'MODULE_PATHNAME' LANGUAGE C; pg_dirtyread-2.2/pg_dirtyread--1.0.sql000066400000000000000000000001301355455506500176100ustar00rootroot00000000000000CREATE FUNCTION pg_dirtyread(oid) RETURNS setof record AS 'MODULE_PATHNAME' LANGUAGE C; pg_dirtyread-2.2/pg_dirtyread--2.sql000066400000000000000000000001401355455506500174540ustar00rootroot00000000000000CREATE FUNCTION pg_dirtyread(regclass) RETURNS SETOF record AS 'MODULE_PATHNAME' LANGUAGE C; pg_dirtyread-2.2/pg_dirtyread.c000066400000000000000000000122551355455506500166750ustar00rootroot00000000000000/* * Copyright (c) 1996-2019, PostgreSQL Global Development Group * Copyright (c) 2012, OmniTI Computer Consulting, Inc. * Portions Copyright (c) 1994, The Regents of the University of California * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * Neither the name OmniTI Computer Consulting, Inc. nor the names * of its contributors may be used to endorse or promote products * derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include "postgres.h" #include "funcapi.h" #if PG_VERSION_NUM >= 120000 #include "access/heapam.h" #include "access/table.h" #include "utils/snapmgr.h" #else #include "utils/tqual.h" #endif #include "utils/rel.h" #include "catalog/pg_type.h" #include "access/tupconvert.h" #if PG_VERSION_NUM >= 90300 #include "access/htup_details.h" #endif #include "access/xlog.h" /* RecoveryInProgress */ #include "miscadmin.h" /* superuser */ #include "storage/procarray.h" /* GetOldestXmin */ #include "dirtyread_tupconvert.h" typedef struct { Relation rel; TupleDesc reltupdesc; TupleConversionMap *map; #if PG_VERSION_NUM >= 120000 TableScanDesc scan; #else HeapScanDesc scan; #endif TransactionId oldest_xmin; } pg_dirtyread_ctx; PG_MODULE_MAGIC; PG_FUNCTION_INFO_V1(pg_dirtyread); Datum pg_dirtyread(PG_FUNCTION_ARGS); Datum pg_dirtyread(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; pg_dirtyread_ctx *usr_ctx; HeapTuple tuplein; if (SRF_IS_FIRSTCALL()) { MemoryContext oldcontext; Oid relid; TupleDesc tupdesc; if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to use pg_dirtyread"))); relid = PG_GETARG_OID(0); if (!OidIsValid(relid)) elog(ERROR, "invalid relation oid \"%d\"", relid); funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); usr_ctx = (pg_dirtyread_ctx *) palloc(sizeof(pg_dirtyread_ctx)); usr_ctx->rel = heap_open(relid, AccessShareLock); usr_ctx->reltupdesc = RelationGetDescr(usr_ctx->rel); if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("function returning record called in context " "that cannot accept type record"))); funcctx->tuple_desc = BlessTupleDesc(tupdesc); usr_ctx->map = dirtyread_convert_tuples_by_name(usr_ctx->reltupdesc, funcctx->tuple_desc, "Error converting tuple descriptors!"); usr_ctx->scan = heap_beginscan(usr_ctx->rel, SnapshotAny, 0, NULL #if PG_VERSION_NUM >= 120000 , NULL, 0 #endif ); /* only call GetOldestXmin while not in recovery */ if (!RecoveryInProgress()) usr_ctx->oldest_xmin = GetOldestXmin( #if PG_VERSION_NUM >= 90400 usr_ctx->rel #else false /* allDbs */ #endif , 0); funcctx->user_fctx = (void *) usr_ctx; MemoryContextSwitchTo(oldcontext); } funcctx = SRF_PERCALL_SETUP(); usr_ctx = (pg_dirtyread_ctx *) funcctx->user_fctx; if ((tuplein = heap_getnext(usr_ctx->scan, ForwardScanDirection)) != NULL) { if (usr_ctx->map != NULL) { tuplein = dirtyread_do_convert_tuple(tuplein, usr_ctx->map, usr_ctx->oldest_xmin); SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuplein)); } else SRF_RETURN_NEXT(funcctx, heap_copy_tuple_as_datum(tuplein, usr_ctx->reltupdesc)); } else { heap_endscan(usr_ctx->scan); heap_close(usr_ctx->rel, AccessShareLock); SRF_RETURN_DONE(funcctx); } } /* vim:et */ pg_dirtyread-2.2/pg_dirtyread.control000066400000000000000000000002261355455506500201260ustar00rootroot00000000000000# pg_dirtyread default_version = '2' comment = 'Read dead but unvacuumed rows from table' module_pathname = '$libdir/pg_dirtyread' relocatable = true pg_dirtyread-2.2/sql/000077500000000000000000000000001355455506500146465ustar00rootroot00000000000000pg_dirtyread-2.2/sql/dirtyread.sql000066400000000000000000000110341355455506500173550ustar00rootroot00000000000000-- Create table and disable autovacuum CREATE TABLE foo (bar bigint, baz text); ALTER TABLE foo SET ( autovacuum_enabled = false, toast.autovacuum_enabled = false ); -- single row INSERT INTO foo VALUES (1, 'Hello world'); SELECT * FROM pg_dirtyread('foo') as t(bar bigint, baz text); DELETE FROM foo; SELECT * FROM pg_dirtyread('foo') as t(bar bigint, baz text); VACUUM foo; -- multiple rows INSERT INTO foo VALUES (1, 'Delete'), (2, 'Insert'), (3, 'Update'), (4, 'Not deleted'), (5, 'Not updated'); DELETE FROM foo WHERE bar = 1; UPDATE foo SET baz = 'Updated' WHERE bar = 3; BEGIN; DELETE FROM foo WHERE bar = 4; UPDATE foo SET baz = 'Not quite updated' where bar = 5; INSERT INTO foo VALUES (6, 'Not inserted'); ROLLBACK; SELECT * FROM foo; SELECT * FROM pg_dirtyread('foo') as t(bar bigint, baz text); -- system columns (don't show tableoid and xmin, but make sure they are numbers) SELECT CASE WHEN tableoid >= 0 THEN 0 END AS tableoid, ctid, CASE WHEN xmin::text::int >= 0 THEN 0 END AS xmin, CASE WHEN xmax::text <> '0' THEN xmax::text::int - xmin::text::int END AS xmax, cmin, cmax, dead, bar, baz FROM pg_dirtyread('foo') AS t(tableoid oid, ctid tid, xmin xid, xmax xid, cmin cid, cmax cid, dead boolean, bar bigint, baz text); -- error cases SELECT pg_dirtyread('foo'); SELECT * FROM pg_dirtyread(0) as t(bar bigint, baz text); SELECT * FROM pg_dirtyread('foo') as t(bar int, baz text); SELECT * FROM pg_dirtyread('foo') as t(moo bigint); SELECT * FROM pg_dirtyread('foo') as t(tableoid bigint); SELECT * FROM pg_dirtyread('foo') as t(ctid bigint); SELECT * FROM pg_dirtyread('foo') as t(xmin bigint); SELECT * FROM pg_dirtyread('foo') as t(xmax bigint); SELECT * FROM pg_dirtyread('foo') as t(cmin bigint); SELECT * FROM pg_dirtyread('foo') as t(cmax bigint); SELECT * FROM pg_dirtyread('foo') as t(dead bigint); SET ROLE luser; SELECT * FROM pg_dirtyread('foo') as t(bar bigint, baz text); RESET ROLE; CREATE INDEX ON foo(bar); SELECT * FROM pg_dirtyread('foo_bar_idx') as t(bar bigint); -- reading from dropped columns CREATE TABLE bar ( id int, a int, b bigint, c text, d varchar(10), e boolean, f bigint[], z int ); ALTER TABLE bar SET ( autovacuum_enabled = false, toast.autovacuum_enabled = false ); INSERT INTO bar VALUES (1, 2, 3, '4', '5', true, '{7}', 8); ALTER TABLE bar DROP COLUMN a, DROP COLUMN b, DROP COLUMN c, DROP COLUMN d, DROP COLUMN e, DROP COLUMN f; INSERT INTO bar VALUES (2, 8); SELECT * FROM bar; SELECT * FROM pg_dirtyread('bar') bar(id int, dropped_2 int, dropped_3 bigint, dropped_4 text, dropped_5 varchar(10), dropped_6 boolean, dropped_7 bigint[], z int); -- errors SELECT * FROM pg_dirtyread('bar') bar(id int, dropped_0 int, dropped_3 bigint, dropped_4 text, dropped_5 varchar(10), dropped_6 boolean, dropped_7 bigint[], z int); SELECT * FROM pg_dirtyread('bar') bar(id int, dropped_9 int, dropped_3 bigint, dropped_4 text, dropped_5 varchar(10), dropped_6 boolean, dropped_7 bigint[], z int); SELECT * FROM pg_dirtyread('bar') bar(id int, dropped_2 bigint, dropped_3 bigint, dropped_4 text, dropped_5 varchar(10), dropped_6 boolean, dropped_7 bigint[], z int); SELECT * FROM pg_dirtyread('bar') bar(id int, dropped_2 int, dropped_3 int, dropped_4 text, dropped_5 varchar(10), dropped_6 boolean, dropped_7 bigint[], z int); -- mismatch not catched: SELECT * FROM pg_dirtyread('bar') bar(id int, dropped_2 int, dropped_3 timestamptz, dropped_4 text, dropped_5 varchar(10), dropped_6 boolean, dropped_7 bigint[], z int); SELECT * FROM pg_dirtyread('bar') bar(id int, dropped_2 int, dropped_3 bigint, dropped_4 int, dropped_5 varchar(10), dropped_6 boolean, dropped_7 bigint[], z int); SELECT * FROM pg_dirtyread('bar') bar(id int, dropped_2 int, dropped_3 bigint, dropped_4 text, dropped_5 varchar(11), dropped_6 boolean, dropped_7 bigint[], z int); SELECT * FROM pg_dirtyread('bar') bar(id int, dropped_2 int, dropped_3 bigint, dropped_4 text, dropped_5 varchar(10), dropped_6 text, dropped_7 bigint[], z int); SELECT * FROM pg_dirtyread('bar') bar(id int, dropped_2 int, dropped_3 bigint, dropped_4 text, dropped_5 varchar(10), dropped_6 boolean, dropped_7 int[], z int); -- mismatch not catched: SELECT * FROM pg_dirtyread('bar') bar(id int, dropped_2 int, dropped_3 bigint, dropped_4 text, dropped_5 varchar(10), dropped_6 boolean, dropped_7 timestamptz[], z int); -- clean table VACUUM FULL bar; SELECT * FROM pg_dirtyread('bar') bar(id int, dropped_2 int, dropped_3 bigint, dropped_4 text, dropped_5 varchar(10), dropped_6 boolean, dropped_7 bigint[], z int); pg_dirtyread-2.2/sql/extension.sql000066400000000000000000000003151355455506500174020ustar00rootroot00000000000000CREATE EXTENSION pg_dirtyread; -- create a non-superuser role, ignoring any output/errors, it might already exist DO $$ BEGIN CREATE ROLE luser; EXCEPTION WHEN duplicate_object THEN NULL; END; $$; pg_dirtyread-2.2/sql/oid.sql000066400000000000000000000004321355455506500161410ustar00rootroot00000000000000-- test oid columns (removed in PostgreSQL 12) SELECT setting::int >= 120000 AS is_pg_12 FROM pg_settings WHERE name = 'server_version_num'; SELECT * FROM pg_dirtyread('foo') AS t(oid oid, bar bigint, baz text); -- error cases SELECT * FROM pg_dirtyread('foo') as t(oid bigint); pg_dirtyread-2.2/tupconvert.c.upstream000066400000000000000000000266471355455506500203020ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * tupconvert.c * Tuple conversion support. * * These functions provide conversion between rowtypes that are logically * equivalent but might have columns in a different order or different sets * of dropped columns. There is some overlap of functionality with the * executor's "junkfilter" routines, but these functions work on bare * HeapTuples rather than TupleTableSlots. * * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * src/backend/access/common/tupconvert.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include "access/htup_details.h" #include "access/tupconvert.h" #include "utils/builtins.h" /* * The conversion setup routines have the following common API: * * The setup routine checks whether the given source and destination tuple * descriptors are logically compatible. If not, it throws an error. * If so, it returns NULL if they are physically compatible (ie, no conversion * is needed), else a TupleConversionMap that can be used by do_convert_tuple * to perform the conversion. * * The TupleConversionMap, if needed, is palloc'd in the caller's memory * context. Also, the given tuple descriptors are referenced by the map, * so they must survive as long as the map is needed. * * The caller must supply a suitable primary error message to be used if * a compatibility error is thrown. Recommended coding practice is to use * gettext_noop() on this string, so that it is translatable but won't * actually be translated unless the error gets thrown. * * * Implementation notes: * * The key component of a TupleConversionMap is an attrMap[] array with * one entry per output column. This entry contains the 1-based index of * the corresponding input column, or zero to force a NULL value (for * a dropped output column). The TupleConversionMap also contains workspace * arrays. */ /* * Set up for tuple conversion, matching input and output columns by * position. (Dropped columns are ignored in both input and output.) * * Note: the errdetail messages speak of indesc as the "returned" rowtype, * outdesc as the "expected" rowtype. This is okay for current uses but * might need generalization in future. */ TupleConversionMap * convert_tuples_by_position(TupleDesc indesc, TupleDesc outdesc, const char *msg) { TupleConversionMap *map; AttrNumber *attrMap; int nincols; int noutcols; int n; int i; int j; bool same; /* Verify compatibility and prepare attribute-number map */ n = outdesc->natts; attrMap = (AttrNumber *) palloc0(n * sizeof(AttrNumber)); j = 0; /* j is next physical input attribute */ nincols = noutcols = 0; /* these count non-dropped attributes */ same = true; for (i = 0; i < n; i++) { Form_pg_attribute att = TupleDescAttr(outdesc, i); Oid atttypid; int32 atttypmod; if (att->attisdropped) continue; /* attrMap[i] is already 0 */ noutcols++; atttypid = att->atttypid; atttypmod = att->atttypmod; for (; j < indesc->natts; j++) { att = TupleDescAttr(indesc, j); if (att->attisdropped) continue; nincols++; /* Found matching column, check type */ if (atttypid != att->atttypid || (atttypmod != att->atttypmod && atttypmod >= 0)) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg_internal("%s", _(msg)), errdetail("Returned type %s does not match expected type %s in column %d.", format_type_with_typemod(att->atttypid, att->atttypmod), format_type_with_typemod(atttypid, atttypmod), noutcols))); attrMap[i] = (AttrNumber) (j + 1); j++; break; } if (attrMap[i] == 0) same = false; /* we'll complain below */ } /* Check for unused input columns */ for (; j < indesc->natts; j++) { if (TupleDescAttr(indesc, j)->attisdropped) continue; nincols++; same = false; /* we'll complain below */ } /* Report column count mismatch using the non-dropped-column counts */ if (!same) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg_internal("%s", _(msg)), errdetail("Number of returned columns (%d) does not match " "expected column count (%d).", nincols, noutcols))); /* * Check to see if the map is one-to-one, in which case we need not do a * tuple conversion. We must also insist that both tupdescs either * specify or don't specify an OID column, else we need a conversion to * add/remove space for that. (For some callers, presence or absence of * an OID column perhaps would not really matter, but let's be safe.) */ if (indesc->natts == outdesc->natts && indesc->tdhasoid == outdesc->tdhasoid) { for (i = 0; i < n; i++) { Form_pg_attribute inatt; Form_pg_attribute outatt; if (attrMap[i] == (i + 1)) continue; /* * If it's a dropped column and the corresponding input column is * also dropped, we needn't convert. However, attlen and attalign * must agree. */ inatt = TupleDescAttr(indesc, i); outatt = TupleDescAttr(outdesc, i); if (attrMap[i] == 0 && inatt->attisdropped && inatt->attlen == outatt->attlen && inatt->attalign == outatt->attalign) continue; same = false; break; } } else same = false; if (same) { /* Runtime conversion is not needed */ pfree(attrMap); return NULL; } /* Prepare the map structure */ map = (TupleConversionMap *) palloc(sizeof(TupleConversionMap)); map->indesc = indesc; map->outdesc = outdesc; map->attrMap = attrMap; /* preallocate workspace for Datum arrays */ map->outvalues = (Datum *) palloc(n * sizeof(Datum)); map->outisnull = (bool *) palloc(n * sizeof(bool)); n = indesc->natts + 1; /* +1 for NULL */ map->invalues = (Datum *) palloc(n * sizeof(Datum)); map->inisnull = (bool *) palloc(n * sizeof(bool)); map->invalues[0] = (Datum) 0; /* set up the NULL entry */ map->inisnull[0] = true; return map; } /* * Set up for tuple conversion, matching input and output columns by name. * (Dropped columns are ignored in both input and output.) This is intended * for use when the rowtypes are related by inheritance, so we expect an exact * match of both type and typmod. The error messages will be a bit unhelpful * unless both rowtypes are named composite types. */ TupleConversionMap * convert_tuples_by_name(TupleDesc indesc, TupleDesc outdesc, const char *msg) { TupleConversionMap *map; AttrNumber *attrMap; int n = outdesc->natts; int i; bool same; /* Verify compatibility and prepare attribute-number map */ attrMap = convert_tuples_by_name_map(indesc, outdesc, msg); /* * Check to see if the map is one-to-one, in which case we need not do a * tuple conversion. We must also insist that both tupdescs either * specify or don't specify an OID column, else we need a conversion to * add/remove space for that. (For some callers, presence or absence of * an OID column perhaps would not really matter, but let's be safe.) */ if (indesc->natts == outdesc->natts && indesc->tdhasoid == outdesc->tdhasoid) { same = true; for (i = 0; i < n; i++) { Form_pg_attribute inatt; Form_pg_attribute outatt; if (attrMap[i] == (i + 1)) continue; /* * If it's a dropped column and the corresponding input column is * also dropped, we needn't convert. However, attlen and attalign * must agree. */ inatt = TupleDescAttr(indesc, i); outatt = TupleDescAttr(outdesc, i); if (attrMap[i] == 0 && inatt->attisdropped && inatt->attlen == outatt->attlen && inatt->attalign == outatt->attalign) continue; same = false; break; } } else same = false; if (same) { /* Runtime conversion is not needed */ pfree(attrMap); return NULL; } /* Prepare the map structure */ map = (TupleConversionMap *) palloc(sizeof(TupleConversionMap)); map->indesc = indesc; map->outdesc = outdesc; map->attrMap = attrMap; /* preallocate workspace for Datum arrays */ map->outvalues = (Datum *) palloc(n * sizeof(Datum)); map->outisnull = (bool *) palloc(n * sizeof(bool)); n = indesc->natts + 1; /* +1 for NULL */ map->invalues = (Datum *) palloc(n * sizeof(Datum)); map->inisnull = (bool *) palloc(n * sizeof(bool)); map->invalues[0] = (Datum) 0; /* set up the NULL entry */ map->inisnull[0] = true; return map; } /* * Return a palloc'd bare attribute map for tuple conversion, matching input * and output columns by name. (Dropped columns are ignored in both input and * output.) This is normally a subroutine for convert_tuples_by_name, but can * be used standalone. */ AttrNumber * convert_tuples_by_name_map(TupleDesc indesc, TupleDesc outdesc, const char *msg) { AttrNumber *attrMap; int n; int i; n = outdesc->natts; attrMap = (AttrNumber *) palloc0(n * sizeof(AttrNumber)); for (i = 0; i < n; i++) { Form_pg_attribute outatt = TupleDescAttr(outdesc, i); char *attname; Oid atttypid; int32 atttypmod; int j; if (outatt->attisdropped) continue; /* attrMap[i] is already 0 */ attname = NameStr(outatt->attname); atttypid = outatt->atttypid; atttypmod = outatt->atttypmod; for (j = 0; j < indesc->natts; j++) { Form_pg_attribute inatt = TupleDescAttr(indesc, j); if (inatt->attisdropped) continue; if (strcmp(attname, NameStr(inatt->attname)) == 0) { /* Found it, check type */ if (atttypid != inatt->atttypid || atttypmod != inatt->atttypmod) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg_internal("%s", _(msg)), errdetail("Attribute \"%s\" of type %s does not match corresponding attribute of type %s.", attname, format_type_be(outdesc->tdtypeid), format_type_be(indesc->tdtypeid)))); attrMap[i] = (AttrNumber) (j + 1); break; } } if (attrMap[i] == 0) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg_internal("%s", _(msg)), errdetail("Attribute \"%s\" of type %s does not exist in type %s.", attname, format_type_be(outdesc->tdtypeid), format_type_be(indesc->tdtypeid)))); } return attrMap; } /* * Perform conversion of a tuple according to the map. */ HeapTuple do_convert_tuple(HeapTuple tuple, TupleConversionMap *map) { AttrNumber *attrMap = map->attrMap; Datum *invalues = map->invalues; bool *inisnull = map->inisnull; Datum *outvalues = map->outvalues; bool *outisnull = map->outisnull; int outnatts = map->outdesc->natts; int i; /* * Extract all the values of the old tuple, offsetting the arrays so that * invalues[0] is left NULL and invalues[1] is the first source attribute; * this exactly matches the numbering convention in attrMap. */ heap_deform_tuple(tuple, map->indesc, invalues + 1, inisnull + 1); /* * Transpose into proper fields of the new tuple. */ for (i = 0; i < outnatts; i++) { int j = attrMap[i]; outvalues[i] = invalues[j]; outisnull[i] = inisnull[j]; } /* * Now form the new tuple. */ return heap_form_tuple(map->outdesc, outvalues, outisnull); } /* * Free a TupleConversionMap structure. */ void free_conversion_map(TupleConversionMap *map) { /* indesc and outdesc are not ours to free */ pfree(map->attrMap); pfree(map->invalues); pfree(map->inisnull); pfree(map->outvalues); pfree(map->outisnull); pfree(map); } pg_dirtyread-2.2/tupconvert.h.upstream000066400000000000000000000026371355455506500203000ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * tupconvert.h * Tuple conversion support. * * * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * src/include/access/tupconvert.h * *------------------------------------------------------------------------- */ #ifndef TUPCONVERT_H #define TUPCONVERT_H #include "access/htup.h" #include "access/tupdesc.h" typedef struct TupleConversionMap { TupleDesc indesc; /* tupdesc for source rowtype */ TupleDesc outdesc; /* tupdesc for result rowtype */ AttrNumber *attrMap; /* indexes of input fields, or 0 for null */ Datum *invalues; /* workspace for deconstructing source */ bool *inisnull; Datum *outvalues; /* workspace for constructing result */ bool *outisnull; } TupleConversionMap; extern TupleConversionMap *convert_tuples_by_position(TupleDesc indesc, TupleDesc outdesc, const char *msg); extern TupleConversionMap *convert_tuples_by_name(TupleDesc indesc, TupleDesc outdesc, const char *msg); extern AttrNumber *convert_tuples_by_name_map(TupleDesc indesc, TupleDesc outdesc, const char *msg); extern HeapTuple do_convert_tuple(HeapTuple tuple, TupleConversionMap *map); extern void free_conversion_map(TupleConversionMap *map); #endif /* TUPCONVERT_H */