pax_global_header00006660000000000000000000000064147662127700014526gustar00rootroot0000000000000052 comment=e1820603600c6d737ed4de894a06330d0856ef36 pgtt-4.1/000077500000000000000000000000001476621277000123505ustar00rootroot00000000000000pgtt-4.1/.github/000077500000000000000000000000001476621277000137105ustar00rootroot00000000000000pgtt-4.1/.github/workflows/000077500000000000000000000000001476621277000157455ustar00rootroot00000000000000pgtt-4.1/.github/workflows/main.yml000066400000000000000000000013501476621277000174130ustar00rootroot00000000000000# This is a basic workflow to help you get started with Actions name: CI # Controls when the workflow will run on: # Triggers the workflow on push or pull request events but only for the master branch push: branches: [ master ] pull_request: branches: [ master ] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: test: strategy: matrix: pg: [16, 15, 14, 13, 12] name: 🐘 PostgreSQL ${{ matrix.pg }} runs-on: ubuntu-latest container: pgxn/pgxn-tools steps: - run: pg-start ${{ matrix.pg }} - uses: actions/checkout@v2 - run: pg-build-test pgtt-4.1/.gitignore000066400000000000000000000007321476621277000143420ustar00rootroot00000000000000# Prerequisites *.d # Object files *.o *.ko *.obj *.elf # Linker output *.ilk *.map *.exp *.bc # Precompiled Headers *.gch *.pch # Libraries *.lib *.a *.la *.lo # Shared objects (inc. Windows DLLs) *.dll *.so *.so.* *.dylib # Executables *.exe #*.out *.app *.i*86 *.x86_64 *.hex # Debug files *.dSYM/ *.su *.idb *.pdb # Kernel Module Compile Results *.mod* *.cmd .tmp_versions/ modules.order Module.symvers Mkfile.old dkms.conf # Test results results/ regression.* pgtt-4.1/AUTHORS000077500000000000000000000001121476621277000134150ustar00rootroot00000000000000PGTT extension is written by: * Gilles Darold pgtt-4.1/CONTRIBUTORS000077500000000000000000000001031476621277000142250ustar00rootroot00000000000000List of contributors ==================== Thanks to all of them! pgtt-4.1/COPYING000077500000000000000000000013351476621277000134100ustar00rootroot00000000000000Copyright (c) 2018-2025 Gilles Darold Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. pgtt-4.1/ChangeLog000066400000000000000000000243271476621277000141320ustar00rootroot00000000000000Version 4.1 - March 18 2025 This is a maintenance release to fix issues reported by users since latest release. - Use query instead of \d to list indexes in regression tests. - Fix wrong complain about temporary table created and active when a regular temporary table is created with (LIKE ... INCLUDING INDEXES). Thanks to Benjamin Wirth for the report. - Remove documentation for non-superuser configuration. Thanks to Julien Rouhaud for the patch. - Fix config in README. The concrete path to the shared library is not needed in postgresql.conf. Thanks to Simon Martin for the patch. - Install docs to a non-generic name, add a symlink to README.md that is referenced in DOCS. Thanks to Christoph Berg for the patch. - Add vaccum pg_class + pg_sleep(1) after each drop table in regression tests to wait that the temporary table is really removed. - Add missing PGDLLEXPORT tags on callback functions, those are needed on windows. Thanks to Julien Rouhaud for the patch. Version 4.0 - May 31 2024 This major release allow preloading pgtt in session_preload_libraries and adds support to Windows operating system. Here is the complete list of changes. - Add port to Windows operating system. Note that, for some unknown reasons it doesn't work at all with PostgreSQL 13. Thanks to Julien Rouhaud for the patch - Allow preloading pgtt in session_preload_libraries. The module is now lazily loaded when it might be needed (after parse analysis, before executor startup and before utility statements execution), and all the code is simply bypassed if the underlying extension has not been created. Thanks to Julien Rouhaud for the patch. - Fix first query execution after a LOAD command. If the extension is loaded with a plain LOAD command, the search_path will only be set during the next query execution. It means that the very first query executed after such a LOAD wouldn't see the global temporary tables. Thanks to Julien Rouhaud for the patch. - Remove the relocation test, the extension is not relocatable anymore. - Fix crash with RESET ALL. RESET ALL has a NULL "name" field, so we can't use strcmp in that case. Since the existing code is only interested in SET commands, just move up the check that the command is a VAR_SET_VALUE to ensure that we will have a GUC name. Thanks to Julien Rouhaud for the patch. - Fix various minor whitespace and indent issues. Thanks to Julien Rouhaud for the patch. - Add Julen Rouhaud in the authors and maintainers list. Version 3.2 - Apr 12 2024 This is a maintenance release to fix issues reported by users since latest release and compatibility with future PG 17. - Add information about privilege on the pgtt schema. - Make test on lock compatible with version <= 14 - The temporary table needs to be locked. Thanks to Leo X.M. Zeng for the patch. - Replace MyBackendId with MyProcNumber for PG > 16 - Check extension when loading pgtt. Thanks to Japin Li for the patch. - Release lock on global template temporary table. Thanks to Japin Li for the patch. Version 3.1 - Dec 26 2023 This is a maintenance release to fix issues reported by users since latest release. - Fix ERROR: attempt to redefine parameter "pgtt.enabled". Thanks to Japin Li for the report. - Forget acquiring lock for temporary table. Thanks to Japin Li for the patch. - Add temporisation after vacuum in 08_plplgsql test. - Update main.yml to remove PG versions prior 12. Version 3.0 - Sep 17 2023 This major release fix several issues related to PostgreSQL v16 port and remove support to PostgreSQL version prior 12. Here is the complete list of changes. - Remove support to PostgreSQL prior v12. - Fix compilation error: static declaration of get_extension_schema follows non-static declaration. Thanks to Devrim Gunduz for the report. - Remove grouping set in regexp. - Add -Wno-ignored-attributes to CPPFLAGS to avoid compilation warning on pg_vsnprintf call. - Replace GetOverrideSearchPath() call by GetSearchPathMatcher() with PG > 16 - Fix debug information print on Windows. Thanks to Lanlan for the patch. - Replace reg* regexp function with the pg_reg* function. Thanks to Lanlan for the report. - Fix port to PostgreSQL v16. Thanks to Julien Rouhaud for the patch. Version 2.10 - Feb 23 2023 This is a maintenance release to fix a lock issue in multi parallel process environment, many locks were generated. Here is the complete list of changes. - Delegate locks on per session temporary tables to PostgreSQL when the table will be used. - Release lock on parent table after temporary table creation based on this relation. Thanks to jayhsiang and liyaojinli for the report. - Use vacuum in test 08 to not look at pg_class too early before the temporary table is remove. - Fix setting default value of the parent table's persistence that can be misleading in future development. Thanks to songjinzhou for the patch. - Fix documentation. Thanks to Luca Ferrari for the patch. Version 2.9 - Aug 16 2022 This is a maintenance release per CVE-2022-2625 and new PostgreSQL minor versions updates. - Remove creation of the pgtt_schema if it not exists from extension files per CVE-2022-2625. Thanks to Dmitry Ukolov for the report. - Add regression test for regular table drop. Version 2.8 - Jun 02 2022 This is a maintenance release to add support to PostgreSQL 15 and fix an error when trying to drop a regular table. - Add support to PostgreSQL 15. - Fix impossibility to drop a regular table when the extension is loaded. Thanks to basildba for the report. Version 2.7 - Nov 23 2021 This is a maintenance release to fix an issue with parallelism and improve performances. - Prevent code to be executed in parallel processes. Thanks to Dmitry Ukolov for the report. - Improve performances by not looking for an existing GTT table if the table is a temporary table or part of the pg_catalog. - Update ChangeLog to acknowledge patch on PG14 support to Dmitry Ukolov. - Update copyright year. Version 2.6 - Sep 22 2021 This is a maintenance release to add support for upcomming PostgreSQL 14 and fixed some issues reported in the past three months. - Add support to PostgreSQL 14. Thanks to Devrim Gunduz for the report and Dmitry Ukolov for the patch. - Remove support to PG 9.5 which obviously was not working. Minimal PG version for this extension is 9.6. - Fix documentation menu. - Fix creation of GTT when there is a CHECK constraint with string constant. Version 2.5 - Jun 08 2021 This is a maintenance release to hotfix port on PostgreSQL 9.6. - Fix port to PostgreSQL 9.6. Thanks to Devrim Gunduz for the report. Version 2.4 - Jun 04 2021 This version allow use of the extension by non superuser and especially the creation and maintenance of GTT. It also fixes compatibility with PostgreSQL v14. Here is the full list of changes: - Fix FailedAssertion "flags & HASH_STRINGS" with PG14. Thanks to MigOps for the patch. - Check for minimum pg version in the C code instead of Makefile. Thanks to MigOps for the patch. - Fixed compiling for PostgreSQL 14. Thanks to Dmitry Ukolov for the patch. - Fix documentation about privilege to set on pgtt_schema for a non superuser role. - Allow creation and maintenance of Global Temporary Tables by non superuser. This require that the user can use schema pgtt_schema and can write to table pg_schema.pg_global_temp_tables. - The library can now be loaded by the user using: LOAD '$libdir/plugins/pgtt.so'; Thanks to Dmitry Ukolov for the feature request. - Fix two crashes when --enable-cassert is used. Thanks to hanson69 for the report. - Fix comment and index on PGTT table. Thanks to Dmitry Ukolov for the report. - Fix unexpected error "attempt to create referential integrity constraint on global temporary table" when creating a regular table and fix detection of FK and throw an error on create global temporary table statement. Thanks to Dmitry Ukolov for the report. - Fix impossibility to recreate GTT if it was dropped in another session. Thanks to Dmitry Ukolov for the report. - Remove useless extension's downgrade files. Thanks to MigOps for the patch. Version 2.3 - Apr 02 2021 This version fix the compatibility with PostgreSQL 10 and 11. When use on PostgreSQL 10 and 11 the following was raised ERROR: unrecognized node type: 375 Thanks to smallcookie086 for the report. Add upgrade/downgrade SQL files. Version 2.2 - Nov 08 2020 This release is a port of the extension for PostgreSQL v12 and v13. Works now on all PostgreSQL version from v9.5 to current. It also fixes automatic creation of the underlying temporary table after a rollback. Other fixes: - Fix regression test for all supported PG version. - Replace call to \d in regression tests, they do not report the same information following PG version. - Remove test about partitioning as it returns a failure for PG < 10. - Update regression tests to avoid failure related to temp table id. - Add regression test for error on rollback issue. Version 2.1 - May 11 2020 This is a maintenance release to complete the work on the extension and fix some issues. * Prevent use of foreign keys with GTT, not that PostgreSQL do not allow it but just to mimic the behavior of Oracle and other RDBMS like DB2, SQL Server and MySQL for example. * Raise an error on an attempt to partition a Global Temporary Table. This is not supported, again not because PostgreSQL do not allow partition on temporary table but because other RDBMS like Oracle, DB2 and MySQL do not support it. * Add support to comments, constraints and identity columns clauses when creating the GTT. Other fixes: - Add regression tests on partitioning and FK. - Exclude regression.* files from git scope. - Improve documentation and add information about constraints. - Add documentation about unsupported FK and partition on GTT. - Fix missing files for expected test results. - Fix exclusion of .out and results directory. - Update regression tests about changes on CREATE TABLE ... LIKE. - Fix some typo in documentation and markdown titles. Version 2.0 - April 19 2020 Initial release. pgtt-4.1/INSTALL000077500000000000000000000000671476621277000134070ustar00rootroot00000000000000The installation steps are covered in the README file. pgtt-4.1/META.json000077500000000000000000000020331476621277000137720ustar00rootroot00000000000000{ "name": "pgtt", "abstract": "Extension to add Global Temporary Tables feature to PostgreSQL.", "version": "4.0.0", "maintainer": "Gilles Darold ", "license": "postgresql", "release_status": "stable", "provides": { "pgtt": { "abstract": "Extension to manage Global Temporary Tables", "file": "sql/pgtt--4.0.0.sql", "docfile": "doc/pgtt.md", "version": "4.0.0" } }, "resources": { "bugtracker": { "web": "https://github.com/darold/pgtt/issues" }, "repository": { "url": "git://github.com/darold/pgtt.git", "web": "https://github.com/darold/pgtt.git", "type": "git" } }, "prereqs": { "runtime": { "requires": { "PostgreSQL": "12.0" } } }, "generated_by": "Gilles Darold", "meta-spec": { "version": "1.0.0", "url": "http://pgxn.org/meta/spec.txt" }, "tags": [ "global", "temporary", "table" ] } pgtt-4.1/Makefile000077500000000000000000000016001476621277000140100ustar00rootroot00000000000000EXTENSION = pgtt EXTVERSION = $(shell grep default_version $(EXTENSION).control | \ sed -e "s/default_version[[:space:]]*=[[:space:]]*'\([^']*\)'/\1/") PGFILEDESC = "pgtt - Global Temporary Tables for PostgreSQL" PG_CONFIG = pg_config PG_CPPFLAGS = -I$(libpq_srcdir) -Wno-uninitialized -Wno-ignored-attributes PG_LDFLAGS = -L$(libpq_builddir) -lpq PG_LIBDIR := $(shell $(PG_CONFIG) --libdir) SHLIB_LINK = $(libpq) DOCS = pgtt.md MODULES = pgtt DATA = $(wildcard updates/*--*.sql) $(wildcard sql/*.sql) TESTS = 00_init 01_oncommitdelete 02_oncommitpreserve \ 03_createontruncate 04_rename 05_useindex \ 06_createas 07_createlike 08_plplgsql \ 09_transaction 10_foreignkey 11_after_error \ 12_droptable REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS)) REGRESS_OPTS = --inputdir=test PGXS := $(shell $(PG_CONFIG) --pgxs) include $(PGXS) pgtt-4.1/README.md000077500000000000000000000447171476621277000136470ustar00rootroot00000000000000* [Description](#description) * [Installation](#installation) * [Configuration](#configuration) * [Use of the extension](#use-of-the-extension) * [How the extension really works](#how-the-extension-really-works) * [Performances](performances) * [Authors](#authors) ## PostgreSQL Global Temporary Tables ### [Description](#description) pgtt is a PostgreSQL extension to create, manage and use Oracle-style Global Temporary Tables and the others RDBMS. The objective of this extension it to provide the Global Temporary Table feature to PostgreSQL waiting for an in core implementation. The main interest of this extension is to mimic the Oracle behavior with GTT when you can not or don't want to rewrite the application code when migrating to PostgreSQL. In all other case best is to rewrite the code to use standard PostgreSQL temporary tables. This version of the GTT extension use a regular unlogged table as "template" table and an internal rerouting to a temporary table. See chapter "How the extension really works" for more details. A previous implementation of this extension using Row Security Level is still available [here](https://github.com/darold/pgtt-rsl). PostgreSQL native temporary tables are automatically dropped at the end of a session, or optionally at the end of the current transaction. Global Temporary Tables (GTT) are permanent, they are created as regular tables visible to all users but their content is relative to the current session or transaction. Even if the table is persistent a session or transaction can not see rows written by an other session. Usually this is not a problem, you have learn to deal with the temporary table behavior of PostgreSQL but the problem comes when you are migrating an Oracle database to PostgreSQL. You have to rewrite the SQL and PlPgSQL code to follow the application logic and use PostgreSQL temporary table, that mean recreating the temporary table everywhere it is used. The other advantage of this kind of object is when your application creates and drops a lot of temporary tables, the PostgreSQL catalogs becomes bloated and the performances start to fall. Usually Global Temporary Tables prevent catalog bloating, but with this implementation and even if we have a permanent table, all DML are rerouted to a regular temporary table created at first access. See below chapter "How the extension really works" for more information. DECLARE TEMPORARY TABLE statement is not supported by PostgreSQL and by this extension. However this statement defines a temporary table for the current connection / session, it creates tables that do not reside in the system catalogs and are not persistent. It cannot be shared with other sessions. This is the equivalent of PostgreSQL standard CREATE TEMPORARY TABLE so you might just have to replace the DECLARE keyword by CREATE. All Oracle's GTT behavior are respected with the different clauses minus what is not supported by PostgreSQL: #### ON COMMIT {DELETE | PRESERVE} ROWS Specifies the action taken on the global temporary table when a COMMIT operation is performed. - DELETE ROWS: all rows of the table will be deleted if no holdable cursor is open on the table. - PRESERVE ROWS: the rows of the table will be preserved after the COMMIT. #### LOGGED or NOT LOGGED [ ON ROLLBACK {DELETE | PRESERVE} ROWS ] Specifies whether operations for the table are logged. The default is `NOT LOGGED ON ROLLBACK DELETE ROWS`. * NOT LOGGED: Specifies that insert, update, or delete operations against the table are not to be logged, but that the creation or dropping of the table is to be logged. During a ROLLBACK or ROLLBACK TO SAVEPOINT operation: - If the table had been created within a transaction, the table is dropped - If the table had been dropped within a transaction, the table is recreated, but without any data * ON ROLLBACK: Specifies the action that is to be taken on the not logged created temporary table when a ROLLBACK or ROLLBACK TO SAVEPOINT operation is performed. The default is DELETE ROWS. - DELETE ROWS: if the table data has been changed, all the rows will be deleted. - PRESERVE ROWS: rows of the table will be preserved. * LOGGED: specifies that insert, update, or delete operations against the table as well as the creation or dropping of the table are to be logged. With PostgreSQL only `NOT LOGGED ON ROLLBACK DELETE ROWS` can be supported. Creation or dropping of the Global Temporary Table are logged, see below "How the extension really works" for the details. ### [Installation](#installation) To install the pgtt extension you need at least a PostgreSQL version 12. Untar the pgtt tarball anywhere you want then you'll need to compile it with pgxs. The `pg_config` tool must be in your path. Depending on your installation, you may need to install some devel package. Once `pg_config` is in your path, do make sudo make install Then it will be possible to use it using `session_preload_libraries = 'pgtt'` in postgresql.conf To create and manage GTT using a non-superuser role you will have to grant the CREATE privilege on the `pgtt_schema` schema to the user. For example: GRANT ALL ON SCHEMA pgtt_schema TO pgtt_user1; To run test execute the following command as superuser: make installcheck An additional standalone test is provided to test the use of the extension as non superuser. The test can be executed using: mkdir results createdb gtt_privilege LANG=C psql -d gtt_privilege -f test/privilege.sql > results/privilege.out 2>&1 diff results/privilege.out test/expected/privilege.out dropdb gtt_privilege dropuser pgtt_user1 ### [Configuration](#configuration) - *pgtt.enabled* The extension can be enable / disable using this GUC, default is enabled. To disable the extension use: SET pgtt.enabled TO off; You can disable or enable the extension at any moment in a session. ### [Use of the extension](#use-of-the-extension) In all database where you want to use Global Temporary Tables you will have to create the extension using: CREATE EXTENSION pgtt; You can load the extension by setting in postgresql.conf : session_preload_libraries = 'pgtt'; or by setting it at database level as follow: DO $$ BEGIN EXECUTE format('ALTER DATABASE %I SET session_preload_libraries = ''pgtt''', current_database()); END $$; non-superuser must load the library using the plugins/ directory as follow: DO $$ BEGIN EXECUTE format('ALTER DATABASE %I SET session_preload_libraries = ''$libdir/plugins/pgtt''', current_database()); END $$; Take care to follow installation instruction above to create the symlink from the plugins/ directory to the extension library file. The pgtt extension use a dedicated schema to store related objects, by default: `pgtt_schema`. The extension take care that this schema is always at end of the `search_path`. gtt_testdb=# LOAD '$libdir/plugins/pgtt'; LOAD gtt_testdb=# SHOW search_path; search_path -------------------- public,pgtt_schema (1 row) gtt_testdb=# SET search_path TO appschema,public; SET gtt_testdb=# SHOW search_path; search_path -------------------------------- appschema, public, pgtt_schema (1 row) The pgtt schema is automatically added to the search_path when you load the extension and if you change the `search_path` later. You must also give the USAGE privilege on this schema to users that will manipulate the global temporary tables. #### Create a Global Temporary Table To create a GTT table named "test_table" use the following statement: CREATE GLOBAL TEMPORARY TABLE test_gtt_table ( id integer, lbl text ) ON COMMIT { PRESERVE | DELETE } ROWS; The GLOBAL keyword is obsolete but can be used safely, the only thing is that it will generate a warning: WARNING: GLOBAL is deprecated in temporary table creation If you don't want to be annoyed by this warning message you can use it like a comment instead: CREATE /*GLOBAL*/ TEMPORARY TABLE test_gtt_table ( LIKE other_table LIKE INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES ) ON COMMIT { PRESERVE | DELETE } ROWS; the extension will detect the GLOBAL keyword. As you can see in the example above the LIKE clause is supported, as well as the AS clause WITH DATA or WITH NO DATA (default): CREATE /*GLOBAL*/ TEMPORARY TABLE test_gtt_table AS SELECT * FROM source_table WITH DATA; In case of WITH DATA, the extension will fill the GTT with data returned from the SELECT statement for the current session only. PostgreSQL temporary table clause `ON COMMIT DROP` is not supported by the extension, GTT are persistent over transactions. If the clause is used an error will be raised. Temporary table rows are deleted or preserved at transactions commit following the clause: ON COMMIT { PRESERVE | DELETE } ROWS #### Drop a Global Temporary Table To drop a Global Temporary Table you just proceed as for a normal table: DROP TABLE test_gtt_table; A Global Temporary Table can be dropped even if it is used by other session. #### Create index on Global Temporary Table You can create indexes on the global temporary table: CREATE INDEX ON test_gtt_table (id); just like with any other tables. #### Constraints on Global Temporary Table You can add any constraint on a Global Temporary Table except FOREIGN KEYS. CREATE GLOBAL TEMPORARY TABLE t2 ( c1 serial PRIMARY KEY, c2 VARCHAR (50) UNIQUE NOT NULL, c3 boolean DEFAULT false ) The use of FOREIGN KEYS in a Global Temporary Table is not allowed. CREATE GLOBAL TEMPORARY TABLE t1 (c1 integer, FOREIGN KEY (c1) REFERENCES source (id)); ERROR: attempt to create referential integrity constraint on global temporary table ALTER TABLE t2 ADD FOREIGN KEY (c1) REFERENCES source (id); ERROR: attempt to create referential integrity constraint on global temporary table Even if PostgreSQL allow foreign keys on temporary table, the pgtt extension try to mimic as much as possible the same behavior of Oracle and other RDBMS like DB2, SQL Server or MySQL. ORA-14455: attempt to create referential integrity constraint on temporary table. #### Partitioning Partitioning on Global Temporary Table is not supported, again not because PostgreSQL do not allow partition on temporary table but because other RDBMS like Oracle, DB2 and MySQL do not support it. SQL Server supports partition on global temporary table. ### [How the extension really works](#how-the-extension-really-works) #### Global Temporary Table usage When `pgtt.enabled` is true (default) and the extension have been loaded using any of the three methods: - `session_preload_libraries = 'pgtt'` in postgresql.conf - `ALTER DATABASE mydb SET session_preload_libraries = 'pgtt'` - in a session `LOAD 'pgtt';` the first access to the table using a SELECT, UPDATE or DELETE statement will produce the creation of a temporary table using the definition of the "template" table created during the call to `CREATE GLOBAL TEMPORARY TABLE` statement. Once the temporary table is created at the first access, the original SELECT, UPDATE or DELETE statement is automatically rerouted to the new regular temporary table. All other access will use the new temporary table, the `pg_temp*` schema where the table is created is always looked first in the search path this is why the "template" table is not concerned by subsequent access. Creating, renaming and removing a GTT is an administration task it shall not be done in an application session. Note that rerouting is active even if you add a namespace qualifier to the table. For example looking at the internal unlogged template table: bench=# LOAD 'pgtt'; LOAD bench=# CREATE /*GLOBAL*/ TEMPORARY TABLE test_tt (id int, lbl text) ON COMMIT PRESERVE ROWS; CREATE TABLE bench=# INSERT INTO test_tt VALUES (1, 'one'), (2, 'two'), (3, 'three'); INSERT 0 3 bench=# SELECT * FROM pgtt_schema.test_tt; id | lbl ----+------- 1 | one 2 | two 3 | three (3 rows) will actually result in the same as looking at the associated temporary table like follow: bench=# SELECT * FROM test_tt; id | lbl ----+------- 1 | one 2 | two 3 | three (3 rows) or bench=# SELECT * FROM pg_temp.test_tt; id | lbl ----+------- 1 | one 2 | two 3 | three (3 rows) If you want to really look at the template table to be sure that it contains no rows, you must disable the extension rerouting: bench=# SET pgtt.enabled TO off; SET bench=# SELECT * FROM pgtt_schema.test_tt; id | lbl ----+----- (0 rows) bench=# SET pgtt.enabled TO on; SET bench=# SELECT * FROM pgtt_schema.test_tt; id | lbl ----+------- 1 | one 2 | two 3 | three (3 rows) Look at test file for more examples. This also mean that you can relocate the extension in a dedicated namespace. This can be useful if your application's queries use the schema qualifier with the table name to access to the GTT and you can't change it. See t/sql/relocation.sql for an example. By default the extension is not relocatable in an other schema, there is some configuration change to perform to be able to use this feature. If you use the CREATE AS form with the WITH DATA clause like in this example: CREATE /*GLOBAL*/ TEMPORARY TABLE test_gtt_table AS SELECT * FROM source_table WITH DATA; the extension will first create the template unlogged table and will create immediately the associated temporary table filled with all data returned by the SELECT statement. The first access will not have to create the table it already exists with data. #### Table creation The extension intercept the call to `CREATE TEMPORARY TABLE ...` statement and look if there is the keyword `GLOBAL` or the comment `/*GLOBAL*/`. When it is found, instead of creating the temporary table, it creates a "template" unlogged persistent table following the temporary table definition. When the template is created it registers the table into a "catalog" table `pg_global_temp_tables`. Both objects are created in the extension schema `pgtt_schema`. When `pgtt.enabled` is false nothing is done. Here is the description of the catalog table: ``` Table « pgtt_schema.pg_global_temp_tables » Colonne | Type | Collationnement | NULL-able | Par défaut -----------+---------+-----------------+-----------+------------ relid | integer | | not null | nspname | name | | not null | relname | name | | not null | preserved | boolean | | | code | text | | | Index : "pg_global_temp_tables_nspname_relname_key" UNIQUE CONSTRAINT, btree (nspname, relname) ``` * `relid`: Oid of the "template" unlogged table. * `nspname`: namespace of the extension `pgtt_schema` by default. * `relname`: name of the GTT relation. * `preserved`: true or false for `ON COMMIT { PRESERVE | DELETE}`. * `code`: code used at Global Temporary Table creation time. #### Table removing The extension intercept the call to `DROP TABLE` and look in the `pg_global_temp_tables` table to see if it is declared. When it is found it drops the template unlogged table and the corresponding entry from the pgtt catalog table `pg_global_temp_tables`. When `pgtt.enabled` is false nothing is done. Dropping a GTT that is in use, when the temporary table has already been created, will raise an error. This is not allowed. #### Table renaming The extension intercept the call to `ALTER TABLE ... RENAME` and look in the `pg_global_temp_tables` table to see if it is declared. When it is found it renames the "template" table and update the name of the relation in the `pg_global_temp_tables` table. If the GTT has already been used in the session the corresponding temporary table exists, in this case the extension will refuse to rename it. It must be inactive to be renamed. When `pgtt.enabled` is false nothing is done. Renaming a GTT that is in use, when the temporary table has already been created, will raise an error. This is not allowed. #### pg_dump / pg_restore When dumping a database using the pgtt extension, the content of the "catalog" table `pg_global_temp_tables` will be dumped as well as all template unlogged tables. Restoring the dump will recreate the database in the same state. ### [Performances](#performances) Overhead of loading the extension but without using it in a pgbench tpcb-like scenario. * Without loading the extension ``` $ pgbench -h localhost bench -c 20 -j 4 -T 60 -f test/bench/bench_noload.sql starting vacuum...end. transaction type: test/bench/bench_noload.sql scaling factor: 1 query mode: simple number of clients: 20 number of threads: 4 duration: 60 s number of transactions actually processed: 51741 latency average = 23.201 ms tps = 862.038042 (including connections establishing) tps = 862.165341 (excluding connections establishing) ``` * With loading the extension ``` $ pgbench -h localhost bench -c 20 -j 4 -T 60 -f test/bench/bench_load.sql starting vacuum...end. transaction type: test/bench/bench_load.sql scaling factor: 1 query mode: simple number of clients: 20 number of threads: 4 duration: 60 s number of transactions actually processed: 51171 latency average = 23.461 ms tps = 852.495877 (including connections establishing) tps = 852.599010 (excluding connections establishing) ``` Now a test between using a regular temporary table and a PGTT in the pgbench tpcb-like scenario. * Using a regular Temporary Table ``` $ pgbench -h localhost bench -c 20 -j 4 -T 60 -f test/bench/bench_use_rtt.sql starting vacuum...end. transaction type: test/bench/bench_use_rtt.sql scaling factor: 1 query mode: simple number of clients: 20 number of threads: 4 duration: 60 s number of transactions actually processed: 17153 latency average = 70.058 ms tps = 285.477860 (including connections establishing) tps = 285.514186 (excluding connections establishing) ``` * Using a Global Temporary Table Created using: CREATE GLOBAL TEMPORARY TABLE test_tt (id int, lbl text) ON COMMIT DELETE ROWS; ``` $ pgbench -h localhost bench -c 20 -j 4 -T 60 -f test/bench/bench_use_gtt.sql starting vacuum...end. transaction type: test/bench/bench_use_gtt.sql scaling factor: 1 query mode: simple number of clients: 20 number of threads: 4 duration: 60 s number of transactions actually processed: 17540 latency average = 68.495 ms tps = 291.993502 (including connections establishing) tps = 292.028832 (excluding connections establishing) ``` Even if this last test shows a significant performances improvement comparing to regular temporary tables, most of the time this will not be the case. ### [Authors](#authors) - Gilles Darold - Julien Rouhaud ### [License](#license) This extension is free software distributed under the PostgreSQL Licence. Copyright (c) 2018-2025, Gilles Darold pgtt-4.1/pgtt.c000077500000000000000000001730311476621277000135020ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * pgtt.c * Add support to Oracle-style Global Temporary Table in PostgreSQL. * * Author: Gilles Darold * Licence: PostgreSQL * Copyright (c) 2018-2025, Gilles Darold, * *------------------------------------------------------------------------- */ #include "postgres.h" #include #include "funcapi.h" #include "libpq/pqformat.h" #include "miscadmin.h" #include "access/htup_details.h" #include "access/parallel.h" #include "access/reloptions.h" #include "access/sysattr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "catalog/indexing.h" #include "catalog/namespace.h" #include "catalog/objectaccess.h" #include "catalog/pg_authid.h" #include "catalog/pg_collation.h" #include "catalog/pg_database.h" #include "catalog/pg_extension.h" #include "catalog/pg_namespace.h" #include "catalog/pg_operator.h" #include "catalog/pg_type.h" #include "catalog/toasting.h" #include "commands/dbcommands.h" #include "commands/defrem.h" #include "commands/extension.h" #include "commands/tablecmds.h" #include "commands/comment.h" #include "executor/spi.h" #include "nodes/makefuncs.h" #include "nodes/nodes.h" #include "nodes/pg_list.h" #include "nodes/print.h" #include "nodes/value.h" #include "optimizer/paths.h" #include "optimizer/plancat.h" #include "parser/analyze.h" #include "parser/parse_utilcmd.h" #include "storage/ipc.h" #include "storage/lmgr.h" #include "storage/proc.h" #include "tcop/utility.h" #include "utils/builtins.h" #include "utils/fmgroids.h" #include "utils/formatting.h" #include "utils/inval.h" #include "utils/lsyscache.h" #include "utils/snapmgr.h" #include "utils/syscache.h" #if PG_VERSION_NUM < 110000 #include "utils/memutils.h" #endif /* for regexp search */ #include "regex/regexport.h" #if (PG_VERSION_NUM >= 120000) #include "access/genam.h" #include "access/heapam.h" #include "catalog/pg_class.h" #endif #if PG_VERSION_NUM < 120000 #error Minimum version of PostgreSQL required is 12 #endif #define CATALOG_GLOBAL_TEMP_REL "pg_global_temp_tables" #define Anum_pgtt_relid 1 #define Anum_pgtt_relname 3 PG_MODULE_MAGIC; #define NOT_IN_PARALLEL_WORKER (ParallelWorkerNumber < 0) #if PG_VERSION_NUM >= 140000 #define STMT_OBJTYPE(stmt) stmt->objtype #else #define STMT_OBJTYPE(stmt) stmt->relkind #endif /* Define ProcessUtility hook proto/parameters following the PostgreSQL version */ #if PG_VERSION_NUM >= 140000 #define GTT_PROCESSUTILITY_PROTO PlannedStmt *pstmt, const char *queryString, \ bool readOnlyTree, \ ProcessUtilityContext context, ParamListInfo params, \ QueryEnvironment *queryEnv, DestReceiver *dest, \ QueryCompletion *qc #define GTT_PROCESSUTILITY_ARGS pstmt, queryString, readOnlyTree, context, params, queryEnv, dest, qc #else #if PG_VERSION_NUM >= 130000 #define GTT_PROCESSUTILITY_PROTO PlannedStmt *pstmt, const char *queryString, \ ProcessUtilityContext context, ParamListInfo params, \ QueryEnvironment *queryEnv, DestReceiver *dest, \ QueryCompletion *qc #define GTT_PROCESSUTILITY_ARGS pstmt, queryString, context, params, queryEnv, dest, qc #else #if PG_VERSION_NUM >= 100000 #define GTT_PROCESSUTILITY_PROTO PlannedStmt *pstmt, const char *queryString, \ ProcessUtilityContext context, ParamListInfo params, \ QueryEnvironment *queryEnv, DestReceiver *dest, \ char *completionTag #define GTT_PROCESSUTILITY_ARGS pstmt, queryString, context, params, queryEnv, dest, completionTag #elif PG_VERSION_NUM >= 90300 #define GTT_PROCESSUTILITY_PROTO Node *parsetree, const char *queryString, \ ProcessUtilityContext context, ParamListInfo params, \ DestReceiver *dest, char *completionTag #define GTT_PROCESSUTILITY_ARGS parsetree, queryString, context, params, dest, completionTag #else #define GTT_PROCESSUTILITY_PROTO Node *parsetree, const char *queryString, \ ParamListInfo params, bool isTopLevel, \ DestReceiver *dest, char *completionTag #define GTT_PROCESSUTILITY_ARGS parsetree, queryString, params, isTopLevel, dest, completionTag #endif #endif #endif /* Saved hook values in case of unload */ static ProcessUtility_hook_type prev_ProcessUtility = NULL; static ExecutorStart_hook_type prev_ExecutorStart = NULL; static post_parse_analyze_hook_type prev_post_parse_analyze_hook = NULL; /* Hook to intercept CREATE GLOBAL TEMPORARY TABLE query */ static void gtt_ProcessUtility(GTT_PROCESSUTILITY_PROTO); static void gtt_ExecutorStart(QueryDesc *queryDesc, int eflags); #if PG_VERSION_NUM >= 140000 static void gtt_post_parse_analyze(ParseState *pstate, Query *query, struct JumbleState * jstate); #else static void gtt_post_parse_analyze(ParseState *pstate, Query *query); #endif static void gtt_try_load(void); #if PG_VERSION_NUM < 160000 Oid get_extension_schema(Oid ext_oid); #endif static bool is_declared_gtt(Oid relid); /* Enable use of Global Temporary Table at session level */ bool pgtt_is_enabled = true; /* Regular expression search */ #define CREATE_GLOBAL_REGEXP "^\\s*CREATE\\s+(?:\\/\\*\\s*)?GLOBAL(?:\\s*\\*\\/)?" #define CREATE_WITH_FK_REGEXP "\\s*FOREIGN\\s+KEY" /* Oid and name of pgtt extrension schema in the database */ Oid pgtt_namespace_oid = InvalidOid; char pgtt_namespace_name[NAMEDATALEN]; /* In memory storage of GTT and state */ typedef struct Gtt { Oid relid; Oid temp_relid; char relname[NAMEDATALEN]; bool preserved; bool created; char *code; } Gtt; typedef struct relhashent { char name[NAMEDATALEN]; Gtt gtt; } GttHashEnt; static HTAB *GttHashTable = NULL; /* Default size of the storage area for GTT but will be dynamically extended */ #define GTT_PER_DATABASE 16 #define GttHashTableDelete(NAME) \ do { \ GttHashEnt *hentry; \ \ hentry = (GttHashEnt *) hash_search(GttHashTable, NAME, HASH_REMOVE, NULL); \ if (hentry == NULL) \ elog(DEBUG1, "trying to delete GTT entry in HTAB that does not exist"); \ } while(0) #define GttHashTableLookup(NAME, GTT) \ do { \ GttHashEnt *hentry; \ \ hentry = (GttHashEnt *) hash_search(GttHashTable, \ (NAME), HASH_FIND, NULL); \ if (hentry) \ GTT = hentry->gtt; \ } while(0) #define GttHashTableInsert(GTT, NAME) \ do { \ GttHashEnt *hentry; bool found; \ \ hentry = (GttHashEnt *) hash_search(GttHashTable, \ (NAME), HASH_ENTER, &found); \ if (found) \ elog(ERROR, "duplicate GTT name"); \ hentry->gtt = GTT; \ strcpy(hentry->name, NAME); \ elog(DEBUG1, "Insert GTT entry in HTAB, key: %s, relid: %d, temp_relid: %d, created: %d", hentry->gtt.relname, hentry->gtt.relid, hentry->gtt.temp_relid, hentry->gtt.created); \ } while(0) /* Function declarations */ PGDLLEXPORT void _PG_init(void); PGDLLEXPORT void _PG_fini(void); int strpos(char *hay, char *needle, int offset); static Oid gtt_create_table_statement(Gtt gtt); static void gtt_create_table_as(Gtt gtt, bool skipdata); static void gtt_unregister_global_temporary_table(Oid relid, const char *relname); void GttHashTableDeleteAll(void); bool EnableGttManager(void); Gtt GetGttByName(const char *name); static void gtt_load_global_temporary_tables(void); static Oid create_temporary_table_internal(Oid parent_relid, bool preserved); static bool gtt_check_command(GTT_PROCESSUTILITY_PROTO); static bool gtt_table_exists(QueryDesc *queryDesc); void exitHook(int code, Datum arg); static bool is_catalog_relid(Oid relid); static void force_pgtt_namespace (void); static void gtt_update_registered_table(Gtt gtt); int strremovestr(char *src, char *toremove); static void gtt_unregister_gtt_not_cached(const char *relname); /* * Module load callback */ void _PG_init(void) { elog(DEBUG1, "_PG_init()"); if (ParallelWorkerNumber >= 0) return; /* * If we are loaded via shared_preload_libraries exit. */ if (process_shared_preload_libraries_in_progress) { ereport(FATAL, (errmsg("The pgtt extension can not be loaded using shared_preload_libraries."), errhint("Add 'pgtt' to session_preload_libraries globally, or" " for the wanted roles or databases instead."))); } /* * Define (or redefine) custom GUC variables. * No custom GUC variable at this time */ DefineCustomBoolVariable("pgtt.enabled", "Enable use of Global Temporary Table", "By default the extension is automatically enabled after load, " "it can be temporary disable by setting the GUC value to false " "then enable again later wnen necessary.", &pgtt_is_enabled, true, PGC_USERSET, 0, NULL, NULL, NULL); /* * Immediately try to load the extension. This will probably be a no-op in * the recommended "session_preload_libraries = 'pgtt'" configuration, as * it will happen outside of a transaction, but if the extension is * explicitly loaded with a plain LOAD command then the search_path would * only be changed after the next command is executed. It means that the * very first query executed after such a LOAD wouldn't see the global * temporary tables. */ gtt_try_load(); /* * Install hooks. */ prev_ExecutorStart = ExecutorStart_hook; ExecutorStart_hook = gtt_ExecutorStart; prev_post_parse_analyze_hook = post_parse_analyze_hook; post_parse_analyze_hook = gtt_post_parse_analyze; prev_ProcessUtility = ProcessUtility_hook; ProcessUtility_hook = gtt_ProcessUtility; /* set the exit hook */ on_proc_exit(&exitHook, PointerGetDatum(NULL)); } /* * Module unload callback */ void _PG_fini(void) { elog(DEBUG1, "_PG_fini()"); /* Uninstall hooks. */ ExecutorStart_hook = prev_ExecutorStart; post_parse_analyze_hook = prev_post_parse_analyze_hook; ProcessUtility_hook = prev_ProcessUtility; } /* * Exit hook. */ void exitHook(int code, Datum arg) { elog(DEBUG1, "exiting with %d", code); } static void gtt_ProcessUtility(GTT_PROCESSUTILITY_PROTO) { elog(DEBUG1, "gtt_ProcessUtility()"); /* Do not waste time here if the feature is not enabled for this session */ if (pgtt_is_enabled && NOT_IN_PARALLEL_WORKER) { /* Try to load pgtt if not already done. */ gtt_try_load(); /* * Be sure that extension schema is at end of the search path so that * "template" tables will be find. */ force_pgtt_namespace(); /* * Check if we have a CREATE GLOBAL TEMPORARY TABLE * in this case do more work than the simple table * creation see SQL file in sql/ subdirectory. * * If the current query use a GTT that is not already * created create it. */ if (gtt_check_command(GTT_PROCESSUTILITY_ARGS)) { elog(DEBUG1, "Work on GTT from Utility Hook done, get out of UtilityHook immediately."); return; } } elog(DEBUG1, "restore ProcessUtility"); /* Excecute the utility command, we are not concerned */ PG_TRY(); { if (prev_ProcessUtility) prev_ProcessUtility(GTT_PROCESSUTILITY_ARGS); else standard_ProcessUtility(GTT_PROCESSUTILITY_ARGS); } PG_CATCH(); { PG_RE_THROW(); } PG_END_TRY(); elog(DEBUG1, "End of gtt_ProcessUtility()"); } /* * Look at utility command to search CREATE TABLE / DROP TABLE * and INSERT INTO statements to see if a Global Temporary Table * is concerned. * Return true if all work is done and the origin statement must * be forgotten. False mean that the statement must be processed * normally. */ static bool gtt_check_command(GTT_PROCESSUTILITY_PROTO) { bool preserved = true; bool work_completed = false; char *name = NULL; #if PG_VERSION_NUM >= 100000 Node *parsetree = pstmt->utilityStmt; #endif Assert(parsetree != NULL); Assert(queryString != NULL); elog(DEBUG1, "gtt_check_command() on query: \"%s\"", queryString); if (GttHashTable == NULL) return false; /* Intercept CREATE / DROP TABLE statements */ switch (nodeTag(parsetree)) { case T_VariableSetStmt: { VariableSetStmt *stmt = (VariableSetStmt *) parsetree; /* * Forcing search_path is not enough because it does not * handle SET search_path TO ... statement. This code also * add the PGTT schema if not present in the path */ if (stmt->kind == VAR_SET_VALUE && strcmp(stmt->name, "search_path") == 0) { ListCell *l; bool found = false; if (stmt->args == NIL) break; foreach(l, stmt->args) { Node *arg = (Node *) lfirst(l); A_Const *con = (A_Const *) arg; char *val; val = strVal(&con->val); if (strcmp(val, get_namespace_name(pgtt_namespace_oid)) == 0) found = true; } /* append the extension schema to the arg list. */ if (!found) { A_Const *newcon = makeNode(A_Const); char *str = (char *) get_namespace_name(pgtt_namespace_oid); #if PG_VERSION_NUM < 150000 newcon->val.type = T_String; newcon->val.val.str = pstrdup(str); #else newcon->val.node.type = T_String; newcon->val.sval.sval = pstrdup(str); #endif newcon->location = strlen(queryString); stmt->args = lappend(stmt->args, newcon); } } } break; case T_CreateTableAsStmt: { Gtt gtt; int i; CreateTableAsStmt *stmt = (CreateTableAsStmt *)parsetree; bool skipdata = stmt->into->skipData; bool regexec_result; /* Get the name of the relation */ name = stmt->into->rel->relname; /* * CREATE TABLE AS is similar as SELECT INTO, * so avoid going further in this last case. */ if (stmt->is_select_into) break; /* do not proceed OBJECT_MATVIEW */ if (STMT_OBJTYPE(stmt) != OBJECT_TABLE) break; /* * Be sure to have CREATE TEMPORARY TABLE definition */ if (stmt->into->rel->relpersistence != RELPERSISTENCE_TEMP) break; /* * We only take care here of statements with the GLOBAL keyword * even if it is deprecated and generate a warning. */ regexec_result = RE_compile_and_execute( cstring_to_text(CREATE_GLOBAL_REGEXP), VARDATA_ANY(cstring_to_text((char *) queryString)), VARSIZE_ANY_EXHDR(cstring_to_text((char *) queryString)), REG_ADVANCED | REG_ICASE | REG_NEWLINE, DEFAULT_COLLATION_OID, 0, NULL); if (!regexec_result) break; /* * What to do at commit time for global temporary relations * default is ON COMMIT PRESERVE ROWS (do nothing) */ if (stmt->into->onCommit == ONCOMMIT_DELETE_ROWS) preserved = false; /* * Case of ON COMMIT DROP and GLOBAL TEMPORARY might not be * allowed, this is the same as using a normal temporary table * inside a transaction. Here the table should be dropped after * commit so it will not survive a transaction. * Throw an error to prevent the use of this clause. */ if (stmt->into->onCommit == ONCOMMIT_DROP) ereport(ERROR, (errmsg("use of ON COMMIT DROP with GLOBAL TEMPORARY is not allowed"), errhint("Create a local temporary table inside a transaction instead, this is the default behavior."))); elog(DEBUG1, "Create table %s, rows persistance: %d, GLOBAL at position: %d", name, preserved, strpos(asc_toupper(queryString, strlen(queryString)), "GLOBAL", 0)); /* Force creation of the temporary table in our pgtt schema */ stmt->into->rel->schemaname = pstrdup(pgtt_namespace_name); /* replace temporary state from the table to unlogged table */ stmt->into->rel->relpersistence = RELPERSISTENCE_UNLOGGED; /* Do not copy data in the unlogged table */ stmt->into->skipData = true; /* * At this stage the unlogged table will be created with normal * utility hook. What we need now is to register the table in * the pgtt catalog table and create a normal temporary table * using the original statement without the GLOBAL keyword */ gtt.relid = 0; gtt.temp_relid = 0; strcpy(gtt.relname, name); gtt.relname[strlen(name)] = 0; gtt.preserved = preserved; gtt.created = false; /* Extract the AS ... code part from the query */ gtt.code = pstrdup(queryString); for (i = 30; i < strlen(queryString) - 1; i++) { if ( isspace(queryString[i]) && (queryString[i+1] == 'A' || queryString[i+1] == 'a') && (queryString[i+2] == 'S' || queryString[i+2] == 's') && (isspace(queryString[i+3]) || queryString[i+3] == '(') ) break; } if (i == strlen(queryString) - 1) elog(ERROR, "can not find AS keyword in this CREATE TABLE AS statement."); gtt.code += i; if (gtt.code[strlen(gtt.code) - 1] == ';') gtt.code[strlen(gtt.code) - 1] = 0; /* remove WITH DATA from the code */ strremovestr(gtt.code, "WITH DATA"); /* Create the necessary object to emulate the GTT */ gtt_create_table_as(gtt, skipdata); work_completed = true; break; } case T_CreateStmt: { /* CREATE TABLE statement */ CreateStmt *stmt = (CreateStmt *)parsetree; Gtt gtt; int len, i, start = 0, end = 0; bool regexec_result; /* Get the name of the relation */ name = stmt->relation->relname; /* * Be sure to have CREATE TEMPORARY TABLE definition */ if (stmt->relation->relpersistence != RELPERSISTENCE_TEMP) break; /* * We only take care here of statements with the GLOBAL keyword * even if it is deprecated and generate a warning. */ regexec_result = RE_compile_and_execute( cstring_to_text(CREATE_GLOBAL_REGEXP), VARDATA_ANY(cstring_to_text((char *) queryString)), VARSIZE_ANY_EXHDR(cstring_to_text((char *) queryString)), REG_ADVANCED | REG_ICASE | REG_NEWLINE, DEFAULT_COLLATION_OID, 0, NULL); if (!regexec_result) break; /* Check if there is foreign key defined in the statement */ regexec_result = RE_compile_and_execute( cstring_to_text(CREATE_WITH_FK_REGEXP), VARDATA_ANY(cstring_to_text((char *) queryString)), VARSIZE_ANY_EXHDR(cstring_to_text((char *) queryString)), REG_ADVANCED | REG_ICASE | REG_NEWLINE, DEFAULT_COLLATION_OID, 0, NULL); if (regexec_result) ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("attempt to create referential integrity constraint on global temporary table"))); #if (PG_VERSION_NUM >= 100000) /* * We do not allow partitioning on GTT, not that PostgreSQL can * not do it but because we want to mimic the Oracle or other * RDBMS behavior. */ if (stmt->partspec != NULL) elog(ERROR, "Global Temporary Table do not support partitioning."); #endif /* * What to do at commit time for global temporary relations * default is ON COMMIT PRESERVE ROWS (do nothing) */ if (stmt->oncommit == ONCOMMIT_DELETE_ROWS) preserved = false; /* * Case of ON COMMIT DROP and GLOBAL TEMPORARY might not be * allowed, this is the same as using a normal temporary table * inside a transaction. Here the table should be dropped after * commit so it will not survive a transaction. * Throw an error to prevent the use of this clause. */ if (stmt->oncommit == ONCOMMIT_DROP) ereport(ERROR, (errmsg("use of ON COMMIT DROP with GLOBAL TEMPORARY is not allowed"), errhint("Create a local temporary table inside a transaction instead, this is the default behavior."))); elog(DEBUG1, "Create table %s, rows persistance: %d, GLOBAL at position: %d", name, preserved, strpos(asc_toupper(queryString, strlen(queryString)), "GLOBAL", 0)); /* Create the Global Temporary Table template and register the table */ gtt.relid = 0; gtt.temp_relid = 0; strcpy(gtt.relname, name); gtt.relname[strlen(name)] = 0; gtt.preserved = preserved; gtt.created = false; gtt.code = NULL; /* Extract the definition of the table */ for (i = 0; i < strlen(queryString); i++) { if (queryString[i] == '(') { start = i; break; } } start++; for (i = start; i < strlen(queryString); i++) { if (queryString[i] == ')') { end = i; } } len = end - start; if (end > 0 && start > 0) { gtt.code = palloc0(sizeof(char *) * (len + 1)); strncpy(gtt.code, queryString+start, len); gtt.code[len] = '\0'; } elog(DEBUG1, "code for Global Temporary Table \"%s\" creation is \"%s\"", gtt.relname, gtt.code); /* Create the necessary object to emulate the GTT */ gtt.relid = gtt_create_table_statement(gtt); /* * In case of problem during GTT creation previous function * call throw an error so the code that's follow is safe. * Update GTT cache with table flagged as created */ gtt.created = false; GttHashTableDelete(gtt.relname); GttHashTableInsert(gtt, gtt.relname); work_completed = true; elog(DEBUG1, "Global Temporary Table \"%s\" created", gtt.relname); break; } case T_DropStmt: { DropStmt *drop = (DropStmt *) parsetree; if (drop->removeType == OBJECT_TABLE) { List *relationNameList = NULL; int relationNameListLength = 0; #if PG_VERSION_NUM < 150000 Value *relationSchemaNameValue = NULL; Value *relationNameValue = NULL; #else String *relationSchemaNameValue = NULL; String *relationNameValue = NULL; #endif Gtt gtt; relationNameList = list_copy((List *) linitial(drop->objects)); relationNameListLength = list_length(relationNameList); switch (relationNameListLength) { case 1: { relationNameValue = linitial(relationNameList); break; } case 2: { relationSchemaNameValue = linitial(relationNameList); relationNameValue = lsecond(relationNameList); break; } case 3: { relationSchemaNameValue = lsecond(relationNameList); relationNameValue = lthird(relationNameList); break; } default: { ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("improper relation name: \"%s\"", NameListToString(relationNameList)))); break; } } /* prefix with schema name if it is not added already */ if (relationSchemaNameValue == NULL) { #if PG_VERSION_NUM < 150000 Value *schemaNameValue = makeString(pgtt_namespace_name); #else String *schemaNameValue = makeString(pgtt_namespace_name); #endif relationNameList = lcons(schemaNameValue, relationNameList); } /* * Check if the table is in the hash list, drop * it if it has already been be created and remove * the cache entry. */ #if PG_VERSION_NUM < 150000 if (PointerIsValid(relationNameValue->val.str)) #else if (PointerIsValid(relationNameValue->sval)) #endif { #if PG_VERSION_NUM < 150000 elog(DEBUG1, "looking for dropping table: %s", relationNameValue->val.str); #else elog(DEBUG1, "looking for dropping table: %s", relationNameValue->sval); #endif /* Initialize Gtt object */ gtt.relid = 0; gtt.temp_relid = 0; gtt.relname[0] = '\0'; gtt.preserved = false; gtt.code = NULL; gtt.created = false; #if PG_VERSION_NUM < 150000 elog(DEBUG1, "looking if table %s is a cached GTT", relationNameValue->val.str); GttHashTableLookup(relationNameValue->val.str, gtt); #else elog(DEBUG1, "looking if table %s is a cached GTT", relationNameValue->sval); GttHashTableLookup(relationNameValue->sval, gtt); #endif if (gtt.relname[0] != '\0') { /* * When the temporary table have been created * we can not remove the GTT in the same session. * Creating and dropping GTT can only be performed * by a superuser in a "maintenance" session. */ if (gtt.created) elog(ERROR, "can not drop a GTT that is in use."); /* * Unregister the Global Temporary Table and its link to the * view stored in pg_global_temp_tables table */ gtt_unregister_global_temporary_table(gtt.relid, gtt.relname); /* Remove the table from the hash table */ GttHashTableDelete(gtt.relname); } else { /* * Table is not on current session cache but remove * it from PGTT list if it exists. */ #if PG_VERSION_NUM < 150000 elog(DEBUG1, "looking if table %s is registered as GTT", relationNameValue->val.str); gtt_unregister_gtt_not_cached(relationNameValue->val.str); #else elog(DEBUG1, "looking if table %s is registered as GTT", relationNameValue->sval); gtt_unregister_gtt_not_cached(relationNameValue->sval); #endif } } } break; } case T_RenameStmt: { /* CREATE TABLE statement */ RenameStmt *stmt = (RenameStmt *)parsetree; Gtt gtt; /* We only take care of tabe renaming to update our internal storage */ if (stmt->renameType != OBJECT_TABLE || stmt->newname == NULL) break; gtt.relid = 0; /* Look if the table is declared as GTT */ GttHashTableLookup(stmt->relation->relname, gtt); /* Not registered as a GTT, nothing to do here */ if (gtt.relid == 0) break; /* If a temporary table have already created do not allow changing name */ if (gtt.created) elog(ERROR, "a temporary table has been created and is active, can not rename the GTT table in this session."); /* Rename the table and get the resulting new Oid */ RenameRelation(stmt); elog(DEBUG1, "updating registered table in %s.pg_global_temp_tables.", pgtt_namespace_name); strcpy(gtt.relname, stmt->newname); gtt_update_registered_table(gtt); /* Delete and recreate the table in cache */ GttHashTableDelete(stmt->relation->relname); GttHashTableInsert(gtt, stmt->newname); work_completed = true; break; } case T_CommentStmt: { /* COMMENT ON TABLE/COLUMN statement */ CommentStmt *stmt = (CommentStmt *)parsetree; Relation relation; char *nspname; /* We only take care of comment on table or column to update our internal storage */ if (stmt->objtype != OBJECT_TABLE && stmt->objtype != OBJECT_COLUMN) break; /* * Get the relation object by calling get_object_address(). * get_object_address() will throw an error if the object * does not exist, and will also acquire a lock on the target * to guard against concurrent DROP operations. */ #if (PG_VERSION_NUM < 100000) (void) get_object_address(stmt->objtype, stmt->objname, stmt->objargs, &relation, ShareUpdateExclusiveLock, false); #else (void) get_object_address(stmt->objtype, stmt->object, &relation, ShareUpdateExclusiveLock, false); #endif /* Just take care that the GTT is not in use */ nspname = get_namespace_name(RelationGetNamespace(relation)); relation_close(relation, NoLock); if (strcmp(nspname, pgtt_namespace_name) != 0) { if (strstr(nspname, "pg_temp") != NULL) elog(ERROR, "a temporary table has been created and is active, can not add a comment on the GTT table in this session."); } break; } case T_AlterTableStmt: { /* Look for contrainst statement */ AlterTableStmt *stmt = (AlterTableStmt *)parsetree; ListCell *lcmd; Gtt gtt; if (STMT_OBJTYPE(stmt) != OBJECT_TABLE) break; /* Look if the table is declared as GTT */ gtt.relid = 0; GttHashTableLookup(stmt->relation->relname, gtt); /* Not registered as a GTT, nothing to do here */ if (gtt.relid == 0) break; /* We do not allow foreign keys on global temporary table */ foreach(lcmd, stmt->cmds) { AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd); if (cmd->subtype == AT_AddConstraint #if (PG_VERSION_NUM < 130000) || cmd->subtype == AT_ProcessedConstraint #endif ) { Constraint *constr = (Constraint *) cmd->def; if (constr->contype == CONSTR_FOREIGN) ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("attempt to create referential integrity constraint on global temporary table"))); } } break; } case T_IndexStmt: { /* CREATE INDEX statement */ IndexStmt *stmt = (IndexStmt *) parsetree; Oid relid; char *nspname; relid = RangeVarGetRelidExtended(stmt->relation, ShareLock, #if (PG_VERSION_NUM >= 110000) 0, #else false, false, #endif RangeVarCallbackOwnsRelation, NULL); /* Just take care that the GTT is not in use */ nspname = get_namespace_name(get_rel_namespace(relid)); if (is_declared_gtt(relid)) { if (strcmp(nspname, pgtt_namespace_name) != 0) { if (strstr(nspname, "pg_temp") != NULL) elog(ERROR, "a temporary table has been created and is active, can not add an index on the GTT table in this session."); } } break; } default: break; } return work_completed; } static void gtt_ExecutorStart(QueryDesc *queryDesc, int eflags) { elog(DEBUG1, "gtt_ExecutorStart()"); /* Do not waste time here if the feature is not enabled for this session */ if (pgtt_is_enabled && NOT_IN_PARALLEL_WORKER) { /* Try to load pgtt if not already done. */ gtt_try_load(); /* check if we are working on a GTT and create it if it doesn't exist */ if (queryDesc->operation == CMD_INSERT || queryDesc->operation == CMD_DELETE || queryDesc->operation == CMD_UPDATE || queryDesc->operation == CMD_SELECT) { /* Verify if a GTT table is defined, create it if this is not already the case */ if (gtt_table_exists(queryDesc)) elog(DEBUG1, "ExecutorStart() statement use a Global Temporary Table"); } } elog(DEBUG1, "restore ExecutorStart()"); /* Continue the normal behavior */ if (prev_ExecutorStart) prev_ExecutorStart(queryDesc, eflags); else standard_ExecutorStart(queryDesc, eflags); elog(DEBUG1, "End of gtt_ExecutorStart()"); } static bool gtt_table_exists(QueryDesc *queryDesc) { bool is_gtt = false; char *name = NULL; char relpersistence; RangeTblEntry *rte; Relation rel; Gtt gtt; PlannedStmt *pstmt = (PlannedStmt *) queryDesc->plannedstmt; if (GttHashTable == NULL || !pstmt) return false; /* no relation in rtable probably a function call */ if (list_length(pstmt->rtable) == 0) return false; /* This must be a valid relation and not a temporary table */ rte = (RangeTblEntry *) linitial(pstmt->rtable); if (rte->relid != InvalidOid && rte->relkind == RELKIND_RELATION && !is_catalog_relid(rte->relid)) { #if (PG_VERSION_NUM >= 120000) rel = table_open(rte->relid, NoLock); #else rel = heap_open(rte->relid, NoLock); #endif name = RelationGetRelationName(rel); relpersistence = rel->rd_rel->relpersistence; #if (PG_VERSION_NUM >= 120000) table_close(rel, NoLock); #else heap_close(rel, NoLock); #endif /* Do not go further with temporary tables, catalog or toast table */ if (relpersistence != RELPERSISTENCE_TEMP) return false; gtt.relid = 0; gtt.temp_relid = 0; gtt.relname[0] = '\0'; gtt.preserved = false; gtt.code = NULL; gtt.created = false; /* Check if the table is in the hash list and it has not already be created */ if (PointerIsValid(name)) GttHashTableLookup(name, gtt); elog(DEBUG1, "gtt_table_exists() looking for table \"%s\" with relid %d into cache.", name, rte->relid); if (gtt.relname[0] != '\0') { elog(DEBUG1, "GTT found in cache with name: %s, relid: %d, temp_relid %d", gtt.relname, gtt.relid, gtt.temp_relid); /* Create the temporary table if it does not exists */ if (!gtt.created) { elog(DEBUG1, "global temporary table does not exists create it: %s", gtt.relname); /* Call create temporary table */ if ((gtt.temp_relid = create_temporary_table_internal(gtt.relid, gtt.preserved)) != InvalidOid) { elog(DEBUG1, "global temporary table %s (oid: %d) created", gtt.relname, gtt.temp_relid); /* Update hash list with table flagged as created */ gtt.created = true; GttHashTableDelete(gtt.relname); GttHashTableInsert(gtt, gtt.relname); } else elog(ERROR, "can not create global temporary table %s", gtt.relname); } is_gtt = true; } else /* the table is not a global temporary table do nothing*/ elog(DEBUG1, "table \"%s\" not registered as GTT", name); } return is_gtt; } static bool is_declared_gtt(Oid relid) { char *name = NULL; char relpersistence; Relation rel; Gtt gtt; if (GttHashTable == NULL) return false; /* This must be a valid relation and not a temporary table */ if (relid != InvalidOid && !is_catalog_relid(relid)) { #if (PG_VERSION_NUM >= 120000) rel = table_open(relid, NoLock); #else rel = heap_open(relid, NoLock); #endif name = RelationGetRelationName(rel); relpersistence = rel->rd_rel->relpersistence; #if (PG_VERSION_NUM >= 120000) table_close(rel, NoLock); #else heap_close(rel, NoLock); #endif /* Do not go further with temporary tables, catalog or toast table */ if (relpersistence != RELPERSISTENCE_TEMP) return false; /* Check if the table is in the hash list and it has not already be created */ gtt.relid = 0; gtt.temp_relid = 0; gtt.relname[0] = '\0'; gtt.preserved = false; gtt.code = NULL; gtt.created = false; if (PointerIsValid(name)) GttHashTableLookup(name, gtt); if (gtt.relname[0] != '\0') return true; } return false; } int strpos(char *hay, char *needle, int offset) { char *haystack; char *p; haystack = (char *) malloc(strlen(hay)); if (haystack == NULL) { fprintf(stderr, "out of memory\n"); exit(EXIT_FAILURE); return -1; } memset(haystack, 0, strlen(hay)); strncpy(haystack, hay+offset, strlen(hay)-offset); p = strstr(haystack, needle); if (p) return p - haystack+offset; return -1; } /* * Create the Global Temporary Table with all associated objects * by creating the template table and register the GTT in the * pg_global_temp_tables table. * */ static Oid gtt_create_table_statement(Gtt gtt) { char *newQueryString = NULL; int connected = 0; int finished = 0; int result = 0; Oid gttOid = InvalidOid; Datum oidDatum; bool isnull; elog(DEBUG1, "proceeding to Global Temporary Table creation."); connected = SPI_connect(); if (connected != SPI_OK_CONNECT) ereport(ERROR, (errmsg("could not connect to SPI manager"))); /* Create the "template" table */ newQueryString = psprintf("CREATE UNLOGGED TABLE %s.%s (%s)", quote_identifier(pgtt_namespace_name), quote_identifier(gtt.relname), gtt.code); result = SPI_exec(newQueryString, 0); if (result < 0) ereport(ERROR, (errmsg("execution failure on query: \"%s\"", newQueryString))); /* Get Oid of the newly created table */ newQueryString = psprintf("SELECT c.relfilenode FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE c.relname='%s' AND n.nspname = '%s'", gtt.relname, pgtt_namespace_name); result = SPI_exec(newQueryString, 0); if (result != SPI_OK_SELECT && SPI_processed != 1) ereport(ERROR, (errmsg("execution failure on query: \"%s\"", newQueryString))); oidDatum = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull); if (!isnull) gttOid = DatumGetInt32(oidDatum); if (isnull || !OidIsValid(gttOid)) ereport(ERROR, (errmsg("can not get OID of newly created GTT template table %s", quote_identifier(gtt.relname)))); /* Now register the GTT table */ newQueryString = psprintf("INSERT INTO %s.pg_global_temp_tables VALUES (%d, '%s', '%s', '%c', %s)", quote_identifier(pgtt_namespace_name), gttOid, pgtt_namespace_name, gtt.relname, (gtt.preserved) ? 't' : 'f', quote_literal_cstr(gtt.code) ); result = SPI_exec(newQueryString, 0); if (result < 0) ereport(ERROR, (errmsg("can not registrer new global temporary table"))); /* Set privilege on the unlogged table */ newQueryString = psprintf("GRANT ALL ON TABLE %s.%s TO public", quote_identifier(pgtt_namespace_name), quote_identifier(gtt.relname)); result = SPI_exec(newQueryString, 0); if (result < 0) ereport(ERROR, (errmsg("execution failure on query: \"%s\"", newQueryString))); /* Mark the GTT as been created before register the table in the cache */ gtt.created = true; finished = SPI_finish(); if (finished != SPI_OK_FINISH) ereport(ERROR, (errmsg("could not disconnect from SPI manager"))); return gttOid; } /* * Unregister a Global Temporary Table in pg_global_temp_tables table * using his relid. */ static void gtt_unregister_global_temporary_table(Oid relid, const char *relname) { RangeVar *rv; Relation rel; ScanKeyData key[1]; SysScanDesc scan; HeapTuple tuple; elog(DEBUG1, "Looking for registered GTT relid = %d, relname = %s", relid, relname); /* Set and open the GTT relation */ rv = makeRangeVar(pgtt_namespace_name, CATALOG_GLOBAL_TEMP_REL, -1); #if (PG_VERSION_NUM >= 120000) rel = table_openrv(rv, RowExclusiveLock); #else rel = heap_openrv(rv, RowExclusiveLock); #endif /* Define scanning */ ScanKeyInit(&key[0], Anum_pgtt_relid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relid)); /* Start search of relation */ scan = systable_beginscan(rel, 0, true, NULL, 1, key); /* Remove the tuples. */ while (HeapTupleIsValid(tuple = systable_getnext(scan))) { elog(DEBUG1, "removing tuple with relid = %d and relname = %s", relid, relname); simple_heap_delete(rel, &tuple->t_self); } /* Cleanup. */ systable_endscan(scan); #if (PG_VERSION_NUM >= 120000) table_close(rel, RowExclusiveLock); #else heap_close(rel, RowExclusiveLock); #endif } /* * Unregister a Global Temporary Table in pg_global_temp_tables table * that is not cached and using his name only. */ static void gtt_unregister_gtt_not_cached(const char *relname) { RangeVar *rv; Relation rel; ScanKeyData key[1]; SysScanDesc scan; HeapTuple tuple; elog(DEBUG1, "Looking for registered GTT relname = %s", relname); /* Set and open the GTT relation */ rv = makeRangeVar(pgtt_namespace_name, CATALOG_GLOBAL_TEMP_REL, -1); #if (PG_VERSION_NUM >= 120000) rel = table_openrv(rv, RowExclusiveLock); #else rel = heap_openrv(rv, RowExclusiveLock); #endif /* Define scanning */ ScanKeyInit(&key[0], Anum_pgtt_relname, BTEqualStrategyNumber, F_NAMEEQ, CStringGetDatum(relname)); /* Start search of relation */ scan = systable_beginscan(rel, 0, true, NULL, 1, key); /* Remove the tuples. */ while (HeapTupleIsValid(tuple = systable_getnext(scan))) { elog(DEBUG1, "removing tuple with relname = %s", relname); simple_heap_delete(rel, &tuple->t_self); } /* Cleanup. */ systable_endscan(scan); #if (PG_VERSION_NUM >= 120000) table_close(rel, RowExclusiveLock); #else heap_close(rel, RowExclusiveLock); #endif } /* * Check if pgtt hasn't been loaded yet, and try to load it in that case. */ static void gtt_try_load(void) { /* * Don't try to load if the extension is disabled, if we can't do it now or * if it's already loaded. */ if (!pgtt_is_enabled || !IsTransactionState() || GttHashTable != NULL) return; /* Initialize list of Global Temporary Table */ if (EnableGttManager()) { /* * Load temporary table definition from pg_global_temp_tables table * into our Hash table and pre-create the temporary tables. */ gtt_load_global_temporary_tables(); /* * Be sure that extension schema is at end of the search path so that * "template" tables will be found. */ force_pgtt_namespace(); } } #if PG_VERSION_NUM < 160000 /* * From src/backend/commands/extension.c */ Oid get_extension_schema(Oid ext_oid) { Oid result; Relation rel; SysScanDesc scandesc; HeapTuple tuple; ScanKeyData entry[1]; #if (PG_VERSION_NUM >= 120000) rel = table_open(ExtensionRelationId, AccessShareLock); #else rel = heap_open(ExtensionRelationId, AccessShareLock); #endif ScanKeyInit(&entry[0], #if (PG_VERSION_NUM >= 120000) Anum_pg_extension_oid, #else ObjectIdAttributeNumber, #endif BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(ext_oid)); scandesc = systable_beginscan(rel, ExtensionOidIndexId, true, NULL, 1, entry); tuple = systable_getnext(scandesc); /* We assume that there can be at most one matching tuple */ if (HeapTupleIsValid(tuple)) result = ((Form_pg_extension) GETSTRUCT(tuple))->extnamespace; else result = InvalidOid; systable_endscan(scandesc); #if (PG_VERSION_NUM >= 120000) table_close(rel, AccessShareLock); #else heap_close(rel, AccessShareLock); #endif return result; } #endif /* * EnableGttManager * Enables the GTT management cache at backend startup. */ bool EnableGttManager(void) { Oid extOid = get_extension_oid("pgtt", true); RangeVar *rv; char *nspname; if (!OidIsValid(extOid)) return false; pgtt_namespace_oid = get_extension_schema(extOid); if (!OidIsValid(pgtt_namespace_oid)) elog(ERROR, "namespace %d can not be found.", pgtt_namespace_oid); /* * Check if the GTT relation also exist. We might be in the middle of the * extension creation, where the line in pg_extension exists but not the * rest of SQL objects. */ nspname = get_namespace_name(pgtt_namespace_oid); rv = makeRangeVar(nspname, CATALOG_GLOBAL_TEMP_REL, -1); if (!OidIsValid(RangeVarGetRelid(rv, AccessShareLock, true))) return false; if (GttHashTable == NULL) { HASHCTL ctl; MemSet(&ctl, 0, sizeof(ctl)); ctl.keysize = NAMEDATALEN; ctl.entrysize = sizeof(GttHashEnt); /* allocate GTT Cache in the cache context */ ctl.hcxt = CacheMemoryContext; GttHashTable = hash_create("Global Temporary Table hash list", GTT_PER_DATABASE, &ctl, #if PG_VERSION_NUM >= 140000 HASH_STRINGS | HASH_ELEM | HASH_CONTEXT #else HASH_ELEM | HASH_CONTEXT #endif ); elog(DEBUG1, "GTT cache initialized."); } /* * Set the OID and name of the extension schema, all objects will be * created in this schema. */ strcpy(pgtt_namespace_name, nspname); return true; } /* * Delete all declared Global Temporary Table. * */ void GttHashTableDeleteAll(void) { HASH_SEQ_STATUS status; GttHashEnt *lentry = NULL; if (GttHashTable == NULL) return; hash_seq_init(&status, GttHashTable); while ((lentry = (GttHashEnt *) hash_seq_search(&status)) != NULL) { Gtt gtt = GetGttByName(lentry->name); elog(DEBUG1, "Remove GTT %s from our hash table", gtt.relname); GttHashTableDelete(lentry->name); /* Restart the iteration in case that led to other drops */ hash_seq_term(&status); hash_seq_init(&status, GttHashTable); } } /* * GetGttByName * Returns a Gtt given a table name, or NULL if name is not found. * * Caller should have made sure that GTT has been properly loaded before * calling this function. */ Gtt GetGttByName(const char *name) { Gtt gtt; Assert(GttHashTable != NULL); if (PointerIsValid(name)) GttHashTableLookup(name, gtt); return gtt; } /* * Load Global Temporary Table in memory from pg_global_temp_tables table. */ static void gtt_load_global_temporary_tables(void) { RangeVar *rv; Relation rel; #if (PG_VERSION_NUM >= 120000) TableScanDesc scan; #else HeapScanDesc scan; #endif HeapTuple tuple; int numberOfAttributes; TupleDesc tupleDesc; Snapshot snapshot; elog(DEBUG1, "gtt_load_global_temporary_tables()"); elog(DEBUG1, "retrieve GTT list from definition table %s.%s", pgtt_namespace_name, CATALOG_GLOBAL_TEMP_REL); /* Set and open the GTT definition storage relation */ rv = makeRangeVar(pgtt_namespace_name, CATALOG_GLOBAL_TEMP_REL, -1); /* Open the CATALOG_GLOBAL_TEMP_REL table. We don't want to allow * writable accesses by other session during import. */ PushActiveSnapshot(GetTransactionSnapshot()); snapshot = GetActiveSnapshot(); #if (PG_VERSION_NUM >= 120000) rel = table_openrv(rv, AccessShareLock); scan = table_beginscan(rel, snapshot, 0, (ScanKey) NULL); #else rel = heap_openrv(rv, AccessShareLock); scan = heap_beginscan(rel, snapshot, 0, (ScanKey) NULL); #endif tupleDesc = RelationGetDescr(rel); numberOfAttributes = tupleDesc->natts; while (HeapTupleIsValid(tuple = heap_getnext(scan, ForwardScanDirection))) { Gtt gtt; Datum *values = (Datum *) palloc(numberOfAttributes * sizeof(Datum)); bool *isnull = (bool *) palloc(numberOfAttributes * sizeof(bool)); /* Extract data */ heap_deform_tuple(tuple, tupleDesc, values, isnull); gtt.relid = DatumGetInt32(values[0]); strcpy(gtt.relname, NameStr(*(DatumGetName(values[2])))); gtt.preserved = DatumGetBool(values[3]); gtt.code = TextDatumGetCString(values[4]); gtt.created = false; gtt.temp_relid = 0; /* Add table to cache */ GttHashTableInsert(gtt, gtt.relname); } /* Cleanup. */ #if (PG_VERSION_NUM >= 120000) table_endscan(scan); table_close(rel, AccessShareLock); #else heap_endscan(scan); heap_close(rel, AccessShareLock); #endif PopActiveSnapshot(); } static Oid create_temporary_table_internal(Oid parent_relid, bool preserved) { /* Value to be returned */ Oid temp_relid = InvalidOid; /* safety */ #if (PG_VERSION_NUM >= 130000) ObjectAddress address; #endif /* Parent's namespace and name */ Oid parent_nsp; char *parent_name, *parent_nsp_name; char parent_persistence; /* Elements of the "CREATE TABLE" query tree */ RangeVar *parent_rv; RangeVar *table_rv; TableLikeClause *like_clause = makeNode(TableLikeClause); CreateStmt *createStmt = makeNode(CreateStmt); List *createStmts; ListCell *lc; elog(DEBUG1, "creating a temporary table like table with Oid %d", parent_relid); /* Lock parent and check if it exists */ LockRelationOid(parent_relid, ShareUpdateExclusiveLock); if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(parent_relid))) elog(ERROR, "relation %u does not exist", parent_relid); /* Cache parent's namespace and name */ parent_name = get_rel_name(parent_relid); parent_nsp = get_rel_namespace(parent_relid); parent_nsp_name = get_namespace_name(parent_nsp); parent_persistence = get_rel_persistence(parent_relid); /* Make up parent's RangeVar */ parent_rv = makeRangeVar(parent_nsp_name, parent_name, -1); parent_rv->relpersistence = parent_persistence; elog(DEBUG1, "Parent namespace: %s, parent relname: %s, parent oid: %d", parent_rv->schemaname, parent_rv->relname, parent_relid); /* Set name of temporary table same as parent table */ table_rv = makeRangeVar("pg_temp", parent_rv->relname, -1); Assert(table_rv); elog(DEBUG1, "Initialize TableLikeClause structure"); /* Initialize TableLikeClause structure */ like_clause->relation = copyObject(parent_rv); like_clause->options = CREATE_TABLE_LIKE_DEFAULTS | CREATE_TABLE_LIKE_INDEXES | CREATE_TABLE_LIKE_CONSTRAINTS #if (PG_VERSION_NUM >= 100000) | CREATE_TABLE_LIKE_IDENTITY #endif #if (PG_VERSION_NUM >= 120000) | CREATE_TABLE_LIKE_GENERATED #endif | CREATE_TABLE_LIKE_COMMENTS; elog(DEBUG1, "Initialize CreateStmt structure"); /* Initialize CreateStmt structure */ createStmt->relation = copyObject(table_rv); createStmt->relation->schemaname = NULL; createStmt->relation->relpersistence = RELPERSISTENCE_TEMP; createStmt->tableElts = list_make1(copyObject(like_clause)); createStmt->inhRelations = NIL; createStmt->ofTypename = NULL; createStmt->constraints = NIL; createStmt->options = NIL; #if (PG_VERSION_NUM >= 120000) createStmt->accessMethod = NULL; #endif if (preserved) createStmt->oncommit = ONCOMMIT_PRESERVE_ROWS; else createStmt->oncommit = ONCOMMIT_DELETE_ROWS; createStmt->tablespacename = NULL; createStmt->if_not_exists = false; elog(DEBUG1, "Obtain the sequence of Stmts to create temporary table"); /* Obtain the sequence of Stmts to create temporary table */ createStmts = transformCreateStmt(createStmt, NULL); elog(DEBUG1, "Processing list of statements"); /* Create the temporary table */ foreach (lc, createStmts) { /* Fetch current CreateStmt */ Node *cur_stmt = (Node *) lfirst(lc); elog(DEBUG1, "Processing statement of type %d", nodeTag(cur_stmt)); if (IsA(cur_stmt, CreateStmt)) { Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; Oid temp_relowner; /* Temporary table owner must be current user */ temp_relowner = GetUserId(); elog(DEBUG1, "Creating a temporary table and get its Oid"); /* Create a temporary table and save its Oid */ #if (PG_VERSION_NUM < 100000) temp_relid = DefineRelation((CreateStmt *) cur_stmt, RELKIND_RELATION, temp_relowner, NULL).objectId; #elif (PG_VERSION_NUM < 130000) temp_relid = DefineRelation((CreateStmt *) cur_stmt, RELKIND_RELATION, temp_relowner, NULL, NULL).objectId; #else address = DefineRelation((CreateStmt *) cur_stmt, RELKIND_RELATION, temp_relowner, NULL, NULL); temp_relid = address.objectId; #endif /* Update config one more time */ CommandCounterIncrement(); /* * parse and validate reloptions for the toast * table */ toast_options = transformRelOptions((Datum) 0, ((CreateStmt *) cur_stmt)->options, "toast", validnsps, true, false); (void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true); NewRelationCreateToastTable(temp_relid, toast_options); } else if (IsA(cur_stmt, IndexStmt)) { Oid relid; elog(DEBUG1, "execution statement CREATE INDEX, relation has an index."); relid = RangeVarGetRelidExtended(((IndexStmt *) cur_stmt)->relation, ShareLock, #if (PG_VERSION_NUM >= 110000) 0, #else false, false, #endif RangeVarCallbackOwnsRelation, NULL); DefineIndex(relid, /* OID of heap relation */ (IndexStmt *) cur_stmt, InvalidOid, /* no predefined OID */ #if (PG_VERSION_NUM >= 110000) InvalidOid, /* no parent index */ InvalidOid, /* no parent constraint */ #endif #if (PG_VERSION_NUM >= 160000) -1,/* total parts */ #endif false, /* is_alter_table */ true, /* check_rights */ #if (PG_VERSION_NUM > 100000) true, /* check_not_in_use */ #endif false, /* skip_build */ false); /* quiet */ } else if (IsA(cur_stmt, CommentStmt)) { CommentObject((CommentStmt *) cur_stmt); } #if (PG_VERSION_NUM >= 90600) else if (IsA(cur_stmt, TableLikeClause)) { TableLikeClause *like = (TableLikeClause *) cur_stmt; RangeVar *rv = createStmt->relation; List *morestmts; morestmts = expandTableLikeClause(rv, like); createStmts = list_concat(createStmts, morestmts); /* don't need a CCI now */ continue; } #endif else { /* * Recurse for anything else. */ #if PG_VERSION_NUM >= 100000 PlannedStmt *stmt = makeNode(PlannedStmt); stmt->commandType = CMD_UTILITY; stmt->canSetTag = true; stmt->utilityStmt = cur_stmt; stmt->stmt_location = -1; stmt->stmt_len = 0; ProcessUtility(stmt, "PGTT provide a query string", #if PG_VERSION_NUM >= 140000 false, #endif PROCESS_UTILITY_SUBCOMMAND, NULL, NULL, None_Receiver, NULL); #else ProcessUtility(cur_stmt, "PGTT provide a query string", #if PG_VERSION_NUM >= 140000 false, #endif PROCESS_UTILITY_SUBCOMMAND, NULL, None_Receiver, NULL); #endif } /* Need CCI between commands */ #if (PG_VERSION_NUM < 130000) if (lnext(lc) != NULL) #else if (lnext(createStmts, lc) != NULL) #endif CommandCounterIncrement(); } /* release lock on "template" relation */ UnlockRelationOid(parent_relid, ShareUpdateExclusiveLock); elog(DEBUG1, "Create a temporary table done with Oid: %d", temp_relid); return temp_relid; } /* * Post-parse-analysis hook: mark query with a queryId */ static void #if PG_VERSION_NUM >= 140000 gtt_post_parse_analyze(ParseState *pstate, Query *query, struct JumbleState * jstate) #else gtt_post_parse_analyze(ParseState *pstate, Query *query) #endif { /* Try to load pgtt if not already done. */ gtt_try_load(); if (NOT_IN_PARALLEL_WORKER && pgtt_is_enabled && query->rtable != NIL && GttHashTable != NULL) { /* replace the Oid of the template table by our new table in the rtable */ RangeTblEntry *rte = (RangeTblEntry *) linitial(query->rtable); Relation rel; Gtt gtt; char *name = NULL; /* This must be a valid relation not from pg_catalog*/ if (rte->relid != InvalidOid && rte->relkind == RELKIND_RELATION && !is_catalog_relid(rte->relid)) { #if (PG_VERSION_NUM >= 120000) rel = table_open(rte->relid, NoLock); #else rel = heap_open(rte->relid, NoLock); #endif name = RelationGetRelationName(rel); #if (PG_VERSION_NUM >= 120000) table_close(rel, NoLock); #else heap_close(rel, NoLock); #endif gtt.relid = 0; gtt.temp_relid = 0; gtt.relname[0] = '\0'; gtt.preserved = false; gtt.code = NULL; gtt.created = false; /* Check if the table is in the hash list and it has not already be created */ if (PointerIsValid(name)) { elog(DEBUG1, "gtt_post_parse_analyze() looking for table \"%s\" with relid %d into cache.", name, rte->relid); GttHashTableLookup(name, gtt); } else elog(ERROR, "gtt_post_parse_analyze() table to search in cache is not valide pointer, relid: %d.", rte->relid); if (gtt.relname[0] != '\0') { /* After an error and rollback the table is still registered in cache but must be initialized */ if (gtt.created && OidIsValid(gtt.temp_relid) && !SearchSysCacheExists1(RELOID, ObjectIdGetDatum(gtt.temp_relid)) ) { elog(DEBUG1, "invalid temporary table with relid %d (%s), reseting.", gtt.temp_relid, gtt.relname); gtt.created = false; gtt.temp_relid = 0; } /* Create the temporary table if it does not exists */ if (!gtt.created) { elog(DEBUG1, "global temporary table from relid %d does not exists create it: %s", rte->relid, gtt.relname); /* Call create temporary table */ if ((gtt.temp_relid = create_temporary_table_internal(gtt.relid, gtt.preserved)) != InvalidOid) { elog(DEBUG1, "global temporary table %s (oid: %d) created", gtt.relname, gtt.temp_relid); /* Update hash list with table flagged as created*/ gtt.created = true; GttHashTableDelete(gtt.relname); GttHashTableInsert(gtt, gtt.relname); } else elog(ERROR, "can not create global temporary table %s", gtt.relname); } elog(DEBUG1, "temporary table exists with oid %d", gtt.temp_relid); if (rte->relid != gtt.temp_relid) { #if PG_VERSION_NUM >= 160000 RTEPermissionInfo *rteperm = list_nth(query->rteperminfos, rte->perminfoindex - 1); rteperm->relid = gtt.temp_relid; #endif LockRelationOid(gtt.temp_relid, rte->rellockmode); if (rte->rellockmode != AccessShareLock) UnlockRelationOid(rte->relid, rte->rellockmode); rte->relid = gtt.temp_relid; elog(DEBUG1, "rerouting relid %d access to %d for GTT table \"%s\"", rte->relid, gtt.temp_relid, name); } } else /* the table is not a global temporary table do nothing*/ elog(DEBUG1, "table \"%s\" not registered as GTT", name); } } /* restore hook */ if (prev_post_parse_analyze_hook) { #if PG_VERSION_NUM >= 140000 prev_post_parse_analyze_hook(pstate, query, jstate); #else prev_post_parse_analyze_hook(pstate, query); #endif } } static bool is_catalog_relid(Oid relid) { HeapTuple reltup; Form_pg_class relform; Oid relnamespace; reltup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); if (!HeapTupleIsValid(reltup)) elog(ERROR, "cache lookup failed for relation %u", relid); relform = (Form_pg_class) GETSTRUCT(reltup); relnamespace = relform->relnamespace; ReleaseSysCache(reltup); if (relnamespace == PG_CATALOG_NAMESPACE || relnamespace == PG_TOAST_NAMESPACE) { elog(DEBUG1, "relation %d is in pg_catalog or pg_toast schema, nothing to do.", relid); return true; } return false; } /* * Be sure that extension schema is at end of the search path so that * "template" tables will be found. */ static void force_pgtt_namespace (void) { #if PG_VERSION_NUM >= 170000 SearchPathMatcher *overridePath; #else OverrideSearchPath *overridePath; #endif ListCell *lc; Oid schemaId = InvalidOid; StringInfoData search_path; bool found = false; bool first = true; if (!IsTransactionState() || GttHashTable == NULL) return; #if PG_VERSION_NUM >= 170000 overridePath = GetSearchPathMatcher(CurrentMemoryContext); #else overridePath = GetOverrideSearchPath(CurrentMemoryContext); #endif initStringInfo(&search_path); /* verify that extension schema is in the path */ foreach(lc, overridePath->schemas) { schemaId = lfirst_oid(lc); if (schemaId == InvalidOid) continue; if (schemaId == pgtt_namespace_oid) found = true; if (!first) appendStringInfoChar(&search_path, ','); appendStringInfo(&search_path, "%s", quote_identifier(get_namespace_name(schemaId))); first = false; } if (!found) { if (!first) appendStringInfoChar(&search_path, ','); appendStringInfo(&search_path, "%s", quote_identifier(pgtt_namespace_name)); /* * Override the search_path by adding our pgtt schema */ (void) set_config_option("search_path", search_path.data, (superuser() ? PGC_SUSET : PGC_USERSET), PGC_S_SESSION, GUC_ACTION_SET, true, 0, false ); } elog(DEBUG1, "search_path forced to %s.", search_path.data); } /* * Update a registered Global Temporary Table * in the pg_global_temp_tables table. * */ static void gtt_update_registered_table(Gtt gtt) { char *newQueryString = NULL; int connected = 0; int finished = 0; int result = 0; elog(DEBUG1, "proceeding to Global Temporary Table creation."); connected = SPI_connect(); if (connected != SPI_OK_CONNECT) ereport(ERROR, (errmsg("could not connect to SPI manager"))); newQueryString = psprintf("UPDATE %s.pg_global_temp_tables SET relname = '%s' WHERE relid = %d", quote_identifier(pgtt_namespace_name), gtt.relname, gtt.relid ); result = SPI_exec(newQueryString, 0); if (result < 0) ereport(ERROR, (errmsg("can not update relid %d into %s.pg_global_temp_tables", gtt.relid, quote_identifier(pgtt_namespace_name)))); finished = SPI_finish(); if (finished != SPI_OK_FINISH) ereport(ERROR, (errmsg("could not disconnect from SPI manager"))); } /* * Create the temporary table related to a Global Temporary Table * and register the GTT in pg_global_temp_tables table. * */ static void gtt_create_table_as(Gtt gtt, bool skipdata) { char *newQueryString = NULL; int connected = 0; int finished = 0; int result = 0; Oid gttOid = InvalidOid; Datum oidDatum; bool isnull; elog(DEBUG1, "proceeding to Global Temporary Table creation."); /* This can only be called if GTT has been properly loaded. */ Assert(GttHashTable != NULL); connected = SPI_connect(); if (connected != SPI_OK_CONNECT) ereport(ERROR, (errmsg("could not connect to SPI manager"))); /* Create the "template" table */ newQueryString = psprintf("CREATE UNLOGGED TABLE %s.%s %s;", quote_identifier(pgtt_namespace_name), quote_identifier(gtt.relname), gtt.code); result = SPI_exec(newQueryString, 0); if (result < 0) ereport(ERROR, (errmsg("execution failure on query: \"%s\"", newQueryString))); /* Get Oid of the newly created table */ newQueryString = psprintf("SELECT c.relfilenode FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE c.relname='%s' AND n.nspname = '%s'", gtt.relname, pgtt_namespace_name); result = SPI_exec(newQueryString, 0); if (result != SPI_OK_SELECT && SPI_processed != 1) ereport(ERROR, (errmsg("execution failure on query: \"%s\"", newQueryString))); oidDatum = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull); if (!isnull) gttOid = DatumGetInt32(oidDatum); if (isnull || !OidIsValid(gttOid)) ereport(ERROR, (errmsg("can not get OID of newly created GTT template table %s", quote_identifier(gtt.relname)))); gtt.relid = gttOid; /* Create the temporary table only if data from source table must be inserted */ if (!skipdata) { char namespaceName[NAMEDATALEN]; /* Get current temporary namespace name */ snprintf(namespaceName, sizeof(namespaceName), "pg_temp_%d", #if PG_VERSION_NUM < 170000 MyBackendId #else MyProcNumber #endif ); newQueryString = psprintf("CREATE TEMPORARY TABLE %s %s WITH DATA", quote_identifier(gtt.relname), gtt.code); result = SPI_exec(newQueryString, 0); if (result < 0) ereport(ERROR, (errmsg("execution failure on query: \"%s\"", newQueryString))); /* Get Oid of the newly created temporary table */ newQueryString = psprintf("SELECT c.relfilenode FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE c.relname='%s' AND n.nspname = '%s'", gtt.relname, namespaceName); result = SPI_exec(newQueryString, 0); if (result != SPI_OK_SELECT && SPI_processed != 1) ereport(ERROR, (errmsg("execution failure on query: \"%s\"", newQueryString))); oidDatum = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull); if (!isnull) gtt.temp_relid = DatumGetInt32(oidDatum); if (isnull || !OidIsValid(gttOid)) ereport(ERROR, (errmsg("can not get OID of newly created temporary table %s", quote_identifier(gtt.relname)))); gtt.created = true; } /* Now register the GTT table */ newQueryString = psprintf("INSERT INTO %s.pg_global_temp_tables VALUES (%d, '%s', '%s', '%c', %s)", quote_identifier(pgtt_namespace_name), gtt.relid, pgtt_namespace_name, gtt.relname, (gtt.preserved) ? 't' : 'f', quote_literal_cstr(gtt.code) ); result = SPI_exec(newQueryString, 0); if (result < 0) ereport(ERROR, (errmsg("can not registrer new global temporary table"))); finished = SPI_finish(); if (finished != SPI_OK_FINISH) ereport(ERROR, (errmsg("could not disconnect from SPI manager"))); /* registrer the table in the cache */ GttHashTableDelete(gtt.relname); GttHashTableInsert(gtt, gtt.relname); } int strremovestr(char *src, char *toremove) { while( *src ) { char *k=toremove,*s=src; while( *k && *k==*s ) ++k,++s; if( !*k ) { while( *s ) *src++=*s++; *src=0; return 1; } ++src; } return 0; } pgtt-4.1/pgtt.control000077500000000000000000000002611476621277000147320ustar00rootroot00000000000000default_version = '4.0.0' comment = 'Extension to add Global Temporary Tables feature to PostgreSQL' module_pathname = '$libdir/pgtt' schema = 'pgtt_schema' relocatable = false pgtt-4.1/pgtt.md000077500000000000000000000447171476621277000136700ustar00rootroot00000000000000* [Description](#description) * [Installation](#installation) * [Configuration](#configuration) * [Use of the extension](#use-of-the-extension) * [How the extension really works](#how-the-extension-really-works) * [Performances](performances) * [Authors](#authors) ## PostgreSQL Global Temporary Tables ### [Description](#description) pgtt is a PostgreSQL extension to create, manage and use Oracle-style Global Temporary Tables and the others RDBMS. The objective of this extension it to provide the Global Temporary Table feature to PostgreSQL waiting for an in core implementation. The main interest of this extension is to mimic the Oracle behavior with GTT when you can not or don't want to rewrite the application code when migrating to PostgreSQL. In all other case best is to rewrite the code to use standard PostgreSQL temporary tables. This version of the GTT extension use a regular unlogged table as "template" table and an internal rerouting to a temporary table. See chapter "How the extension really works" for more details. A previous implementation of this extension using Row Security Level is still available [here](https://github.com/darold/pgtt-rsl). PostgreSQL native temporary tables are automatically dropped at the end of a session, or optionally at the end of the current transaction. Global Temporary Tables (GTT) are permanent, they are created as regular tables visible to all users but their content is relative to the current session or transaction. Even if the table is persistent a session or transaction can not see rows written by an other session. Usually this is not a problem, you have learn to deal with the temporary table behavior of PostgreSQL but the problem comes when you are migrating an Oracle database to PostgreSQL. You have to rewrite the SQL and PlPgSQL code to follow the application logic and use PostgreSQL temporary table, that mean recreating the temporary table everywhere it is used. The other advantage of this kind of object is when your application creates and drops a lot of temporary tables, the PostgreSQL catalogs becomes bloated and the performances start to fall. Usually Global Temporary Tables prevent catalog bloating, but with this implementation and even if we have a permanent table, all DML are rerouted to a regular temporary table created at first access. See below chapter "How the extension really works" for more information. DECLARE TEMPORARY TABLE statement is not supported by PostgreSQL and by this extension. However this statement defines a temporary table for the current connection / session, it creates tables that do not reside in the system catalogs and are not persistent. It cannot be shared with other sessions. This is the equivalent of PostgreSQL standard CREATE TEMPORARY TABLE so you might just have to replace the DECLARE keyword by CREATE. All Oracle's GTT behavior are respected with the different clauses minus what is not supported by PostgreSQL: #### ON COMMIT {DELETE | PRESERVE} ROWS Specifies the action taken on the global temporary table when a COMMIT operation is performed. - DELETE ROWS: all rows of the table will be deleted if no holdable cursor is open on the table. - PRESERVE ROWS: the rows of the table will be preserved after the COMMIT. #### LOGGED or NOT LOGGED [ ON ROLLBACK {DELETE | PRESERVE} ROWS ] Specifies whether operations for the table are logged. The default is `NOT LOGGED ON ROLLBACK DELETE ROWS`. * NOT LOGGED: Specifies that insert, update, or delete operations against the table are not to be logged, but that the creation or dropping of the table is to be logged. During a ROLLBACK or ROLLBACK TO SAVEPOINT operation: - If the table had been created within a transaction, the table is dropped - If the table had been dropped within a transaction, the table is recreated, but without any data * ON ROLLBACK: Specifies the action that is to be taken on the not logged created temporary table when a ROLLBACK or ROLLBACK TO SAVEPOINT operation is performed. The default is DELETE ROWS. - DELETE ROWS: if the table data has been changed, all the rows will be deleted. - PRESERVE ROWS: rows of the table will be preserved. * LOGGED: specifies that insert, update, or delete operations against the table as well as the creation or dropping of the table are to be logged. With PostgreSQL only `NOT LOGGED ON ROLLBACK DELETE ROWS` can be supported. Creation or dropping of the Global Temporary Table are logged, see below "How the extension really works" for the details. ### [Installation](#installation) To install the pgtt extension you need at least a PostgreSQL version 12. Untar the pgtt tarball anywhere you want then you'll need to compile it with pgxs. The `pg_config` tool must be in your path. Depending on your installation, you may need to install some devel package. Once `pg_config` is in your path, do make sudo make install Then it will be possible to use it using `session_preload_libraries = 'pgtt'` in postgresql.conf To create and manage GTT using a non-superuser role you will have to grant the CREATE privilege on the `pgtt_schema` schema to the user. For example: GRANT ALL ON SCHEMA pgtt_schema TO pgtt_user1; To run test execute the following command as superuser: make installcheck An additional standalone test is provided to test the use of the extension as non superuser. The test can be executed using: mkdir results createdb gtt_privilege LANG=C psql -d gtt_privilege -f test/privilege.sql > results/privilege.out 2>&1 diff results/privilege.out test/expected/privilege.out dropdb gtt_privilege dropuser pgtt_user1 ### [Configuration](#configuration) - *pgtt.enabled* The extension can be enable / disable using this GUC, default is enabled. To disable the extension use: SET pgtt.enabled TO off; You can disable or enable the extension at any moment in a session. ### [Use of the extension](#use-of-the-extension) In all database where you want to use Global Temporary Tables you will have to create the extension using: CREATE EXTENSION pgtt; You can load the extension by setting in postgresql.conf : session_preload_libraries = 'pgtt'; or by setting it at database level as follow: DO $$ BEGIN EXECUTE format('ALTER DATABASE %I SET session_preload_libraries = ''pgtt''', current_database()); END $$; non-superuser must load the library using the plugins/ directory as follow: DO $$ BEGIN EXECUTE format('ALTER DATABASE %I SET session_preload_libraries = ''$libdir/plugins/pgtt''', current_database()); END $$; Take care to follow installation instruction above to create the symlink from the plugins/ directory to the extension library file. The pgtt extension use a dedicated schema to store related objects, by default: `pgtt_schema`. The extension take care that this schema is always at end of the `search_path`. gtt_testdb=# LOAD '$libdir/plugins/pgtt'; LOAD gtt_testdb=# SHOW search_path; search_path -------------------- public,pgtt_schema (1 row) gtt_testdb=# SET search_path TO appschema,public; SET gtt_testdb=# SHOW search_path; search_path -------------------------------- appschema, public, pgtt_schema (1 row) The pgtt schema is automatically added to the search_path when you load the extension and if you change the `search_path` later. You must also give the USAGE privilege on this schema to users that will manipulate the global temporary tables. #### Create a Global Temporary Table To create a GTT table named "test_table" use the following statement: CREATE GLOBAL TEMPORARY TABLE test_gtt_table ( id integer, lbl text ) ON COMMIT { PRESERVE | DELETE } ROWS; The GLOBAL keyword is obsolete but can be used safely, the only thing is that it will generate a warning: WARNING: GLOBAL is deprecated in temporary table creation If you don't want to be annoyed by this warning message you can use it like a comment instead: CREATE /*GLOBAL*/ TEMPORARY TABLE test_gtt_table ( LIKE other_table LIKE INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES ) ON COMMIT { PRESERVE | DELETE } ROWS; the extension will detect the GLOBAL keyword. As you can see in the example above the LIKE clause is supported, as well as the AS clause WITH DATA or WITH NO DATA (default): CREATE /*GLOBAL*/ TEMPORARY TABLE test_gtt_table AS SELECT * FROM source_table WITH DATA; In case of WITH DATA, the extension will fill the GTT with data returned from the SELECT statement for the current session only. PostgreSQL temporary table clause `ON COMMIT DROP` is not supported by the extension, GTT are persistent over transactions. If the clause is used an error will be raised. Temporary table rows are deleted or preserved at transactions commit following the clause: ON COMMIT { PRESERVE | DELETE } ROWS #### Drop a Global Temporary Table To drop a Global Temporary Table you just proceed as for a normal table: DROP TABLE test_gtt_table; A Global Temporary Table can be dropped even if it is used by other session. #### Create index on Global Temporary Table You can create indexes on the global temporary table: CREATE INDEX ON test_gtt_table (id); just like with any other tables. #### Constraints on Global Temporary Table You can add any constraint on a Global Temporary Table except FOREIGN KEYS. CREATE GLOBAL TEMPORARY TABLE t2 ( c1 serial PRIMARY KEY, c2 VARCHAR (50) UNIQUE NOT NULL, c3 boolean DEFAULT false ) The use of FOREIGN KEYS in a Global Temporary Table is not allowed. CREATE GLOBAL TEMPORARY TABLE t1 (c1 integer, FOREIGN KEY (c1) REFERENCES source (id)); ERROR: attempt to create referential integrity constraint on global temporary table ALTER TABLE t2 ADD FOREIGN KEY (c1) REFERENCES source (id); ERROR: attempt to create referential integrity constraint on global temporary table Even if PostgreSQL allow foreign keys on temporary table, the pgtt extension try to mimic as much as possible the same behavior of Oracle and other RDBMS like DB2, SQL Server or MySQL. ORA-14455: attempt to create referential integrity constraint on temporary table. #### Partitioning Partitioning on Global Temporary Table is not supported, again not because PostgreSQL do not allow partition on temporary table but because other RDBMS like Oracle, DB2 and MySQL do not support it. SQL Server supports partition on global temporary table. ### [How the extension really works](#how-the-extension-really-works) #### Global Temporary Table usage When `pgtt.enabled` is true (default) and the extension have been loaded using any of the three methods: - `session_preload_libraries = 'pgtt'` in postgresql.conf - `ALTER DATABASE mydb SET session_preload_libraries = 'pgtt'` - in a session `LOAD 'pgtt';` the first access to the table using a SELECT, UPDATE or DELETE statement will produce the creation of a temporary table using the definition of the "template" table created during the call to `CREATE GLOBAL TEMPORARY TABLE` statement. Once the temporary table is created at the first access, the original SELECT, UPDATE or DELETE statement is automatically rerouted to the new regular temporary table. All other access will use the new temporary table, the `pg_temp*` schema where the table is created is always looked first in the search path this is why the "template" table is not concerned by subsequent access. Creating, renaming and removing a GTT is an administration task it shall not be done in an application session. Note that rerouting is active even if you add a namespace qualifier to the table. For example looking at the internal unlogged template table: bench=# LOAD 'pgtt'; LOAD bench=# CREATE /*GLOBAL*/ TEMPORARY TABLE test_tt (id int, lbl text) ON COMMIT PRESERVE ROWS; CREATE TABLE bench=# INSERT INTO test_tt VALUES (1, 'one'), (2, 'two'), (3, 'three'); INSERT 0 3 bench=# SELECT * FROM pgtt_schema.test_tt; id | lbl ----+------- 1 | one 2 | two 3 | three (3 rows) will actually result in the same as looking at the associated temporary table like follow: bench=# SELECT * FROM test_tt; id | lbl ----+------- 1 | one 2 | two 3 | three (3 rows) or bench=# SELECT * FROM pg_temp.test_tt; id | lbl ----+------- 1 | one 2 | two 3 | three (3 rows) If you want to really look at the template table to be sure that it contains no rows, you must disable the extension rerouting: bench=# SET pgtt.enabled TO off; SET bench=# SELECT * FROM pgtt_schema.test_tt; id | lbl ----+----- (0 rows) bench=# SET pgtt.enabled TO on; SET bench=# SELECT * FROM pgtt_schema.test_tt; id | lbl ----+------- 1 | one 2 | two 3 | three (3 rows) Look at test file for more examples. This also mean that you can relocate the extension in a dedicated namespace. This can be useful if your application's queries use the schema qualifier with the table name to access to the GTT and you can't change it. See t/sql/relocation.sql for an example. By default the extension is not relocatable in an other schema, there is some configuration change to perform to be able to use this feature. If you use the CREATE AS form with the WITH DATA clause like in this example: CREATE /*GLOBAL*/ TEMPORARY TABLE test_gtt_table AS SELECT * FROM source_table WITH DATA; the extension will first create the template unlogged table and will create immediately the associated temporary table filled with all data returned by the SELECT statement. The first access will not have to create the table it already exists with data. #### Table creation The extension intercept the call to `CREATE TEMPORARY TABLE ...` statement and look if there is the keyword `GLOBAL` or the comment `/*GLOBAL*/`. When it is found, instead of creating the temporary table, it creates a "template" unlogged persistent table following the temporary table definition. When the template is created it registers the table into a "catalog" table `pg_global_temp_tables`. Both objects are created in the extension schema `pgtt_schema`. When `pgtt.enabled` is false nothing is done. Here is the description of the catalog table: ``` Table « pgtt_schema.pg_global_temp_tables » Colonne | Type | Collationnement | NULL-able | Par défaut -----------+---------+-----------------+-----------+------------ relid | integer | | not null | nspname | name | | not null | relname | name | | not null | preserved | boolean | | | code | text | | | Index : "pg_global_temp_tables_nspname_relname_key" UNIQUE CONSTRAINT, btree (nspname, relname) ``` * `relid`: Oid of the "template" unlogged table. * `nspname`: namespace of the extension `pgtt_schema` by default. * `relname`: name of the GTT relation. * `preserved`: true or false for `ON COMMIT { PRESERVE | DELETE}`. * `code`: code used at Global Temporary Table creation time. #### Table removing The extension intercept the call to `DROP TABLE` and look in the `pg_global_temp_tables` table to see if it is declared. When it is found it drops the template unlogged table and the corresponding entry from the pgtt catalog table `pg_global_temp_tables`. When `pgtt.enabled` is false nothing is done. Dropping a GTT that is in use, when the temporary table has already been created, will raise an error. This is not allowed. #### Table renaming The extension intercept the call to `ALTER TABLE ... RENAME` and look in the `pg_global_temp_tables` table to see if it is declared. When it is found it renames the "template" table and update the name of the relation in the `pg_global_temp_tables` table. If the GTT has already been used in the session the corresponding temporary table exists, in this case the extension will refuse to rename it. It must be inactive to be renamed. When `pgtt.enabled` is false nothing is done. Renaming a GTT that is in use, when the temporary table has already been created, will raise an error. This is not allowed. #### pg_dump / pg_restore When dumping a database using the pgtt extension, the content of the "catalog" table `pg_global_temp_tables` will be dumped as well as all template unlogged tables. Restoring the dump will recreate the database in the same state. ### [Performances](#performances) Overhead of loading the extension but without using it in a pgbench tpcb-like scenario. * Without loading the extension ``` $ pgbench -h localhost bench -c 20 -j 4 -T 60 -f test/bench/bench_noload.sql starting vacuum...end. transaction type: test/bench/bench_noload.sql scaling factor: 1 query mode: simple number of clients: 20 number of threads: 4 duration: 60 s number of transactions actually processed: 51741 latency average = 23.201 ms tps = 862.038042 (including connections establishing) tps = 862.165341 (excluding connections establishing) ``` * With loading the extension ``` $ pgbench -h localhost bench -c 20 -j 4 -T 60 -f test/bench/bench_load.sql starting vacuum...end. transaction type: test/bench/bench_load.sql scaling factor: 1 query mode: simple number of clients: 20 number of threads: 4 duration: 60 s number of transactions actually processed: 51171 latency average = 23.461 ms tps = 852.495877 (including connections establishing) tps = 852.599010 (excluding connections establishing) ``` Now a test between using a regular temporary table and a PGTT in the pgbench tpcb-like scenario. * Using a regular Temporary Table ``` $ pgbench -h localhost bench -c 20 -j 4 -T 60 -f test/bench/bench_use_rtt.sql starting vacuum...end. transaction type: test/bench/bench_use_rtt.sql scaling factor: 1 query mode: simple number of clients: 20 number of threads: 4 duration: 60 s number of transactions actually processed: 17153 latency average = 70.058 ms tps = 285.477860 (including connections establishing) tps = 285.514186 (excluding connections establishing) ``` * Using a Global Temporary Table Created using: CREATE GLOBAL TEMPORARY TABLE test_tt (id int, lbl text) ON COMMIT DELETE ROWS; ``` $ pgbench -h localhost bench -c 20 -j 4 -T 60 -f test/bench/bench_use_gtt.sql starting vacuum...end. transaction type: test/bench/bench_use_gtt.sql scaling factor: 1 query mode: simple number of clients: 20 number of threads: 4 duration: 60 s number of transactions actually processed: 17540 latency average = 68.495 ms tps = 291.993502 (including connections establishing) tps = 292.028832 (excluding connections establishing) ``` Even if this last test shows a significant performances improvement comparing to regular temporary tables, most of the time this will not be the case. ### [Authors](#authors) - Gilles Darold - Julien Rouhaud ### [License](#license) This extension is free software distributed under the PostgreSQL Licence. Copyright (c) 2018-2025, Gilles Darold pgtt-4.1/sql/000077500000000000000000000000001476621277000131475ustar00rootroot00000000000000pgtt-4.1/sql/pgtt--2.0.0.sql000066400000000000000000000014701476621277000153600ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgtt" to load this file. \quit ---- -- Create schema dedicated to the global temporary table ---- CREATE SCHEMA IF NOT EXISTS @extschema@; REVOKE ALL ON SCHEMA @extschema@ FROM PUBLIC; GRANT USAGE ON SCHEMA @extschema@ TO PUBLIC; ---- -- Table used to store information about Global Temporary Tables. -- Content will be loaded in memory by the pgtt extension. ---- CREATE TABLE @extschema@.pg_global_temp_tables ( relid integer NOT NULL, nspname name NOT NULL, relname name NOT NULL, preserved boolean, code text, UNIQUE (nspname, relname) ); GRANT ALL ON TABLE @extschema@.pg_global_temp_tables TO PUBLIC; -- Include tables into pg_dump SELECT pg_catalog.pg_extension_config_dump('pg_global_temp_tables', ''); pgtt-4.1/sql/pgtt--2.1.0.sql000066400000000000000000000014701476621277000153610ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgtt" to load this file. \quit ---- -- Create schema dedicated to the global temporary table ---- CREATE SCHEMA IF NOT EXISTS @extschema@; REVOKE ALL ON SCHEMA @extschema@ FROM PUBLIC; GRANT USAGE ON SCHEMA @extschema@ TO PUBLIC; ---- -- Table used to store information about Global Temporary Tables. -- Content will be loaded in memory by the pgtt extension. ---- CREATE TABLE @extschema@.pg_global_temp_tables ( relid integer NOT NULL, nspname name NOT NULL, relname name NOT NULL, preserved boolean, code text, UNIQUE (nspname, relname) ); GRANT ALL ON TABLE @extschema@.pg_global_temp_tables TO PUBLIC; -- Include tables into pg_dump SELECT pg_catalog.pg_extension_config_dump('pg_global_temp_tables', ''); pgtt-4.1/sql/pgtt--2.10.0.sql000066400000000000000000000014321476621277000154370ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgtt" to load this file. \quit ---- -- Fix privileges on schema dedicated to the global temporary table ---- REVOKE ALL ON SCHEMA @extschema@ FROM PUBLIC; GRANT USAGE ON SCHEMA @extschema@ TO PUBLIC; ---- -- Table used to store information about Global Temporary Tables. -- Content will be loaded in memory by the pgtt extension. ---- CREATE TABLE @extschema@.pg_global_temp_tables ( relid integer NOT NULL, nspname name NOT NULL, relname name NOT NULL, preserved boolean, code text, UNIQUE (nspname, relname) ); GRANT ALL ON TABLE @extschema@.pg_global_temp_tables TO PUBLIC; -- Include tables into pg_dump SELECT pg_catalog.pg_extension_config_dump('pg_global_temp_tables', ''); pgtt-4.1/sql/pgtt--2.2.0.sql000066400000000000000000000014701476621277000153620ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgtt" to load this file. \quit ---- -- Create schema dedicated to the global temporary table ---- CREATE SCHEMA IF NOT EXISTS @extschema@; REVOKE ALL ON SCHEMA @extschema@ FROM PUBLIC; GRANT USAGE ON SCHEMA @extschema@ TO PUBLIC; ---- -- Table used to store information about Global Temporary Tables. -- Content will be loaded in memory by the pgtt extension. ---- CREATE TABLE @extschema@.pg_global_temp_tables ( relid integer NOT NULL, nspname name NOT NULL, relname name NOT NULL, preserved boolean, code text, UNIQUE (nspname, relname) ); GRANT ALL ON TABLE @extschema@.pg_global_temp_tables TO PUBLIC; -- Include tables into pg_dump SELECT pg_catalog.pg_extension_config_dump('pg_global_temp_tables', ''); pgtt-4.1/sql/pgtt--2.3.0.sql000066400000000000000000000014701476621277000153630ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgtt" to load this file. \quit ---- -- Create schema dedicated to the global temporary table ---- CREATE SCHEMA IF NOT EXISTS @extschema@; REVOKE ALL ON SCHEMA @extschema@ FROM PUBLIC; GRANT USAGE ON SCHEMA @extschema@ TO PUBLIC; ---- -- Table used to store information about Global Temporary Tables. -- Content will be loaded in memory by the pgtt extension. ---- CREATE TABLE @extschema@.pg_global_temp_tables ( relid integer NOT NULL, nspname name NOT NULL, relname name NOT NULL, preserved boolean, code text, UNIQUE (nspname, relname) ); GRANT ALL ON TABLE @extschema@.pg_global_temp_tables TO PUBLIC; -- Include tables into pg_dump SELECT pg_catalog.pg_extension_config_dump('pg_global_temp_tables', ''); pgtt-4.1/sql/pgtt--2.4.0.sql000066400000000000000000000014701476621277000153640ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgtt" to load this file. \quit ---- -- Create schema dedicated to the global temporary table ---- CREATE SCHEMA IF NOT EXISTS @extschema@; REVOKE ALL ON SCHEMA @extschema@ FROM PUBLIC; GRANT USAGE ON SCHEMA @extschema@ TO PUBLIC; ---- -- Table used to store information about Global Temporary Tables. -- Content will be loaded in memory by the pgtt extension. ---- CREATE TABLE @extschema@.pg_global_temp_tables ( relid integer NOT NULL, nspname name NOT NULL, relname name NOT NULL, preserved boolean, code text, UNIQUE (nspname, relname) ); GRANT ALL ON TABLE @extschema@.pg_global_temp_tables TO PUBLIC; -- Include tables into pg_dump SELECT pg_catalog.pg_extension_config_dump('pg_global_temp_tables', ''); pgtt-4.1/sql/pgtt--2.5.0.sql000066400000000000000000000014701476621277000153650ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgtt" to load this file. \quit ---- -- Create schema dedicated to the global temporary table ---- CREATE SCHEMA IF NOT EXISTS @extschema@; REVOKE ALL ON SCHEMA @extschema@ FROM PUBLIC; GRANT USAGE ON SCHEMA @extschema@ TO PUBLIC; ---- -- Table used to store information about Global Temporary Tables. -- Content will be loaded in memory by the pgtt extension. ---- CREATE TABLE @extschema@.pg_global_temp_tables ( relid integer NOT NULL, nspname name NOT NULL, relname name NOT NULL, preserved boolean, code text, UNIQUE (nspname, relname) ); GRANT ALL ON TABLE @extschema@.pg_global_temp_tables TO PUBLIC; -- Include tables into pg_dump SELECT pg_catalog.pg_extension_config_dump('pg_global_temp_tables', ''); pgtt-4.1/sql/pgtt--2.6.0.sql000066400000000000000000000014701476621277000153660ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgtt" to load this file. \quit ---- -- Create schema dedicated to the global temporary table ---- CREATE SCHEMA IF NOT EXISTS @extschema@; REVOKE ALL ON SCHEMA @extschema@ FROM PUBLIC; GRANT USAGE ON SCHEMA @extschema@ TO PUBLIC; ---- -- Table used to store information about Global Temporary Tables. -- Content will be loaded in memory by the pgtt extension. ---- CREATE TABLE @extschema@.pg_global_temp_tables ( relid integer NOT NULL, nspname name NOT NULL, relname name NOT NULL, preserved boolean, code text, UNIQUE (nspname, relname) ); GRANT ALL ON TABLE @extschema@.pg_global_temp_tables TO PUBLIC; -- Include tables into pg_dump SELECT pg_catalog.pg_extension_config_dump('pg_global_temp_tables', ''); pgtt-4.1/sql/pgtt--2.7.0.sql000066400000000000000000000014701476621277000153670ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgtt" to load this file. \quit ---- -- Create schema dedicated to the global temporary table ---- CREATE SCHEMA IF NOT EXISTS @extschema@; REVOKE ALL ON SCHEMA @extschema@ FROM PUBLIC; GRANT USAGE ON SCHEMA @extschema@ TO PUBLIC; ---- -- Table used to store information about Global Temporary Tables. -- Content will be loaded in memory by the pgtt extension. ---- CREATE TABLE @extschema@.pg_global_temp_tables ( relid integer NOT NULL, nspname name NOT NULL, relname name NOT NULL, preserved boolean, code text, UNIQUE (nspname, relname) ); GRANT ALL ON TABLE @extschema@.pg_global_temp_tables TO PUBLIC; -- Include tables into pg_dump SELECT pg_catalog.pg_extension_config_dump('pg_global_temp_tables', ''); pgtt-4.1/sql/pgtt--2.8.0.sql000066400000000000000000000014701476621277000153700ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgtt" to load this file. \quit ---- -- Create schema dedicated to the global temporary table ---- CREATE SCHEMA IF NOT EXISTS @extschema@; REVOKE ALL ON SCHEMA @extschema@ FROM PUBLIC; GRANT USAGE ON SCHEMA @extschema@ TO PUBLIC; ---- -- Table used to store information about Global Temporary Tables. -- Content will be loaded in memory by the pgtt extension. ---- CREATE TABLE @extschema@.pg_global_temp_tables ( relid integer NOT NULL, nspname name NOT NULL, relname name NOT NULL, preserved boolean, code text, UNIQUE (nspname, relname) ); GRANT ALL ON TABLE @extschema@.pg_global_temp_tables TO PUBLIC; -- Include tables into pg_dump SELECT pg_catalog.pg_extension_config_dump('pg_global_temp_tables', ''); pgtt-4.1/sql/pgtt--2.9.0.sql000066400000000000000000000014321476621277000153670ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgtt" to load this file. \quit ---- -- Fix privileges on schema dedicated to the global temporary table ---- REVOKE ALL ON SCHEMA @extschema@ FROM PUBLIC; GRANT USAGE ON SCHEMA @extschema@ TO PUBLIC; ---- -- Table used to store information about Global Temporary Tables. -- Content will be loaded in memory by the pgtt extension. ---- CREATE TABLE @extschema@.pg_global_temp_tables ( relid integer NOT NULL, nspname name NOT NULL, relname name NOT NULL, preserved boolean, code text, UNIQUE (nspname, relname) ); GRANT ALL ON TABLE @extschema@.pg_global_temp_tables TO PUBLIC; -- Include tables into pg_dump SELECT pg_catalog.pg_extension_config_dump('pg_global_temp_tables', ''); pgtt-4.1/sql/pgtt--3.0.0.sql000066400000000000000000000014321476621277000153570ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgtt" to load this file. \quit ---- -- Fix privileges on schema dedicated to the global temporary table ---- REVOKE ALL ON SCHEMA @extschema@ FROM PUBLIC; GRANT USAGE ON SCHEMA @extschema@ TO PUBLIC; ---- -- Table used to store information about Global Temporary Tables. -- Content will be loaded in memory by the pgtt extension. ---- CREATE TABLE @extschema@.pg_global_temp_tables ( relid integer NOT NULL, nspname name NOT NULL, relname name NOT NULL, preserved boolean, code text, UNIQUE (nspname, relname) ); GRANT ALL ON TABLE @extschema@.pg_global_temp_tables TO PUBLIC; -- Include tables into pg_dump SELECT pg_catalog.pg_extension_config_dump('pg_global_temp_tables', ''); pgtt-4.1/sql/pgtt--3.1.0.sql000066400000000000000000000014321476621277000153600ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgtt" to load this file. \quit ---- -- Fix privileges on schema dedicated to the global temporary table ---- REVOKE ALL ON SCHEMA @extschema@ FROM PUBLIC; GRANT USAGE ON SCHEMA @extschema@ TO PUBLIC; ---- -- Table used to store information about Global Temporary Tables. -- Content will be loaded in memory by the pgtt extension. ---- CREATE TABLE @extschema@.pg_global_temp_tables ( relid integer NOT NULL, nspname name NOT NULL, relname name NOT NULL, preserved boolean, code text, UNIQUE (nspname, relname) ); GRANT ALL ON TABLE @extschema@.pg_global_temp_tables TO PUBLIC; -- Include tables into pg_dump SELECT pg_catalog.pg_extension_config_dump('pg_global_temp_tables', ''); pgtt-4.1/sql/pgtt--3.2.0.sql000066400000000000000000000014321476621277000153610ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgtt" to load this file. \quit ---- -- Fix privileges on schema dedicated to the global temporary table ---- REVOKE ALL ON SCHEMA @extschema@ FROM PUBLIC; GRANT USAGE ON SCHEMA @extschema@ TO PUBLIC; ---- -- Table used to store information about Global Temporary Tables. -- Content will be loaded in memory by the pgtt extension. ---- CREATE TABLE @extschema@.pg_global_temp_tables ( relid integer NOT NULL, nspname name NOT NULL, relname name NOT NULL, preserved boolean, code text, UNIQUE (nspname, relname) ); GRANT ALL ON TABLE @extschema@.pg_global_temp_tables TO PUBLIC; -- Include tables into pg_dump SELECT pg_catalog.pg_extension_config_dump('pg_global_temp_tables', ''); pgtt-4.1/sql/pgtt--4.0.0.sql000066400000000000000000000014321476621277000153600ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgtt" to load this file. \quit ---- -- Fix privileges on schema dedicated to the global temporary table ---- REVOKE ALL ON SCHEMA @extschema@ FROM PUBLIC; GRANT USAGE ON SCHEMA @extschema@ TO PUBLIC; ---- -- Table used to store information about Global Temporary Tables. -- Content will be loaded in memory by the pgtt extension. ---- CREATE TABLE @extschema@.pg_global_temp_tables ( relid integer NOT NULL, nspname name NOT NULL, relname name NOT NULL, preserved boolean, code text, UNIQUE (nspname, relname) ); GRANT ALL ON TABLE @extschema@.pg_global_temp_tables TO PUBLIC; -- Include tables into pg_dump SELECT pg_catalog.pg_extension_config_dump('pg_global_temp_tables', ''); pgtt-4.1/test/000077500000000000000000000000001476621277000133275ustar00rootroot00000000000000pgtt-4.1/test/bench/000077500000000000000000000000001476621277000144065ustar00rootroot00000000000000pgtt-4.1/test/bench/bench_load.sql000066400000000000000000000010511476621277000172020ustar00rootroot00000000000000\set aid random(1, 100000 * :scale) \set bid random(1, 1 * :scale) \set tid random(1, 10 * :scale) \set delta random(-5000, 5000) -- LOAD 'pgtt'; BEGIN; UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid; SELECT abalance FROM pgbench_accounts WHERE aid = :aid; UPDATE pgbench_tellers SET tbalance = tbalance + :delta WHERE tid = :tid; UPDATE pgbench_branches SET bbalance = bbalance + :delta WHERE bid = :bid; INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP); END; pgtt-4.1/test/bench/bench_use_gtt.sql000066400000000000000000000013361476621277000177430ustar00rootroot00000000000000\set aid random(1, 100000 * :scale) \set bid random(1, 1 * :scale) \set tid random(1, 10 * :scale) \set delta random(-5000, 5000) -- LOAD 'pgtt'; BEGIN; INSERT INTO test_tt (id, lbl) SELECT i, md5(i::text) FROM generate_series(1, 1000) i; UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid; SELECT abalance FROM pgbench_accounts WHERE aid = :aid; UPDATE pgbench_tellers SET tbalance = tbalance + :delta WHERE tid = :tid; UPDATE pgbench_branches SET bbalance = bbalance + :delta WHERE bid = :bid; UPDATE test_tt SET id = id+100 WHERE id > 500; INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP); SELECT * FROM test_tt ORDER BY id DESC LIMIT 10; END; pgtt-4.1/test/bench/bench_use_rtt.sql000066400000000000000000000015201476621277000177510ustar00rootroot00000000000000\set aid random(1, 100000 * :scale) \set bid random(1, 1 * :scale) \set tid random(1, 10 * :scale) \set delta random(-5000, 5000) SET client_min_messages TO error; CREATE TEMPORARY TABLE IF NOT EXISTS test_tt (id integer, lbl varchar) ON COMMIT DELETE ROWS; BEGIN; INSERT INTO test_tt (id, lbl) SELECT i, md5(i::text) FROM generate_series(1, 1000) i; UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid; SELECT abalance FROM pgbench_accounts WHERE aid = :aid; UPDATE pgbench_tellers SET tbalance = tbalance + :delta WHERE tid = :tid; UPDATE pgbench_branches SET bbalance = bbalance + :delta WHERE bid = :bid; UPDATE test_tt SET id = id+100 WHERE id > 500; INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP); SELECT * FROM test_tt ORDER BY id DESC LIMIT 10; END; pgtt-4.1/test/expected/000077500000000000000000000000001476621277000151305ustar00rootroot00000000000000pgtt-4.1/test/expected/00_init.out000066400000000000000000000011721476621277000171240ustar00rootroot00000000000000/* * Must be executed before all regression test. */ -- Create the PostgreSQL extension CREATE EXTENSION pgtt; -- Set session_preload_libraries DO $$ BEGIN EXECUTE format('ALTER DATABASE %I SET session_preload_libraries = ''pgtt''', current_database()); END $$; -- Create a regular table with some rows CREATE TABLE source (id serial PRIMARY KEY, c2 varchar(50) UNIQUE NOT NULL, lbl varchar DEFAULT '-'); COMMENT ON TABLE source IS 'Table used to demonstrate GTT create as feature'; COMMENT ON COLUMN source.id IS 'auto generated column'; CREATE INDEX ON source(lbl); INSERT INTO source VALUES (1,'one'), (2,'two'),(3,'three'); pgtt-4.1/test/expected/01_oncommitdelete.out000066400000000000000000000066271476621277000212040ustar00rootroot00000000000000---- -- Regression test to Global Temporary Table implementation -- -- Test for GTT with ON COMMIT DELETE ROWS clause. -- -- Test drop of GTT when in use throwing error and effective -- when done in a separate session. -- ---- -- Create a GTT like table to test ON COMMIT DELETE ROWS CREATE GLOBAL TEMPORARY TABLE t_glob_temptable1 (id integer, lbl text) ON COMMIT DELETE ROWS; WARNING: GLOBAL is deprecated in temporary table creation LINE 1: CREATE GLOBAL TEMPORARY TABLE t_glob_temptable1 (id integer,... ^ -- Look at Global Temporary Table definition SELECT nspname, relname, preserved, code FROM pgtt_schema.pg_global_temp_tables; nspname | relname | preserved | code -------------+-------------------+-----------+---------------------- pgtt_schema | t_glob_temptable1 | f | id integer, lbl text (1 row) -- A "template" unlogged table should exists SELECT n.nspname, c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; nspname | relname -------------+------------------- pgtt_schema | t_glob_temptable1 (1 row) BEGIN; -- With the first insert some value in the temporary table INSERT INTO t_glob_temptable1 VALUES (1, 'One'); -- Look if we have two tables now SELECT regexp_replace(n.nspname, '\d+', 'x', 'g'), c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; regexp_replace | relname ----------------+------------------- pgtt_schema | t_glob_temptable1 pg_temp_x | t_glob_temptable1 (2 rows) -- Second insert, the temporary table exists INSERT INTO t_glob_temptable1 VALUES (2, 'Two'); -- Look at content of the template for Global Temporary Table, must be empty SET pgtt.enabled TO off; SELECT * FROM pgtt_schema.t_glob_temptable1; id | lbl ----+----- (0 rows) SET pgtt.enabled TO on; -- Look at content of the Global Temporary Table SELECT * FROM t_glob_temptable1; id | lbl ----+----- 1 | One 2 | Two (2 rows) COMMIT; -- No row must perstist after the commit SELECT * FROM t_glob_temptable1; id | lbl ----+----- (0 rows) -- With holdabe cursor the rows must remain BEGIN; INSERT INTO t_glob_temptable1 VALUES (1, 'One'); INSERT INTO t_glob_temptable1 VALUES (2, 'Two'); DECLARE curs1 CURSOR WITH HOLD FOR SELECT * FROM t_glob_temptable1; FETCH curs1; id | lbl ----+----- 1 | One (1 row) COMMIT; -- No row must perstist after the commit for the cursor FETCH curs1; id | lbl ----+----- 2 | Two (1 row) -- But not for a new select SELECT * FROM t_glob_temptable1; id | lbl ----+----- (0 rows) CLOSE curs1; -- Drop the global temporary table: fail because it is in use DROP TABLE t_glob_temptable1; ERROR: can not drop a GTT that is in use. -- Reconnect and drop it \c - - SHOW search_path; search_path -------------------- public,pgtt_schema (1 row) DROP TABLE t_glob_temptable1; VACUUM pg_class; SELECT pg_sleep(1); pg_sleep ---------- (1 row) -- Look at Global Temporary Table definition SELECT nspname, relname, preserved, code FROM pgtt_schema.pg_global_temp_tables; -- should be empty nspname | relname | preserved | code ---------+---------+-----------+------ (0 rows) -- The "template" unlogged table should not exists anymore SELECT n.nspname, c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; nspname | relname ---------+--------- (0 rows) pgtt-4.1/test/expected/02_oncommitpreserve.out000066400000000000000000000043541476621277000215710ustar00rootroot00000000000000---- -- Regression test to Global Temporary Table implementation -- -- Test for GTT with ON COMMIT PRESERVE ROWS clause. -- -- Test rereouting of truncate on the temporary table. -- ---- -- Create a GTT like table to test ON COMMIT PRESERVE ROWS CREATE GLOBAL TEMPORARY TABLE t_glob_temptable1 (id integer, lbl text) ON COMMIT PRESERVE ROWS; WARNING: GLOBAL is deprecated in temporary table creation LINE 1: CREATE GLOBAL TEMPORARY TABLE t_glob_temptable1 (id integer,... ^ -- Look at Global Temporary Table definition SELECT nspname, relname, preserved, code FROM pgtt_schema.pg_global_temp_tables; nspname | relname | preserved | code -------------+-------------------+-----------+---------------------- pgtt_schema | t_glob_temptable1 | t | id integer, lbl text (1 row) -- A "template" unlogged table should exists SELECT n.nspname, c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; nspname | relname -------------+------------------- pgtt_schema | t_glob_temptable1 (1 row) BEGIN; -- With the first insert some value in the temporary table INSERT INTO t_glob_temptable1 VALUES (1, 'One'); -- Look if we have two tables now SELECT regexp_replace(n.nspname, '\d+', 'x', 'g'), c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; regexp_replace | relname ----------------+------------------- pgtt_schema | t_glob_temptable1 pg_temp_x | t_glob_temptable1 (2 rows) -- Second insert, the temporary table exists INSERT INTO t_glob_temptable1 VALUES (2, 'Two'); -- Look at content of the template for Global Temporary Table, must be empty SET pgtt.enabled TO off; SELECT * FROM pgtt_schema.t_glob_temptable1; id | lbl ----+----- (0 rows) SET pgtt.enabled TO on; -- Look at content of the Global Temporary Table SELECT * FROM t_glob_temptable1; id | lbl ----+----- 1 | One 2 | Two (2 rows) COMMIT; SELECT * FROM t_glob_temptable1; id | lbl ----+----- 1 | One 2 | Two (2 rows) -- Truncate GTT TRUNCATE t_glob_temptable1; SELECT * FROM t_glob_temptable1; id | lbl ----+----- (0 rows) -- Reconnect and drop it \c - - -- Cleanup DROP TABLE t_glob_temptable1; pgtt-4.1/test/expected/03_createontruncate.out000066400000000000000000000056011476621277000215330ustar00rootroot00000000000000---- -- Regression test to Global Temporary Table implementation -- -- Test for TRUNCATE on GTT with temporary table first access in a transaction. -- The temporary table must not persist after the transaction rollback. -- ---- -- Create a GTT like table CREATE GLOBAL TEMPORARY TABLE t_glob_temptable1 (id integer, lbl text) ON COMMIT PRESERVE ROWS; WARNING: GLOBAL is deprecated in temporary table creation LINE 1: CREATE GLOBAL TEMPORARY TABLE t_glob_temptable1 (id integer,... ^ -- Look at Global Temporary Table definition SELECT nspname, relname, preserved, code FROM pgtt_schema.pg_global_temp_tables; nspname | relname | preserved | code -------------+-------------------+-----------+---------------------- pgtt_schema | t_glob_temptable1 | t | id integer, lbl text (1 row) -- A "template" unlogged table should exists SELECT n.nspname, c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; nspname | relname -------------+------------------- pgtt_schema | t_glob_temptable1 (1 row) BEGIN; -- Truncate first will not create the temporary table, it will -- be operated on the "template" table which will do nothing. TRUNCATE t_glob_temptable1; -- Look if we have two tables now SELECT n.nspname, c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; nspname | relname -------------+------------------- pgtt_schema | t_glob_temptable1 (1 row) -- Insert some value will create the temporary table INSERT INTO t_glob_temptable1 VALUES (1, 'One'); INSERT INTO t_glob_temptable1 VALUES (2, 'Two'); -- Look at content of the template for Global Temporary Table, must be empty SET pgtt.enabled TO off; SELECT * FROM pgtt_schema.t_glob_temptable1; id | lbl ----+----- (0 rows) SET pgtt.enabled TO on; -- Look at content of the Global Temporary Table SELECT * FROM t_glob_temptable1; id | lbl ----+----- 1 | One 2 | Two (2 rows) -- Now truncate the temporary table TRUNCATE t_glob_temptable1; -- Verify that there is no mo rows SELECT * FROM t_glob_temptable1; id | lbl ----+----- (0 rows) ROLLBACK; -- The "template" unlogged table exists, but the -- temporary table not because of the rollback SELECT n.nspname, c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; nspname | relname -------------+------------------- pgtt_schema | t_glob_temptable1 (1 row) -- Reconnect and drop it \c - - -- Cleanup DROP TABLE t_glob_temptable1; -- Reconnect to test locking, see #41 \c - - -- Create a GTT like table CREATE /*GLOBAL*/ TEMPORARY TABLE test_gtt (id int, lbl text); SELECT * FROM pgtt_schema.test_gtt; id | lbl ----+----- (0 rows) SELECT * FROM pgtt_schema.test_gtt; -- success id | lbl ----+----- (0 rows) \c - - -- Cleanup DROP TABLE test_gtt; pgtt-4.1/test/expected/04_rename.out000066400000000000000000000160141476621277000174350ustar00rootroot00000000000000---- -- Regression test to Global Temporary Table implementation -- -- Test on renaming GTT using ALTER TABLE ... RENAME TO ... -- -- Renaming a GGT when the temporary table has already been -- created is not allowed and must be done in a new session. -- ---- -- Create a GTT like table CREATE /*GLOBAL*/ TEMPORARY TABLE t_glob_temptable1 (id integer, lbl text) ON COMMIT PRESERVE ROWS; CREATE INDEX ON pgtt_schema.t_glob_temptable1 (id); CREATE INDEX ON pgtt_schema.t_glob_temptable1 (lbl); -- Look at Global Temporary Table definition SELECT nspname, relname, preserved, code FROM pgtt_schema.pg_global_temp_tables; nspname | relname | preserved | code -------------+-------------------+-----------+---------------------- pgtt_schema | t_glob_temptable1 | t | id integer, lbl text (1 row) -- A "template" unlogged table should exists SELECT a.attname, pg_catalog.format_type(a.atttypid, a.atttypmod), (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid, true) for 128) FROM pg_catalog.pg_attrdef d WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef), a.attnotnull, pg_catalog.col_description(a.attrelid, a.attnum) FROM pg_catalog.pg_attribute a WHERE a.attrelid = ( SELECT c.oid FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = 't_glob_temptable1' AND n.nspname = 'pgtt_schema' ) AND a.attnum > 0 AND NOT a.attisdropped ORDER BY a.attnum; attname | format_type | substring | attnotnull | col_description ---------+-------------+-----------+------------+----------------- id | integer | | f | lbl | text | | f | (2 rows) -- With indexes defined SELECT c2.relname, i.indisprimary, i.indisunique, pg_catalog.pg_get_indexdef(i.indexrelid, 0, true), pg_catalog.pg_get_constraintdef(con.oid, true), contype FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i LEFT JOIN pg_catalog.pg_constraint con ON (conrelid = i.indrelid AND conindid = i.indexrelid AND contype IN ('p','u','x')) WHERE c.oid = ( SELECT c.oid FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = 't_glob_temptable1' AND n.nspname = 'pgtt_schema' ) AND c.oid = i.indrelid AND i.indexrelid = c2.oid ORDER BY i.indisprimary DESC, c2.relname; relname | indisprimary | indisunique | pg_get_indexdef | pg_get_constraintdef | contype ---------------------------+--------------+-------------+-------------------------------------------------------------------------------+----------------------+--------- t_glob_temptable1_id_idx | f | f | CREATE INDEX t_glob_temptable1_id_idx ON t_glob_temptable1 USING btree (id) | | t_glob_temptable1_lbl_idx | f | f | CREATE INDEX t_glob_temptable1_lbl_idx ON t_glob_temptable1 USING btree (lbl) | | (2 rows) -- Rename the table ALTER TABLE t_glob_temptable1 RENAME TO t_glob_temptable2; -- Look at Global Temporary Table definition SELECT nspname, relname, preserved, code FROM pgtt_schema.pg_global_temp_tables; nspname | relname | preserved | code -------------+-------------------+-----------+---------------------- pgtt_schema | t_glob_temptable2 | t | id integer, lbl text (1 row) -- A "template" unlogged table should exists SELECT a.attname, pg_catalog.format_type(a.atttypid, a.atttypmod), (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid, true) for 128) FROM pg_catalog.pg_attrdef d WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef), a.attnotnull, pg_catalog.col_description(a.attrelid, a.attnum) FROM pg_catalog.pg_attribute a WHERE a.attrelid = ( SELECT c.oid FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = 't_glob_temptable2' AND n.nspname = 'pgtt_schema' ) AND a.attnum > 0 AND NOT a.attisdropped ORDER BY a.attnum; attname | format_type | substring | attnotnull | col_description ---------+-------------+-----------+------------+----------------- id | integer | | f | lbl | text | | f | (2 rows) -- With indexes still defined SELECT c2.relname, i.indisprimary, i.indisunique, pg_catalog.pg_get_indexdef(i.indexrelid, 0, true), pg_catalog.pg_get_constraintdef(con.oid, true), contype FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i LEFT JOIN pg_catalog.pg_constraint con ON (conrelid = i.indrelid AND conindid = i.indexrelid AND contype IN ('p','u','x')) WHERE c.oid = ( SELECT c.oid FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = 't_glob_temptable2' AND n.nspname = 'pgtt_schema' ) AND c.oid = i.indrelid AND i.indexrelid = c2.oid ORDER BY i.indisprimary DESC, c2.relname; relname | indisprimary | indisunique | pg_get_indexdef | pg_get_constraintdef | contype ---------------------------+--------------+-------------+-------------------------------------------------------------------------------+----------------------+--------- t_glob_temptable1_id_idx | f | f | CREATE INDEX t_glob_temptable1_id_idx ON t_glob_temptable2 USING btree (id) | | t_glob_temptable1_lbl_idx | f | f | CREATE INDEX t_glob_temptable1_lbl_idx ON t_glob_temptable2 USING btree (lbl) | | (2 rows) -- With the first insert a temporary table is created and the row inseterd SET pgtt.enabled TO on; INSERT INTO t_glob_temptable2 VALUES (1, 'One'); -- Look if we have two tables now SELECT regexp_replace(n.nspname, '\d+', 'x', 'g'), c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable2'; regexp_replace | relname ----------------+------------------- pgtt_schema | t_glob_temptable2 pg_temp_x | t_glob_temptable2 (2 rows) -- The table doesn't exist anymore SET pgtt.enabled TO off; \d t_glob_temptable1; SET pgtt.enabled TO on; INSERT INTO t_glob_temptable2 VALUES (2, 'two'); SELECT * FROM t_glob_temptable2 WHERE id = 2; id | lbl ----+----- 2 | two (1 row) -- Rename the table when the temporary table has already been created is not allowed ALTER TABLE t_glob_temptable2 RENAME TO t_glob_temptable1; ERROR: a temporary table has been created and is active, can not rename the GTT table in this session. \c - - ALTER TABLE t_glob_temptable2 RENAME TO t_glob_temptable1; -- Look if the renaming is effective SELECT n.nspname, c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; nspname | relname -------------+------------------- pgtt_schema | t_glob_temptable1 (1 row) -- Reconnect and drop it \c - - -- Cleanup DROP TABLE t_glob_temptable1; pgtt-4.1/test/expected/05_useindex.out000066400000000000000000000151071476621277000200150ustar00rootroot00000000000000---- -- Regression test to Global Temporary Table implementation -- -- Test for GTT with index, the temporary table must have the -- index too. -- ---- -- Create a GTT like table CREATE /*GLOBAL*/ TEMPORARY TABLE t_glob_temptable1 (id integer, lbl text) ON COMMIT PRESERVE ROWS; SET pgtt.enabled TO off; CREATE INDEX ON pgtt_schema.t_glob_temptable1 (id); CREATE INDEX ON pgtt_schema.t_glob_temptable1 (lbl); -- Look at Global Temporary Table definition SELECT nspname, relname, preserved, code FROM pgtt_schema.pg_global_temp_tables; nspname | relname | preserved | code -------------+-------------------+-----------+---------------------- pgtt_schema | t_glob_temptable1 | t | id integer, lbl text (1 row) -- A "template" unlogged table should exists SELECT a.attname, pg_catalog.format_type(a.atttypid, a.atttypmod), (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid, true) for 128) FROM pg_catalog.pg_attrdef d WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef), a.attnotnull, pg_catalog.col_description(a.attrelid, a.attnum) FROM pg_catalog.pg_attribute a WHERE a.attrelid = ( SELECT c.oid FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = 't_glob_temptable1' AND n.nspname = 'pgtt_schema' ) AND a.attnum > 0 AND NOT a.attisdropped ORDER BY a.attnum; attname | format_type | substring | attnotnull | col_description ---------+-------------+-----------+------------+----------------- id | integer | | f | lbl | text | | f | (2 rows) -- With indexes still defined SELECT c2.relname, i.indisprimary, i.indisunique, pg_catalog.pg_get_indexdef(i.indexrelid, 0, true), pg_catalog.pg_get_constraintdef(con.oid, true), contype FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i LEFT JOIN pg_catalog.pg_constraint con ON (conrelid = i.indrelid AND conindid = i.indexrelid AND contype IN ('p','u','x')) WHERE c.oid = ( SELECT c.oid FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = 't_glob_temptable1' AND n.nspname = 'pgtt_schema' ) AND c.oid = i.indrelid AND i.indexrelid = c2.oid ORDER BY i.indisprimary DESC, c2.relname; relname | indisprimary | indisunique | pg_get_indexdef | pg_get_constraintdef | contype ---------------------------+--------------+-------------+-------------------------------------------------------------------------------+----------------------+--------- t_glob_temptable1_id_idx | f | f | CREATE INDEX t_glob_temptable1_id_idx ON t_glob_temptable1 USING btree (id) | | t_glob_temptable1_lbl_idx | f | f | CREATE INDEX t_glob_temptable1_lbl_idx ON t_glob_temptable1 USING btree (lbl) | | (2 rows) SET pgtt.enabled TO on; -- With the first insert some value in the temporary table INSERT INTO t_glob_temptable1 VALUES (1, 'One'); -- Look if we have two tables now SELECT regexp_replace(n.nspname, '\d+', 'x', 'g'), c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; regexp_replace | relname ----------------+------------------- pgtt_schema | t_glob_temptable1 pg_temp_x | t_glob_temptable1 (2 rows) -- and that the temporary table has the indexes too SELECT tablename, indexname FROM pg_indexes WHERE tablename = 't_glob_temptable1' and schemaname LIKE 'pg_temp_%' ORDER BY tablename, indexname; tablename | indexname -------------------+--------------------------- t_glob_temptable1 | t_glob_temptable1_id_idx t_glob_temptable1 | t_glob_temptable1_lbl_idx (2 rows) INSERT INTO t_glob_temptable1 VALUES (2, 'two'); -- Verify that the index is used SET enable_bitmapscan TO off; EXPLAIN (COSTS OFF) SELECT * FROM t_glob_temptable1 WHERE id = 2; QUERY PLAN ---------------------------------------------------------------- Index Scan using t_glob_temptable1_id_idx on t_glob_temptable1 Index Cond: (id = 2) (2 rows) -- Reconnect and drop it \c - - -- Cleanup DROP TABLE t_glob_temptable1; -- Create a GTT like table CREATE /*GLOBAL*/ TEMPORARY TABLE t_glob_temptable2 (id integer, lbl text) ON COMMIT PRESERVE ROWS; CREATE INDEX ON t_glob_temptable2 (id); SELECT * FROM t_glob_temptable2; id | lbl ----+----- (0 rows) -- Must complain that the GTT is in use CREATE INDEX ON t_glob_temptable2 (lbl); ERROR: a temporary table has been created and is active, can not add an index on the GTT table in this session. SELECT pg_catalog.pg_get_indexdef(i.indexrelid, 0, true) FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i LEFT JOIN pg_catalog.pg_constraint con ON (conrelid = i.indrelid AND conindid = i.indexrelid AND contype IN ('p','u','x')) WHERE c.oid = 'pgtt_schema.t_glob_temptable2'::regclass::oid AND c.oid = i.indrelid AND i.indexrelid = c2.oid ORDER BY i.indisprimary DESC, c2.relname; pg_get_indexdef ----------------------------------------------------------------------------------------- CREATE INDEX t_glob_temptable2_id_idx ON pgtt_schema.t_glob_temptable2 USING btree (id) (1 row) -- Check that we do not break LIKE ... USING INDEXES CREATE TABLE tb_with_index (id integer PRIMARY KEY, lbl varchar); CREATE INDEX ON tb_with_index(lbl); CREATE TEMPORARY TABLE temptb_with_index (LIKE tb_with_index INCLUDING INDEXES); SELECT c2.relname, pg_catalog.pg_get_indexdef(i.indexrelid, 0, true), pg_catalog.pg_get_constraintdef(con.oid, true) FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i LEFT JOIN pg_catalog.pg_constraint con ON (conrelid = i.indrelid AND conindid = i.indexrelid AND contype IN ('p','u','x')) WHERE c.oid = 'temptb_with_index'::regclass AND c.oid = i.indrelid AND i.indexrelid = c2.oid ORDER BY i.indisprimary DESC, c2.relname; relname | pg_get_indexdef | pg_get_constraintdef ---------------------------+----------------------------------------------------------------------------------+---------------------- temptb_with_index_pkey | CREATE UNIQUE INDEX temptb_with_index_pkey ON temptb_with_index USING btree (id) | PRIMARY KEY (id) temptb_with_index_lbl_idx | CREATE INDEX temptb_with_index_lbl_idx ON temptb_with_index USING btree (lbl) | (2 rows) \c - - DROP TABLE t_glob_temptable2; pgtt-4.1/test/expected/06_createas.out000066400000000000000000000046061476621277000177630ustar00rootroot00000000000000---- -- Regression test to Global Temporary Table implementation -- -- Test for GTT with TABLE ... AS clause. -- ---- -- Create a GTT like table to test ON COMMIT PRESERVE ROWS CREATE GLOBAL TEMPORARY TABLE t_glob_temptable1 AS SELECT * FROM source WITH DATA; WARNING: GLOBAL is deprecated in temporary table creation LINE 1: CREATE GLOBAL TEMPORARY TABLE t_glob_temptable1 AS SELECT * ... ^ -- Look at Global Temporary Table definition SELECT nspname, relname, preserved, code FROM pgtt_schema.pg_global_temp_tables; nspname | relname | preserved | code -------------+-------------------+-----------+--------------------------- pgtt_schema | t_glob_temptable1 | t | AS SELECT * FROM source (1 row) -- A "template" unlogged table should exists as well as -- the temporary table as we have used WITH DATA SELECT regexp_replace(n.nspname, '\d+', 'x', 'g'), c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; regexp_replace | relname ----------------+------------------- pgtt_schema | t_glob_temptable1 pg_temp_x | t_glob_temptable1 (2 rows) -- Look at content of the template for Global Temporary Table, must be empty SET pgtt.enabled TO off; SELECT * FROM pgtt_schema.t_glob_temptable1; id | c2 | lbl ----+-------+----- 1 | one | - 2 | two | - 3 | three | - (3 rows) SET pgtt.enabled TO on; -- Look at the temporary table itself, it must have the rows SELECT * FROM t_glob_temptable1 ORDER BY id; id | c2 | lbl ----+-------+----- 1 | one | - 2 | two | - 3 | three | - (3 rows) BEGIN; -- With a new row INSERT INTO t_glob_temptable1 VALUES (4, 'four'); -- Look at content of the template for Global Temporary Table, must be empty SET pgtt.enabled TO off; SELECT * FROM pgtt_schema.t_glob_temptable1; id | c2 | lbl ----+-------+----- 1 | one | - 2 | two | - 3 | three | - (3 rows) SET pgtt.enabled TO on; -- Look at content of the Global Temporary Table SELECT * FROM t_glob_temptable1 ORDER BY id; id | c2 | lbl ----+-------+----- 1 | one | - 2 | two | - 3 | three | - 4 | four | (4 rows) COMMIT; SELECT * FROM t_glob_temptable1 ORDER BY id; id | c2 | lbl ----+-------+----- 1 | one | - 2 | two | - 3 | three | - 4 | four | (4 rows) -- Reconnect and drop it \c - - -- Cleanup DROP TABLE t_glob_temptable1; pgtt-4.1/test/expected/07_createlike.out000066400000000000000000000167031476621277000203060ustar00rootroot00000000000000---- -- Regression test to Global Temporary Table implementation -- -- Test for GTT with TABLE ... (LIKE ...) clause. -- ---- -- Create a GTT like table to test ON COMMIT PRESERVE ROWS CREATE GLOBAL TEMPORARY TABLE t_glob_temptable1 ( LIKE source INCLUDING ALL ) ON COMMIT PRESERVE ROWS; WARNING: GLOBAL is deprecated in temporary table creation LINE 1: CREATE GLOBAL TEMPORARY TABLE t_glob_temptable1 ( ^ -- Look at table description SELECT a.attname, pg_catalog.format_type(a.atttypid, a.atttypmod), (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid, true) for 128) FROM pg_catalog.pg_attrdef d WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef), a.attnotnull, pg_catalog.col_description(a.attrelid, a.attnum) FROM pg_catalog.pg_attribute a WHERE a.attrelid = ( SELECT c.oid FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = 't_glob_temptable1' AND n.nspname = 'pgtt_schema' ) AND a.attnum > 0 AND NOT a.attisdropped ORDER BY a.attnum; attname | format_type | substring | attnotnull | col_description ---------+-----------------------+------------------------------------+------------+----------------------- id | integer | nextval('source_id_seq'::regclass) | t | auto generated column c2 | character varying(50) | | t | lbl | character varying | '-'::character varying | f | (3 rows) -- With indexes defined SELECT c2.relname, i.indisprimary, i.indisunique, pg_catalog.pg_get_indexdef(i.indexrelid, 0, true), pg_catalog.pg_get_constraintdef(con.oid, true), contype FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i LEFT JOIN pg_catalog.pg_constraint con ON (conrelid = i.indrelid AND conindid = i.indexrelid AND contype IN ('p','u','x')) WHERE c.oid = ( SELECT c.oid FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = 't_glob_temptable1' AND n.nspname = 'pgtt_schema' ) AND c.oid = i.indrelid AND i.indexrelid = c2.oid ORDER BY i.indisprimary DESC, c2.relname; relname | indisprimary | indisunique | pg_get_indexdef | pg_get_constraintdef | contype ---------------------------+--------------+-------------+------------------------------------------------------------------------------------+----------------------+--------- t_glob_temptable1_pkey | t | t | CREATE UNIQUE INDEX t_glob_temptable1_pkey ON t_glob_temptable1 USING btree (id) | PRIMARY KEY (id) | p t_glob_temptable1_c2_key | f | t | CREATE UNIQUE INDEX t_glob_temptable1_c2_key ON t_glob_temptable1 USING btree (c2) | UNIQUE (c2) | u t_glob_temptable1_lbl_idx | f | f | CREATE INDEX t_glob_temptable1_lbl_idx ON t_glob_temptable1 USING btree (lbl) | | (3 rows) -- Look at Global Temporary Table definition SELECT nspname, relname, preserved, code FROM pgtt_schema.pg_global_temp_tables; nspname | relname | preserved | code -------------+-------------------+-----------+----------------------- pgtt_schema | t_glob_temptable1 | t | + | | | LIKE source + | | | INCLUDING ALL+ | | | (1 row) -- A "template" unlogged table should exists SELECT n.nspname, c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; nspname | relname -------------+------------------- pgtt_schema | t_glob_temptable1 (1 row) BEGIN; -- With the first insert some value in the temporary table INSERT INTO t_glob_temptable1 VALUES (1, 'One'); -- Look at temp table description SELECT a.attname, pg_catalog.format_type(a.atttypid, a.atttypmod), (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid, true) for 128) FROM pg_catalog.pg_attrdef d WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef), a.attnotnull, pg_catalog.col_description(a.attrelid, a.attnum) FROM pg_catalog.pg_attribute a WHERE a.attrelid = ( SELECT c.oid FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = 't_glob_temptable1' AND n.nspname LIKE 'pg_temp_%' ) AND a.attnum > 0 AND NOT a.attisdropped ORDER BY a.attnum; attname | format_type | substring | attnotnull | col_description ---------+-----------------------+------------------------------------+------------+----------------------- id | integer | nextval('source_id_seq'::regclass) | t | auto generated column c2 | character varying(50) | | t | lbl | character varying | '-'::character varying | f | (3 rows) SELECT c2.relname, i.indisprimary, i.indisunique, pg_catalog.pg_get_indexdef(i.indexrelid, 0, true), pg_catalog.pg_get_constraintdef(con.oid, true), contype FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i LEFT JOIN pg_catalog.pg_constraint con ON (conrelid = i.indrelid AND conindid = i.indexrelid AND contype IN ('p','u','x')) WHERE c.oid = ( SELECT c.oid FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = 't_glob_temptable1' AND n.nspname LIKE 'pg_temp_%' ) AND c.oid = i.indrelid AND i.indexrelid = c2.oid ORDER BY i.indisprimary DESC, c2.relname; relname | indisprimary | indisunique | pg_get_indexdef | pg_get_constraintdef | contype ---------------------------+--------------+-------------+------------------------------------------------------------------------------------+----------------------+--------- t_glob_temptable1_pkey | t | t | CREATE UNIQUE INDEX t_glob_temptable1_pkey ON t_glob_temptable1 USING btree (id) | PRIMARY KEY (id) | p t_glob_temptable1_c2_key | f | t | CREATE UNIQUE INDEX t_glob_temptable1_c2_key ON t_glob_temptable1 USING btree (c2) | UNIQUE (c2) | u t_glob_temptable1_lbl_idx | f | f | CREATE INDEX t_glob_temptable1_lbl_idx ON t_glob_temptable1 USING btree (lbl) | | (3 rows) -- Look if we have two tables now SELECT regexp_replace(n.nspname, '\d+', 'x', 'g'), c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; regexp_replace | relname ----------------+------------------- pgtt_schema | t_glob_temptable1 pg_temp_x | t_glob_temptable1 (2 rows) -- Look at content of the template for Global Temporary Table, must be empty SET pgtt.enabled TO off; SELECT * FROM pgtt_schema.t_glob_temptable1; id | c2 | lbl ----+----+----- (0 rows) SET pgtt.enabled TO on; -- Look at content of the Global Temporary Table SELECT * FROM t_glob_temptable1 ORDER BY id; id | c2 | lbl ----+-----+----- 1 | One | - (1 row) COMMIT; SELECT * FROM t_glob_temptable1 ORDER BY id; id | c2 | lbl ----+-----+----- 1 | One | - (1 row) -- Reconnect and drop it \c - - -- Cleanup DROP TABLE t_glob_temptable1; pgtt-4.1/test/expected/08_plplgsql.out000066400000000000000000000122001476621277000200210ustar00rootroot00000000000000---- -- Regression test to Global Temporary Table implementation -- -- Test for GTT defined inside a PLPGSQL function. -- ---- CREATE OR REPLACE FUNCTION test_temp_table () RETURNS integer AS $$ DECLARE nrows integer; BEGIN CREATE /*GLOBAL*/ TEMPORARY TABLE t_glob_temptable1(id int, lbl text) ON COMMIT PRESERVE ROWS; INSERT INTO t_glob_temptable1 (id, lbl) SELECT i, md5(i::text) FROM generate_series(1, 10) i; SELECT count(*) INTO nrows FROM t_glob_temptable1 ; RETURN nrows; END; $$ LANGUAGE plpgsql SECURITY DEFINER; -- Look at Global Temporary Table definition: none SELECT nspname, relname, preserved, code FROM pgtt_schema.pg_global_temp_tables; nspname | relname | preserved | code ---------+---------+-----------+------ (0 rows) -- Call the function, must returns 10 rows SELECT test_temp_table(); test_temp_table ----------------- 10 (1 row) -- Look at Global Temporary Table definition: table exists SELECT nspname, relname, preserved, code FROM pgtt_schema.pg_global_temp_tables; nspname | relname | preserved | code -------------+-------------------+-----------+------------------ pgtt_schema | t_glob_temptable1 | t | id int, lbl text (1 row) -- Look if the temporary table exists outside the function call SELECT regexp_replace(n.nspname, '\d+', 'x', 'g'), c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; regexp_replace | relname ----------------+------------------- pgtt_schema | t_glob_temptable1 pg_temp_x | t_glob_temptable1 (2 rows) -- A "template" unlogged table should exists SET pgtt.enabled TO off; SELECT a.attname, pg_catalog.format_type(a.atttypid, a.atttypmod), (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid, true) for 128) FROM pg_catalog.pg_attrdef d WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef), a.attnotnull, pg_catalog.col_description(a.attrelid, a.attnum) FROM pg_catalog.pg_attribute a WHERE a.attrelid = ( SELECT c.oid FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = 't_glob_temptable1' AND n.nspname = 'pgtt_schema' ) AND a.attnum > 0 AND NOT a.attisdropped ORDER BY a.attnum; attname | format_type | substring | attnotnull | col_description ---------+-------------+-----------+------------+----------------- id | integer | | f | lbl | text | | f | (2 rows) -- Get rows from the template table SELECT * FROM pgtt_schema.t_glob_temptable1; id | lbl ----+----- (0 rows) -- Get rows from the temporary table SET pgtt.enabled TO on; SELECT * FROM t_glob_temptable1; id | lbl ----+---------------------------------- 1 | c4ca4238a0b923820dcc509a6f75849b 2 | c81e728d9d4c2f636f067f89cc14862c 3 | eccbc87e4b5ce2fe28308fd9f2a7baf3 4 | a87ff679a2f3e71d9181a67b7542122c 5 | e4da3b7fbbce2345d7772b0674a318d5 6 | 1679091c5a880faf6fb5e6087eb1b2dc 7 | 8f14e45fceea167a5a36dedd4bea2543 8 | c9f0f895fb98ab9159f51fd0297e236d 9 | 45c48cce2e2d7fbdea1afc51c7c6ad26 10 | d3d9446802a44259755d38e6d163e820 (10 rows) -- Reconnect without dropping the global temporary table \c - - SET pgtt.enabled TO off; VACUUM pg_class; SELECT pg_sleep(1); pg_sleep ---------- (1 row) -- Verify that only the temporary table have been dropped -- Only the "template" unlogged table should exists SELECT regexp_replace(n.nspname, '\d+', 'x', 'g'), c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; regexp_replace | relname ----------------+------------------- pgtt_schema | t_glob_temptable1 (1 row) -- Look at Global Temporary Table definition, the table must be present SELECT nspname, relname, preserved, code FROM pgtt_schema.pg_global_temp_tables; nspname | relname | preserved | code -------------+-------------------+-----------+------------------ pgtt_schema | t_glob_temptable1 | t | id int, lbl text (1 row) SET pgtt.enabled TO on; -- Call the function a second time - must fail the table already exists SELECT test_temp_table(); ERROR: relation "t_glob_temptable1" already exists CONTEXT: SQL statement "CREATE UNLOGGED TABLE pgtt_schema.t_glob_temptable1 (id int, lbl text)" SQL statement "CREATE /*GLOBAL*/ TEMPORARY TABLE t_glob_temptable1(id int, lbl text) ON COMMIT PRESERVE ROWS" PL/pgSQL function test_temp_table() line 6 at SQL statement -- Look at temporary table content, must be empty after the reconnect and function failure SELECT * FROM t_glob_temptable1; id | lbl ----+----- (0 rows) -- Now the "template" unlogged table should exists as well as the temporary table SELECT regexp_replace(n.nspname, '\d+', 'x', 'g'), c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; regexp_replace | relname ----------------+------------------- pgtt_schema | t_glob_temptable1 pg_temp_x | t_glob_temptable1 (2 rows) -- Reconnect and drop it \c - - -- Cleanup DROP TABLE t_glob_temptable1; DROP FUNCTION test_temp_table(); pgtt-4.1/test/expected/09_transaction.out000066400000000000000000000052011476621277000205140ustar00rootroot00000000000000---- -- Regression test to Global Temporary Table implementation -- -- Test for transaction manamgement on GTT. -- -- Test that the creation a GTT in rollbacked transaction -- will not preserve it. -- ---- BEGIN; -- Register the Global temporary table in a transaction CREATE /*GLOBAL*/ TEMPORARY TABLE t_glob_temptable1 (id integer, lbl text) ON COMMIT PRESERVE ROWS; -- Look at Global Temporary Table definition SELECT nspname, relname, preserved, code FROM pgtt_schema.pg_global_temp_tables; nspname | relname | preserved | code -------------+-------------------+-----------+---------------------- pgtt_schema | t_glob_temptable1 | t | id integer, lbl text (1 row) -- A "template" unlogged table should exists SELECT n.nspname, c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; nspname | relname -------------+------------------- pgtt_schema | t_glob_temptable1 (1 row) -- Insert some value will create the temporary table INSERT INTO t_glob_temptable1 VALUES (1, 'One'); INSERT INTO t_glob_temptable1 VALUES (2, 'Two'); -- Look at content of the template for Global Temporary Table, must be empty SET pgtt.enabled TO off; SELECT * FROM pgtt_schema.t_glob_temptable1; id | lbl ----+----- (0 rows) SET pgtt.enabled TO on; -- Look at content of the Global Temporary Table SELECT * FROM t_glob_temptable1; id | lbl ----+----- 1 | One 2 | Two (2 rows) ROLLBACK; -- The GTT must not exists SELECT * FROM t_glob_temptable1; ERROR: relation "t_glob_temptable1" does not exist LINE 1: SELECT * FROM t_glob_temptable1; ^ -- Return nothing SELECT n.nspname, c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; nspname | relname ---------+--------- (0 rows) -- Register the Global temporary table outside the transaction CREATE /*GLOBAL*/ TEMPORARY TABLE t_glob_temptable1 (id integer, lbl text) ON COMMIT PRESERVE ROWS; BEGIN; -- Drop the GTT DROP TABLE t_glob_temptable1; ROLLBACK; -- Insert some value will create the temporary table INSERT INTO t_glob_temptable1 VALUES (1, 'One'); INSERT INTO t_glob_temptable1 VALUES (2, 'Two'); -- The GTT must not exists SELECT * FROM t_glob_temptable1; id | lbl ----+----- 1 | One 2 | Two (2 rows) -- Both tables muste exists SELECT n.nspname, c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; nspname | relname -------------+------------------- pgtt_schema | t_glob_temptable1 (1 row) -- Reconnect and drop it \c - - -- Cleanup DROP TABLE t_glob_temptable1; pgtt-4.1/test/expected/10_foreignkey.out000066400000000000000000000015201476621277000203210ustar00rootroot00000000000000---- -- Regression test to Global Temporary Table implementation -- -- Test for unsupported foreign keys on GTT. -- ---- -- Import the library -- LOAD 'pgtt'; -- Must throw ERROR: attempt to create referential integrity constraint on temporary table. CREATE /*GLOBAL*/ TEMPORARY TABLE t2 (c1 integer, FOREIGN KEY (c1) REFERENCES source (id)); ERROR: attempt to create referential integrity constraint on global temporary table BEGIN; CREATE /*GLOBAL*/ TEMPORARY TABLE t2 (c1 integer); -- Must throw ERROR: attempt to create referential integrity constraint on temporary table. ALTER TABLE t2 ADD FOREIGN KEY (c1) REFERENCES source (id); ERROR: attempt to create referential integrity constraint on global temporary table ROLLBACK; BEGIN; -- Must be valid statement CREATE TABLE t3 (c1 integer, FOREIGN KEY (c1) REFERENCES source (id)); ROLLBACK; pgtt-4.1/test/expected/11_after_error.out000066400000000000000000000071161476621277000205010ustar00rootroot00000000000000-- Import the library -- LOAD 'pgtt'; -- Create a GTT like table to test ON COMMIT DELETE ROWS CREATE GLOBAL TEMPORARY TABLE t_glob_temptable1 (id integer, lbl text) ON COMMIT DELETE ROWS; WARNING: GLOBAL is deprecated in temporary table creation LINE 1: CREATE GLOBAL TEMPORARY TABLE t_glob_temptable1 (id integer,... ^ -- Look at Global Temporary Table definition SELECT nspname, relname, preserved, code FROM pgtt_schema.pg_global_temp_tables; nspname | relname | preserved | code -------------+-------------------+-----------+---------------------- pgtt_schema | t_glob_temptable1 | f | id integer, lbl text (1 row) -- A "template" unlogged table should exists SELECT n.nspname, c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; nspname | relname -------------+------------------- pgtt_schema | t_glob_temptable1 (1 row) BEGIN; -- With the first insert some value in the temporary table INSERT INTO t_glob_temptable1 VALUES (1, 'One'); -- Look if we have two tables now SELECT regexp_replace(n.nspname, '\d+', 'x', 'g'), c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; regexp_replace | relname ----------------+------------------- pgtt_schema | t_glob_temptable1 pg_temp_x | t_glob_temptable1 (2 rows) -- Verify the registering of the temporary table SELECT nspname, relname, preserved, code FROM pgtt_schema.pg_global_temp_tables; nspname | relname | preserved | code -------------+-------------------+-----------+---------------------- pgtt_schema | t_glob_temptable1 | f | id integer, lbl text (1 row) -- Second insert failure INSERT INTO t_glob_temptable1 VALUES (2, two); ERROR: column "two" does not exist LINE 1: INSERT INTO t_glob_temptable1 VALUES (2, two); ^ ROLLBACK; -- Look if we have two tables now SELECT regexp_replace(n.nspname, '\d+', 'x', 'g'), c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; regexp_replace | relname ----------------+------------------- pgtt_schema | t_glob_temptable1 (1 row) -- Insert a new row BEGIN; -- With the first insert some value in the temporary table -- Should not return an error that table doesn't exists INSERT INTO t_glob_temptable1 VALUES (2, 'Two'); -- Look if we have two tables now SELECT regexp_replace(n.nspname, '\d+', 'x', 'g'), c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; regexp_replace | relname ----------------+------------------- pgtt_schema | t_glob_temptable1 pg_temp_x | t_glob_temptable1 (2 rows) -- Verify the insert SELECT * FROM t_glob_temptable1; id | lbl ----+----- 2 | Two (1 row) COMMIT; -- Reconnect and drop the GTT \c - - -- LOAD 'pgtt'; SHOW search_path; search_path -------------------- public,pgtt_schema (1 row) DROP TABLE t_glob_temptable1; VACUUM pg_class; SELECT pg_sleep(1); pg_sleep ---------- (1 row) -- Look at Global Temporary Table definition SELECT nspname, relname, preserved, code FROM pgtt_schema.pg_global_temp_tables; -- should be empty nspname | relname | preserved | code ---------+---------+-----------+------ (0 rows) -- The "template" unlogged table should not exists anymore SELECT n.nspname, c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; nspname | relname ---------+--------- (0 rows) pgtt-4.1/test/expected/12_droptable.out000066400000000000000000000032131476621277000201360ustar00rootroot00000000000000---- -- Regression test to Global Temporary Table implementation -- -- Test on dropping regular table when the extension is loaded -- ---- -- Import the library -- LOAD 'pgtt'; -- Create a GTT like table CREATE /*GLOBAL*/ TEMPORARY TABLE t_glob_temptable1 (id integer, lbl text) ON COMMIT PRESERVE ROWS; -- Look at Global Temporary Table definition SELECT nspname, relname, preserved, code FROM pgtt_schema.pg_global_temp_tables; nspname | relname | preserved | code -------------+-------------------+-----------+---------------------- pgtt_schema | t_glob_temptable1 | t | id integer, lbl text (1 row) -- A "template" unlogged table should exists SELECT a.attname, pg_catalog.format_type(a.atttypid, a.atttypmod), (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid, true) for 128) FROM pg_catalog.pg_attrdef d WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef), a.attnotnull, pg_catalog.col_description(a.attrelid, a.attnum) FROM pg_catalog.pg_attribute a WHERE a.attrelid = ( SELECT c.oid FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = 't_glob_temptable1' AND n.nspname = 'pgtt_schema' ) AND a.attnum > 0 AND NOT a.attisdropped ORDER BY a.attnum; attname | format_type | substring | attnotnull | col_description ---------+-------------+-----------+------------+----------------- id | integer | | f | lbl | text | | f | (2 rows) -- Create and drop a regular table CREATE TABLE t1 (id integer); DROP TABLE t1; -- Cleanup DROP TABLE t_glob_temptable1; pgtt-4.1/test/expected/privilege.out000066400000000000000000000012331476621277000176460ustar00rootroot00000000000000CREATE ROLE CREATE EXTENSION GRANT DO You are now connected to database "gtt_privilege" as user "pgtt_user1". search_path -------------------- public,pgtt_schema (1 row) CREATE TABLE nspname | relname | preserved | code -------------+-------------------+-----------+---------------------- pgtt_schema | t_glob_temptable1 | t | id integer, lbl text (1 row) INSERT 0 1 INSERT 0 1 nspname | relname -------------+------------------- pgtt_schema | t_glob_temptable1 pg_temp_4 | t_glob_temptable1 (2 rows) SET id | lbl ----+----- (0 rows) SET id | lbl ----+----- 1 | One 2 | two (2 rows) pgtt-4.1/test/privilege.sql000077500000000000000000000036261476621277000160500ustar00rootroot00000000000000---- -- Regression test to Global Temporary Table implementation -- -- This test is not part of the regression tests run with -- make check install because it need manual changes. -- -- Test for extension privilege to use non-superuser role. -- For this test the extension must have been registered as -- a plugin to allow simple user to use LOAD of the extension. -- See Installation instruction in documentation for details. -- Then execute: -- -- createdb gtt_privilege -- LANG=C psql -d gtt_privilege -f test/privilege.sql > results/privilege.out 2>&1 -- diff results/privilege.out test/expected/privilege.out -- dropdb gtt_privilege -- dropuser pgtt_user1 -- ---- -- As superuser CREATE ROLE pgtt_user1 LOGIN; CREATE EXTENSION IF NOT EXISTS pgtt; GRANT ALL ON SCHEMA pgtt_schema TO pgtt_user1; -- Set session_preload_libraries DO $$ BEGIN --EXECUTE format('ALTER DATABASE %I SET session_preload_libraries = ''$libdir/plugins/pgtt''', current_database()); EXECUTE format('ALTER DATABASE %I SET session_preload_libraries = ''pgtt''', current_database()); END $$; \c - pgtt_user1 -- Import the library -- LOAD '$libdir/plugins/pgtt'; SHOW search_path; -- Create a GTT like table with ON COMMIT PRESERVE ROWS CREATE /*GLOBAL*/ TEMPORARY TABLE t_glob_temptable1 (id integer, lbl text) ON COMMIT PRESERVE ROWS; -- Look at Global Temporary Table definition SELECT nspname, relname, preserved, code FROM pgtt_schema.pg_global_temp_tables; -- With the first insert some value in the temporary table INSERT INTO t_glob_temptable1 VALUES (1, 'One'); INSERT INTO t_glob_temptable1 VALUES (2, 'two'); -- Look if we have two tables now SELECT n.nspname, c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; SET pgtt.enabled TO off; SELECT * FROM pgtt_schema.t_glob_temptable1; SET pgtt.enabled TO on; SELECT * FROM t_glob_temptable1; pgtt-4.1/test/sql/000077500000000000000000000000001476621277000141265ustar00rootroot00000000000000pgtt-4.1/test/sql/00_init.sql000066400000000000000000000011751476621277000161150ustar00rootroot00000000000000/* * Must be executed before all regression test. */ -- Create the PostgreSQL extension CREATE EXTENSION pgtt; -- Set session_preload_libraries DO $$ BEGIN EXECUTE format('ALTER DATABASE %I SET session_preload_libraries = ''pgtt''', current_database()); END $$; -- Create a regular table with some rows CREATE TABLE source (id serial PRIMARY KEY, c2 varchar(50) UNIQUE NOT NULL, lbl varchar DEFAULT '-'); COMMENT ON TABLE source IS 'Table used to demonstrate GTT create as feature'; COMMENT ON COLUMN source.id IS 'auto generated column'; CREATE INDEX ON source(lbl); INSERT INTO source VALUES (1,'one'), (2,'two'),(3,'three'); pgtt-4.1/test/sql/01_oncommitdelete.sql000066400000000000000000000044621476621277000201650ustar00rootroot00000000000000---- -- Regression test to Global Temporary Table implementation -- -- Test for GTT with ON COMMIT DELETE ROWS clause. -- -- Test drop of GTT when in use throwing error and effective -- when done in a separate session. -- ---- -- Create a GTT like table to test ON COMMIT DELETE ROWS CREATE GLOBAL TEMPORARY TABLE t_glob_temptable1 (id integer, lbl text) ON COMMIT DELETE ROWS; -- Look at Global Temporary Table definition SELECT nspname, relname, preserved, code FROM pgtt_schema.pg_global_temp_tables; -- A "template" unlogged table should exists SELECT n.nspname, c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; BEGIN; -- With the first insert some value in the temporary table INSERT INTO t_glob_temptable1 VALUES (1, 'One'); -- Look if we have two tables now SELECT regexp_replace(n.nspname, '\d+', 'x', 'g'), c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; -- Second insert, the temporary table exists INSERT INTO t_glob_temptable1 VALUES (2, 'Two'); -- Look at content of the template for Global Temporary Table, must be empty SET pgtt.enabled TO off; SELECT * FROM pgtt_schema.t_glob_temptable1; SET pgtt.enabled TO on; -- Look at content of the Global Temporary Table SELECT * FROM t_glob_temptable1; COMMIT; -- No row must perstist after the commit SELECT * FROM t_glob_temptable1; -- With holdabe cursor the rows must remain BEGIN; INSERT INTO t_glob_temptable1 VALUES (1, 'One'); INSERT INTO t_glob_temptable1 VALUES (2, 'Two'); DECLARE curs1 CURSOR WITH HOLD FOR SELECT * FROM t_glob_temptable1; FETCH curs1; COMMIT; -- No row must perstist after the commit for the cursor FETCH curs1; -- But not for a new select SELECT * FROM t_glob_temptable1; CLOSE curs1; -- Drop the global temporary table: fail because it is in use DROP TABLE t_glob_temptable1; -- Reconnect and drop it \c - - SHOW search_path; DROP TABLE t_glob_temptable1; VACUUM pg_class; SELECT pg_sleep(1); -- Look at Global Temporary Table definition SELECT nspname, relname, preserved, code FROM pgtt_schema.pg_global_temp_tables; -- should be empty -- The "template" unlogged table should not exists anymore SELECT n.nspname, c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; pgtt-4.1/test/sql/02_oncommitpreserve.sql000066400000000000000000000027351476621277000205600ustar00rootroot00000000000000---- -- Regression test to Global Temporary Table implementation -- -- Test for GTT with ON COMMIT PRESERVE ROWS clause. -- -- Test rereouting of truncate on the temporary table. -- ---- -- Create a GTT like table to test ON COMMIT PRESERVE ROWS CREATE GLOBAL TEMPORARY TABLE t_glob_temptable1 (id integer, lbl text) ON COMMIT PRESERVE ROWS; -- Look at Global Temporary Table definition SELECT nspname, relname, preserved, code FROM pgtt_schema.pg_global_temp_tables; -- A "template" unlogged table should exists SELECT n.nspname, c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; BEGIN; -- With the first insert some value in the temporary table INSERT INTO t_glob_temptable1 VALUES (1, 'One'); -- Look if we have two tables now SELECT regexp_replace(n.nspname, '\d+', 'x', 'g'), c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; -- Second insert, the temporary table exists INSERT INTO t_glob_temptable1 VALUES (2, 'Two'); -- Look at content of the template for Global Temporary Table, must be empty SET pgtt.enabled TO off; SELECT * FROM pgtt_schema.t_glob_temptable1; SET pgtt.enabled TO on; -- Look at content of the Global Temporary Table SELECT * FROM t_glob_temptable1; COMMIT; SELECT * FROM t_glob_temptable1; -- Truncate GTT TRUNCATE t_glob_temptable1; SELECT * FROM t_glob_temptable1; -- Reconnect and drop it \c - - -- Cleanup DROP TABLE t_glob_temptable1; pgtt-4.1/test/sql/03_createontruncate.sql000066400000000000000000000040531476621277000205210ustar00rootroot00000000000000---- -- Regression test to Global Temporary Table implementation -- -- Test for TRUNCATE on GTT with temporary table first access in a transaction. -- The temporary table must not persist after the transaction rollback. -- ---- -- Create a GTT like table CREATE GLOBAL TEMPORARY TABLE t_glob_temptable1 (id integer, lbl text) ON COMMIT PRESERVE ROWS; -- Look at Global Temporary Table definition SELECT nspname, relname, preserved, code FROM pgtt_schema.pg_global_temp_tables; -- A "template" unlogged table should exists SELECT n.nspname, c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; BEGIN; -- Truncate first will not create the temporary table, it will -- be operated on the "template" table which will do nothing. TRUNCATE t_glob_temptable1; -- Look if we have two tables now SELECT n.nspname, c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; -- Insert some value will create the temporary table INSERT INTO t_glob_temptable1 VALUES (1, 'One'); INSERT INTO t_glob_temptable1 VALUES (2, 'Two'); -- Look at content of the template for Global Temporary Table, must be empty SET pgtt.enabled TO off; SELECT * FROM pgtt_schema.t_glob_temptable1; SET pgtt.enabled TO on; -- Look at content of the Global Temporary Table SELECT * FROM t_glob_temptable1; -- Now truncate the temporary table TRUNCATE t_glob_temptable1; -- Verify that there is no mo rows SELECT * FROM t_glob_temptable1; ROLLBACK; -- The "template" unlogged table exists, but the -- temporary table not because of the rollback SELECT n.nspname, c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; -- Reconnect and drop it \c - - -- Cleanup DROP TABLE t_glob_temptable1; -- Reconnect to test locking, see #41 \c - - -- Create a GTT like table CREATE /*GLOBAL*/ TEMPORARY TABLE test_gtt (id int, lbl text); SELECT * FROM pgtt_schema.test_gtt; SELECT * FROM pgtt_schema.test_gtt; -- success \c - - -- Cleanup DROP TABLE test_gtt; pgtt-4.1/test/sql/04_rename.sql000066400000000000000000000106461476621277000164300ustar00rootroot00000000000000---- -- Regression test to Global Temporary Table implementation -- -- Test on renaming GTT using ALTER TABLE ... RENAME TO ... -- -- Renaming a GGT when the temporary table has already been -- created is not allowed and must be done in a new session. -- ---- -- Create a GTT like table CREATE /*GLOBAL*/ TEMPORARY TABLE t_glob_temptable1 (id integer, lbl text) ON COMMIT PRESERVE ROWS; CREATE INDEX ON pgtt_schema.t_glob_temptable1 (id); CREATE INDEX ON pgtt_schema.t_glob_temptable1 (lbl); -- Look at Global Temporary Table definition SELECT nspname, relname, preserved, code FROM pgtt_schema.pg_global_temp_tables; -- A "template" unlogged table should exists SELECT a.attname, pg_catalog.format_type(a.atttypid, a.atttypmod), (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid, true) for 128) FROM pg_catalog.pg_attrdef d WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef), a.attnotnull, pg_catalog.col_description(a.attrelid, a.attnum) FROM pg_catalog.pg_attribute a WHERE a.attrelid = ( SELECT c.oid FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = 't_glob_temptable1' AND n.nspname = 'pgtt_schema' ) AND a.attnum > 0 AND NOT a.attisdropped ORDER BY a.attnum; -- With indexes defined SELECT c2.relname, i.indisprimary, i.indisunique, pg_catalog.pg_get_indexdef(i.indexrelid, 0, true), pg_catalog.pg_get_constraintdef(con.oid, true), contype FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i LEFT JOIN pg_catalog.pg_constraint con ON (conrelid = i.indrelid AND conindid = i.indexrelid AND contype IN ('p','u','x')) WHERE c.oid = ( SELECT c.oid FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = 't_glob_temptable1' AND n.nspname = 'pgtt_schema' ) AND c.oid = i.indrelid AND i.indexrelid = c2.oid ORDER BY i.indisprimary DESC, c2.relname; -- Rename the table ALTER TABLE t_glob_temptable1 RENAME TO t_glob_temptable2; -- Look at Global Temporary Table definition SELECT nspname, relname, preserved, code FROM pgtt_schema.pg_global_temp_tables; -- A "template" unlogged table should exists SELECT a.attname, pg_catalog.format_type(a.atttypid, a.atttypmod), (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid, true) for 128) FROM pg_catalog.pg_attrdef d WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef), a.attnotnull, pg_catalog.col_description(a.attrelid, a.attnum) FROM pg_catalog.pg_attribute a WHERE a.attrelid = ( SELECT c.oid FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = 't_glob_temptable2' AND n.nspname = 'pgtt_schema' ) AND a.attnum > 0 AND NOT a.attisdropped ORDER BY a.attnum; -- With indexes still defined SELECT c2.relname, i.indisprimary, i.indisunique, pg_catalog.pg_get_indexdef(i.indexrelid, 0, true), pg_catalog.pg_get_constraintdef(con.oid, true), contype FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i LEFT JOIN pg_catalog.pg_constraint con ON (conrelid = i.indrelid AND conindid = i.indexrelid AND contype IN ('p','u','x')) WHERE c.oid = ( SELECT c.oid FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = 't_glob_temptable2' AND n.nspname = 'pgtt_schema' ) AND c.oid = i.indrelid AND i.indexrelid = c2.oid ORDER BY i.indisprimary DESC, c2.relname; -- With the first insert a temporary table is created and the row inseterd SET pgtt.enabled TO on; INSERT INTO t_glob_temptable2 VALUES (1, 'One'); -- Look if we have two tables now SELECT regexp_replace(n.nspname, '\d+', 'x', 'g'), c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable2'; -- The table doesn't exist anymore SET pgtt.enabled TO off; \d t_glob_temptable1; SET pgtt.enabled TO on; INSERT INTO t_glob_temptable2 VALUES (2, 'two'); SELECT * FROM t_glob_temptable2 WHERE id = 2; -- Rename the table when the temporary table has already been created is not allowed ALTER TABLE t_glob_temptable2 RENAME TO t_glob_temptable1; \c - - ALTER TABLE t_glob_temptable2 RENAME TO t_glob_temptable1; -- Look if the renaming is effective SELECT n.nspname, c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; -- Reconnect and drop it \c - - -- Cleanup DROP TABLE t_glob_temptable1; pgtt-4.1/test/sql/05_useindex.sql000066400000000000000000000077651476621277000170160ustar00rootroot00000000000000---- -- Regression test to Global Temporary Table implementation -- -- Test for GTT with index, the temporary table must have the -- index too. -- ---- -- Create a GTT like table CREATE /*GLOBAL*/ TEMPORARY TABLE t_glob_temptable1 (id integer, lbl text) ON COMMIT PRESERVE ROWS; SET pgtt.enabled TO off; CREATE INDEX ON pgtt_schema.t_glob_temptable1 (id); CREATE INDEX ON pgtt_schema.t_glob_temptable1 (lbl); -- Look at Global Temporary Table definition SELECT nspname, relname, preserved, code FROM pgtt_schema.pg_global_temp_tables; -- A "template" unlogged table should exists SELECT a.attname, pg_catalog.format_type(a.atttypid, a.atttypmod), (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid, true) for 128) FROM pg_catalog.pg_attrdef d WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef), a.attnotnull, pg_catalog.col_description(a.attrelid, a.attnum) FROM pg_catalog.pg_attribute a WHERE a.attrelid = ( SELECT c.oid FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = 't_glob_temptable1' AND n.nspname = 'pgtt_schema' ) AND a.attnum > 0 AND NOT a.attisdropped ORDER BY a.attnum; -- With indexes still defined SELECT c2.relname, i.indisprimary, i.indisunique, pg_catalog.pg_get_indexdef(i.indexrelid, 0, true), pg_catalog.pg_get_constraintdef(con.oid, true), contype FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i LEFT JOIN pg_catalog.pg_constraint con ON (conrelid = i.indrelid AND conindid = i.indexrelid AND contype IN ('p','u','x')) WHERE c.oid = ( SELECT c.oid FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = 't_glob_temptable1' AND n.nspname = 'pgtt_schema' ) AND c.oid = i.indrelid AND i.indexrelid = c2.oid ORDER BY i.indisprimary DESC, c2.relname; SET pgtt.enabled TO on; -- With the first insert some value in the temporary table INSERT INTO t_glob_temptable1 VALUES (1, 'One'); -- Look if we have two tables now SELECT regexp_replace(n.nspname, '\d+', 'x', 'g'), c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; -- and that the temporary table has the indexes too SELECT tablename, indexname FROM pg_indexes WHERE tablename = 't_glob_temptable1' and schemaname LIKE 'pg_temp_%' ORDER BY tablename, indexname; INSERT INTO t_glob_temptable1 VALUES (2, 'two'); -- Verify that the index is used SET enable_bitmapscan TO off; EXPLAIN (COSTS OFF) SELECT * FROM t_glob_temptable1 WHERE id = 2; -- Reconnect and drop it \c - - -- Cleanup DROP TABLE t_glob_temptable1; -- Create a GTT like table CREATE /*GLOBAL*/ TEMPORARY TABLE t_glob_temptable2 (id integer, lbl text) ON COMMIT PRESERVE ROWS; CREATE INDEX ON t_glob_temptable2 (id); SELECT * FROM t_glob_temptable2; -- Must complain that the GTT is in use CREATE INDEX ON t_glob_temptable2 (lbl); SELECT pg_catalog.pg_get_indexdef(i.indexrelid, 0, true) FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i LEFT JOIN pg_catalog.pg_constraint con ON (conrelid = i.indrelid AND conindid = i.indexrelid AND contype IN ('p','u','x')) WHERE c.oid = 'pgtt_schema.t_glob_temptable2'::regclass::oid AND c.oid = i.indrelid AND i.indexrelid = c2.oid ORDER BY i.indisprimary DESC, c2.relname; -- Check that we do not break LIKE ... USING INDEXES CREATE TABLE tb_with_index (id integer PRIMARY KEY, lbl varchar); CREATE INDEX ON tb_with_index(lbl); CREATE TEMPORARY TABLE temptb_with_index (LIKE tb_with_index INCLUDING INDEXES); SELECT c2.relname, pg_catalog.pg_get_indexdef(i.indexrelid, 0, true), pg_catalog.pg_get_constraintdef(con.oid, true) FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i LEFT JOIN pg_catalog.pg_constraint con ON (conrelid = i.indrelid AND conindid = i.indexrelid AND contype IN ('p','u','x')) WHERE c.oid = 'temptb_with_index'::regclass AND c.oid = i.indrelid AND i.indexrelid = c2.oid ORDER BY i.indisprimary DESC, c2.relname; \c - - DROP TABLE t_glob_temptable2; pgtt-4.1/test/sql/06_createas.sql000066400000000000000000000025771476621277000167560ustar00rootroot00000000000000---- -- Regression test to Global Temporary Table implementation -- -- Test for GTT with TABLE ... AS clause. -- ---- -- Create a GTT like table to test ON COMMIT PRESERVE ROWS CREATE GLOBAL TEMPORARY TABLE t_glob_temptable1 AS SELECT * FROM source WITH DATA; -- Look at Global Temporary Table definition SELECT nspname, relname, preserved, code FROM pgtt_schema.pg_global_temp_tables; -- A "template" unlogged table should exists as well as -- the temporary table as we have used WITH DATA SELECT regexp_replace(n.nspname, '\d+', 'x', 'g'), c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; -- Look at content of the template for Global Temporary Table, must be empty SET pgtt.enabled TO off; SELECT * FROM pgtt_schema.t_glob_temptable1; SET pgtt.enabled TO on; -- Look at the temporary table itself, it must have the rows SELECT * FROM t_glob_temptable1 ORDER BY id; BEGIN; -- With a new row INSERT INTO t_glob_temptable1 VALUES (4, 'four'); -- Look at content of the template for Global Temporary Table, must be empty SET pgtt.enabled TO off; SELECT * FROM pgtt_schema.t_glob_temptable1; SET pgtt.enabled TO on; -- Look at content of the Global Temporary Table SELECT * FROM t_glob_temptable1 ORDER BY id; COMMIT; SELECT * FROM t_glob_temptable1 ORDER BY id; -- Reconnect and drop it \c - - -- Cleanup DROP TABLE t_glob_temptable1; pgtt-4.1/test/sql/07_createlike.sql000066400000000000000000000075371476621277000173010ustar00rootroot00000000000000---- -- Regression test to Global Temporary Table implementation -- -- Test for GTT with TABLE ... (LIKE ...) clause. -- ---- -- Create a GTT like table to test ON COMMIT PRESERVE ROWS CREATE GLOBAL TEMPORARY TABLE t_glob_temptable1 ( LIKE source INCLUDING ALL ) ON COMMIT PRESERVE ROWS; -- Look at table description SELECT a.attname, pg_catalog.format_type(a.atttypid, a.atttypmod), (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid, true) for 128) FROM pg_catalog.pg_attrdef d WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef), a.attnotnull, pg_catalog.col_description(a.attrelid, a.attnum) FROM pg_catalog.pg_attribute a WHERE a.attrelid = ( SELECT c.oid FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = 't_glob_temptable1' AND n.nspname = 'pgtt_schema' ) AND a.attnum > 0 AND NOT a.attisdropped ORDER BY a.attnum; -- With indexes defined SELECT c2.relname, i.indisprimary, i.indisunique, pg_catalog.pg_get_indexdef(i.indexrelid, 0, true), pg_catalog.pg_get_constraintdef(con.oid, true), contype FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i LEFT JOIN pg_catalog.pg_constraint con ON (conrelid = i.indrelid AND conindid = i.indexrelid AND contype IN ('p','u','x')) WHERE c.oid = ( SELECT c.oid FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = 't_glob_temptable1' AND n.nspname = 'pgtt_schema' ) AND c.oid = i.indrelid AND i.indexrelid = c2.oid ORDER BY i.indisprimary DESC, c2.relname; -- Look at Global Temporary Table definition SELECT nspname, relname, preserved, code FROM pgtt_schema.pg_global_temp_tables; -- A "template" unlogged table should exists SELECT n.nspname, c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; BEGIN; -- With the first insert some value in the temporary table INSERT INTO t_glob_temptable1 VALUES (1, 'One'); -- Look at temp table description SELECT a.attname, pg_catalog.format_type(a.atttypid, a.atttypmod), (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid, true) for 128) FROM pg_catalog.pg_attrdef d WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef), a.attnotnull, pg_catalog.col_description(a.attrelid, a.attnum) FROM pg_catalog.pg_attribute a WHERE a.attrelid = ( SELECT c.oid FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = 't_glob_temptable1' AND n.nspname LIKE 'pg_temp_%' ) AND a.attnum > 0 AND NOT a.attisdropped ORDER BY a.attnum; SELECT c2.relname, i.indisprimary, i.indisunique, pg_catalog.pg_get_indexdef(i.indexrelid, 0, true), pg_catalog.pg_get_constraintdef(con.oid, true), contype FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i LEFT JOIN pg_catalog.pg_constraint con ON (conrelid = i.indrelid AND conindid = i.indexrelid AND contype IN ('p','u','x')) WHERE c.oid = ( SELECT c.oid FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = 't_glob_temptable1' AND n.nspname LIKE 'pg_temp_%' ) AND c.oid = i.indrelid AND i.indexrelid = c2.oid ORDER BY i.indisprimary DESC, c2.relname; -- Look if we have two tables now SELECT regexp_replace(n.nspname, '\d+', 'x', 'g'), c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; -- Look at content of the template for Global Temporary Table, must be empty SET pgtt.enabled TO off; SELECT * FROM pgtt_schema.t_glob_temptable1; SET pgtt.enabled TO on; -- Look at content of the Global Temporary Table SELECT * FROM t_glob_temptable1 ORDER BY id; COMMIT; SELECT * FROM t_glob_temptable1 ORDER BY id; -- Reconnect and drop it \c - - -- Cleanup DROP TABLE t_glob_temptable1; pgtt-4.1/test/sql/08_plplgsql.sql000066400000000000000000000061011476621277000170120ustar00rootroot00000000000000---- -- Regression test to Global Temporary Table implementation -- -- Test for GTT defined inside a PLPGSQL function. -- ---- CREATE OR REPLACE FUNCTION test_temp_table () RETURNS integer AS $$ DECLARE nrows integer; BEGIN CREATE /*GLOBAL*/ TEMPORARY TABLE t_glob_temptable1(id int, lbl text) ON COMMIT PRESERVE ROWS; INSERT INTO t_glob_temptable1 (id, lbl) SELECT i, md5(i::text) FROM generate_series(1, 10) i; SELECT count(*) INTO nrows FROM t_glob_temptable1 ; RETURN nrows; END; $$ LANGUAGE plpgsql SECURITY DEFINER; -- Look at Global Temporary Table definition: none SELECT nspname, relname, preserved, code FROM pgtt_schema.pg_global_temp_tables; -- Call the function, must returns 10 rows SELECT test_temp_table(); -- Look at Global Temporary Table definition: table exists SELECT nspname, relname, preserved, code FROM pgtt_schema.pg_global_temp_tables; -- Look if the temporary table exists outside the function call SELECT regexp_replace(n.nspname, '\d+', 'x', 'g'), c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; -- A "template" unlogged table should exists SET pgtt.enabled TO off; SELECT a.attname, pg_catalog.format_type(a.atttypid, a.atttypmod), (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid, true) for 128) FROM pg_catalog.pg_attrdef d WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef), a.attnotnull, pg_catalog.col_description(a.attrelid, a.attnum) FROM pg_catalog.pg_attribute a WHERE a.attrelid = ( SELECT c.oid FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = 't_glob_temptable1' AND n.nspname = 'pgtt_schema' ) AND a.attnum > 0 AND NOT a.attisdropped ORDER BY a.attnum; -- Get rows from the template table SELECT * FROM pgtt_schema.t_glob_temptable1; -- Get rows from the temporary table SET pgtt.enabled TO on; SELECT * FROM t_glob_temptable1; -- Reconnect without dropping the global temporary table \c - - SET pgtt.enabled TO off; VACUUM pg_class; SELECT pg_sleep(1); -- Verify that only the temporary table have been dropped -- Only the "template" unlogged table should exists SELECT regexp_replace(n.nspname, '\d+', 'x', 'g'), c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; -- Look at Global Temporary Table definition, the table must be present SELECT nspname, relname, preserved, code FROM pgtt_schema.pg_global_temp_tables; SET pgtt.enabled TO on; -- Call the function a second time - must fail the table already exists SELECT test_temp_table(); -- Look at temporary table content, must be empty after the reconnect and function failure SELECT * FROM t_glob_temptable1; -- Now the "template" unlogged table should exists as well as the temporary table SELECT regexp_replace(n.nspname, '\d+', 'x', 'g'), c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; -- Reconnect and drop it \c - - -- Cleanup DROP TABLE t_glob_temptable1; DROP FUNCTION test_temp_table(); pgtt-4.1/test/sql/09_transaction.sql000066400000000000000000000036621476621277000175130ustar00rootroot00000000000000---- -- Regression test to Global Temporary Table implementation -- -- Test for transaction manamgement on GTT. -- -- Test that the creation a GTT in rollbacked transaction -- will not preserve it. -- ---- BEGIN; -- Register the Global temporary table in a transaction CREATE /*GLOBAL*/ TEMPORARY TABLE t_glob_temptable1 (id integer, lbl text) ON COMMIT PRESERVE ROWS; -- Look at Global Temporary Table definition SELECT nspname, relname, preserved, code FROM pgtt_schema.pg_global_temp_tables; -- A "template" unlogged table should exists SELECT n.nspname, c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; -- Insert some value will create the temporary table INSERT INTO t_glob_temptable1 VALUES (1, 'One'); INSERT INTO t_glob_temptable1 VALUES (2, 'Two'); -- Look at content of the template for Global Temporary Table, must be empty SET pgtt.enabled TO off; SELECT * FROM pgtt_schema.t_glob_temptable1; SET pgtt.enabled TO on; -- Look at content of the Global Temporary Table SELECT * FROM t_glob_temptable1; ROLLBACK; -- The GTT must not exists SELECT * FROM t_glob_temptable1; -- Return nothing SELECT n.nspname, c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; -- Register the Global temporary table outside the transaction CREATE /*GLOBAL*/ TEMPORARY TABLE t_glob_temptable1 (id integer, lbl text) ON COMMIT PRESERVE ROWS; BEGIN; -- Drop the GTT DROP TABLE t_glob_temptable1; ROLLBACK; -- Insert some value will create the temporary table INSERT INTO t_glob_temptable1 VALUES (1, 'One'); INSERT INTO t_glob_temptable1 VALUES (2, 'Two'); -- The GTT must not exists SELECT * FROM t_glob_temptable1; -- Both tables muste exists SELECT n.nspname, c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; -- Reconnect and drop it \c - - -- Cleanup DROP TABLE t_glob_temptable1; pgtt-4.1/test/sql/10_foreignkey.sql000066400000000000000000000012461476621277000173140ustar00rootroot00000000000000---- -- Regression test to Global Temporary Table implementation -- -- Test for unsupported foreign keys on GTT. -- ---- -- Import the library -- LOAD 'pgtt'; -- Must throw ERROR: attempt to create referential integrity constraint on temporary table. CREATE /*GLOBAL*/ TEMPORARY TABLE t2 (c1 integer, FOREIGN KEY (c1) REFERENCES source (id)); BEGIN; CREATE /*GLOBAL*/ TEMPORARY TABLE t2 (c1 integer); -- Must throw ERROR: attempt to create referential integrity constraint on temporary table. ALTER TABLE t2 ADD FOREIGN KEY (c1) REFERENCES source (id); ROLLBACK; BEGIN; -- Must be valid statement CREATE TABLE t3 (c1 integer, FOREIGN KEY (c1) REFERENCES source (id)); ROLLBACK; pgtt-4.1/test/sql/11_after_error.sql000066400000000000000000000041341476621277000174640ustar00rootroot00000000000000-- Import the library -- LOAD 'pgtt'; -- Create a GTT like table to test ON COMMIT DELETE ROWS CREATE GLOBAL TEMPORARY TABLE t_glob_temptable1 (id integer, lbl text) ON COMMIT DELETE ROWS; -- Look at Global Temporary Table definition SELECT nspname, relname, preserved, code FROM pgtt_schema.pg_global_temp_tables; -- A "template" unlogged table should exists SELECT n.nspname, c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; BEGIN; -- With the first insert some value in the temporary table INSERT INTO t_glob_temptable1 VALUES (1, 'One'); -- Look if we have two tables now SELECT regexp_replace(n.nspname, '\d+', 'x', 'g'), c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; -- Verify the registering of the temporary table SELECT nspname, relname, preserved, code FROM pgtt_schema.pg_global_temp_tables; -- Second insert failure INSERT INTO t_glob_temptable1 VALUES (2, two); ROLLBACK; -- Look if we have two tables now SELECT regexp_replace(n.nspname, '\d+', 'x', 'g'), c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; -- Insert a new row BEGIN; -- With the first insert some value in the temporary table -- Should not return an error that table doesn't exists INSERT INTO t_glob_temptable1 VALUES (2, 'Two'); -- Look if we have two tables now SELECT regexp_replace(n.nspname, '\d+', 'x', 'g'), c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; -- Verify the insert SELECT * FROM t_glob_temptable1; COMMIT; -- Reconnect and drop the GTT \c - - -- LOAD 'pgtt'; SHOW search_path; DROP TABLE t_glob_temptable1; VACUUM pg_class; SELECT pg_sleep(1); -- Look at Global Temporary Table definition SELECT nspname, relname, preserved, code FROM pgtt_schema.pg_global_temp_tables; -- should be empty -- The "template" unlogged table should not exists anymore SELECT n.nspname, c.relname FROM pg_class c JOIN pg_namespace n ON (c.relnamespace=n.oid) WHERE relname = 't_glob_temptable1'; pgtt-4.1/test/sql/12_droptable.sql000066400000000000000000000023051476621277000171250ustar00rootroot00000000000000---- -- Regression test to Global Temporary Table implementation -- -- Test on dropping regular table when the extension is loaded -- ---- -- Import the library -- LOAD 'pgtt'; -- Create a GTT like table CREATE /*GLOBAL*/ TEMPORARY TABLE t_glob_temptable1 (id integer, lbl text) ON COMMIT PRESERVE ROWS; -- Look at Global Temporary Table definition SELECT nspname, relname, preserved, code FROM pgtt_schema.pg_global_temp_tables; -- A "template" unlogged table should exists SELECT a.attname, pg_catalog.format_type(a.atttypid, a.atttypmod), (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid, true) for 128) FROM pg_catalog.pg_attrdef d WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef), a.attnotnull, pg_catalog.col_description(a.attrelid, a.attnum) FROM pg_catalog.pg_attribute a WHERE a.attrelid = ( SELECT c.oid FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = 't_glob_temptable1' AND n.nspname = 'pgtt_schema' ) AND a.attnum > 0 AND NOT a.attisdropped ORDER BY a.attnum; -- Create and drop a regular table CREATE TABLE t1 (id integer); DROP TABLE t1; -- Cleanup DROP TABLE t_glob_temptable1; pgtt-4.1/updates/000077500000000000000000000000001476621277000140155ustar00rootroot00000000000000pgtt-4.1/updates/pgtt--2.0.0--2.1.0.sql000066400000000000000000000004761476621277000167440ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION pgtt UPDATE" to load this file. \quit -- check the functions bodies as creation time, enabled by default SET LOCAL check_function_bodies = on ; -- make sure of client encoding SET LOCAL client_encoding = 'UTF8'; pgtt-4.1/updates/pgtt--2.1.0--2.2.0.sql000066400000000000000000000004761476621277000167460ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION pgtt UPDATE" to load this file. \quit -- check the functions bodies as creation time, enabled by default SET LOCAL check_function_bodies = on ; -- make sure of client encoding SET LOCAL client_encoding = 'UTF8'; pgtt-4.1/updates/pgtt--2.10.0--3.0.0.sql000066400000000000000000000004761476621277000170250ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION pgtt UPDATE" to load this file. \quit -- check the functions bodies as creation time, enabled by default SET LOCAL check_function_bodies = on ; -- make sure of client encoding SET LOCAL client_encoding = 'UTF8'; pgtt-4.1/updates/pgtt--2.2.0--2.3.0.sql000066400000000000000000000004761476621277000167500ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION pgtt UPDATE" to load this file. \quit -- check the functions bodies as creation time, enabled by default SET LOCAL check_function_bodies = on ; -- make sure of client encoding SET LOCAL client_encoding = 'UTF8'; pgtt-4.1/updates/pgtt--2.3.0--2.4.0.sql000066400000000000000000000004761476621277000167520ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION pgtt UPDATE" to load this file. \quit -- check the functions bodies as creation time, enabled by default SET LOCAL check_function_bodies = on ; -- make sure of client encoding SET LOCAL client_encoding = 'UTF8'; pgtt-4.1/updates/pgtt--2.4.0--2.5.0.sql000066400000000000000000000004761476621277000167540ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION pgtt UPDATE" to load this file. \quit -- check the functions bodies as creation time, enabled by default SET LOCAL check_function_bodies = on ; -- make sure of client encoding SET LOCAL client_encoding = 'UTF8'; pgtt-4.1/updates/pgtt--2.5.0--2.6.0.sql000066400000000000000000000004761476621277000167560ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION pgtt UPDATE" to load this file. \quit -- check the functions bodies as creation time, enabled by default SET LOCAL check_function_bodies = on ; -- make sure of client encoding SET LOCAL client_encoding = 'UTF8'; pgtt-4.1/updates/pgtt--2.6.0--2.7.0.sql000066400000000000000000000004761476621277000167600ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION pgtt UPDATE" to load this file. \quit -- check the functions bodies as creation time, enabled by default SET LOCAL check_function_bodies = on ; -- make sure of client encoding SET LOCAL client_encoding = 'UTF8'; pgtt-4.1/updates/pgtt--2.7.0--2.8.0.sql000066400000000000000000000004761476621277000167620ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION pgtt UPDATE" to load this file. \quit -- check the functions bodies as creation time, enabled by default SET LOCAL check_function_bodies = on ; -- make sure of client encoding SET LOCAL client_encoding = 'UTF8'; pgtt-4.1/updates/pgtt--2.8.0--2.9.0.sql000066400000000000000000000004761476621277000167640ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION pgtt UPDATE" to load this file. \quit -- check the functions bodies as creation time, enabled by default SET LOCAL check_function_bodies = on ; -- make sure of client encoding SET LOCAL client_encoding = 'UTF8'; pgtt-4.1/updates/pgtt--2.9.0--2.10.0.sql000066400000000000000000000004761476621277000170350ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION pgtt UPDATE" to load this file. \quit -- check the functions bodies as creation time, enabled by default SET LOCAL check_function_bodies = on ; -- make sure of client encoding SET LOCAL client_encoding = 'UTF8'; pgtt-4.1/updates/pgtt--3.0.0--3.1.0.sql000066400000000000000000000004761476621277000167460ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION pgtt UPDATE" to load this file. \quit -- check the functions bodies as creation time, enabled by default SET LOCAL check_function_bodies = on ; -- make sure of client encoding SET LOCAL client_encoding = 'UTF8'; pgtt-4.1/updates/pgtt--3.1.0--3.2.0.sql000066400000000000000000000004761476621277000167500ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION pgtt UPDATE" to load this file. \quit -- check the functions bodies as creation time, enabled by default SET LOCAL check_function_bodies = on ; -- make sure of client encoding SET LOCAL client_encoding = 'UTF8'; pgtt-4.1/updates/pgtt--3.2.0--4.0.0.sql000066400000000000000000000004761476621277000167500ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION pgtt UPDATE" to load this file. \quit -- check the functions bodies as creation time, enabled by default SET LOCAL check_function_bodies = on ; -- make sure of client encoding SET LOCAL client_encoding = 'UTF8';