{"id":17288243,"url":"https://github.com/yallie/pg_global_temp_tables","last_synced_at":"2025-04-14T11:07:49.574Z","repository":{"id":149267267,"uuid":"81509386","full_name":"yallie/pg_global_temp_tables","owner":"yallie","description":"Oracle-style global temporary tables for PostgreSQL","archived":false,"fork":false,"pushed_at":"2019-01-15T20:41:29.000Z","size":39,"stargazers_count":18,"open_issues_count":2,"forks_count":5,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-14T11:07:43.405Z","etag":null,"topics":["compatibility","emulation","migration","oracle","postgres","postgresql","table","temporary"],"latest_commit_sha":null,"homepage":"https://www.codeproject.com/Articles/1176045/Oracle-style-global-temporary-tables-for-PostgreSQ","language":"PLpgSQL","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yallie.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2017-02-10T00:36:49.000Z","updated_at":"2024-04-24T03:12:44.000Z","dependencies_parsed_at":null,"dependency_job_id":"2d5ba3e5-3037-4cf0-bb97-27565248c3f4","html_url":"https://github.com/yallie/pg_global_temp_tables","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yallie%2Fpg_global_temp_tables","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yallie%2Fpg_global_temp_tables/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yallie%2Fpg_global_temp_tables/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yallie%2Fpg_global_temp_tables/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yallie","download_url":"https://codeload.github.com/yallie/pg_global_temp_tables/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248868769,"owners_count":21174758,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["compatibility","emulation","migration","oracle","postgres","postgresql","table","temporary"],"created_at":"2024-10-15T10:26:33.876Z","updated_at":"2025-04-14T11:07:49.550Z","avatar_url":"https://github.com/yallie.png","language":"PLpgSQL","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Oracle-style global temporary tables for PostgreSQL\n\n[![Build status](https://ci.appveyor.com/api/projects/status/ogjn6bc2avxg66jl?svg=true)](https://ci.appveyor.com/project/yallie/pg-global-temp-tables)\n\nPostgreSQL semantic of temporary tables is substantially different from that of Oracle.\n\n* Oracle temporary tables are permanent, so their structure is static and visible to all users, and the content is temporary.\n* PostgreSQL temporary tables are dropped either at the end of a session or at the end of a transaction. In PostgreSQL, the structure and the content of a temp table is local for a database backend (a process) which created the table.\n* Oracle temporary tables are always defined within a user-specified schema.\n* PostgreSQL temporary tables cannot be defined within user's schema, they always use a special temporary schema instead.\n\nPorting large Oracle application relying on many temporary tables can be difficult:\n\n* Oracle queries may use `schema.table` notation for temporary tables, which is not allowed in Postgres. We can omit `schema` if it's the same as the current user, but we are still likely to have queries that reference other schemata.\n* Postgres requires that each temporary table is created within the same session or transaction before it is accessed.\n\nIt gets worse if the application is supposed to work with both Postgres and Oracle, so we can't just fix the queries and litter the code with lots of `create temporary table` statements.\n\n# Enter pg_global_temp_tables\n\nThis library creates Oracle-style temporary tables in Postgres, so that Oracle queries work without any syntactic changes. Check it out:\n\n```sql\n-- Oracle application (1)\n-- \n-- Temporary table is created like this:\n-- create global temporary table temp_idlist(id number(18)) \n\ninsert into myapp.temp_idlist(id) values(:p);\n\nselect u.login \nfrom myapp.users u\njoin myapp.temp_idlist t on u.id = t.id;\n\n-- PostgreSQL application (2) using ordinary temporary tables\n--\n-- Temporary table is created in the same transaction \n\ncreate temporary table if not exists temp_idlist(id bigint);\ninsert into temp_idlist(id) values(:p);\n\nselect u.login \nfrom myapp.users u\njoin temp_idlist t on u.id = t.id;\n\n-- PostgreSQL application (3) using pg_global_temp_tables\n--\n-- Temporary table is created like this:\n-- create temporary table temp_idlist(id bigint);\n-- create_permanent_temp_table('temp_idlist', 'myapp');\n-- commit;\n\ninsert into myapp.temp_idlist(id) values(:p);\n\nselect u.login \nfrom myapp.users u\njoin myapp.temp_idlist t on u.id = t.id;\n```\n\nNote that the usage part in (1) and (3) is exactly the same.\n\n# Usage\n\nThe library consists of two functions:\n\n* create_permanent_temp_table(p_table_name varchar, p_schema varchar default null)\n* drop_permanent_temp_table(p_table_name varchar, p_schema varchar default null)\n\nTo create a permanent temporary table, first create an ordinary temp table and then convert it to a persistent one using the `create_permanent_temp_table` function:\n\n```sql\ncreate temporary table if not exists another_temp_table\n(\n    first_name varchar,\n    last_name varchar,\n    date timestamp(0) with time zone,\n    primary key(first_name, last_name)\n)\non commit drop;\n\n-- create my_schema.another_temp_table\nselect create_permanent_temp_table('another_temp_table', 'my_schema');\n\n-- or create another_temp_table in the current schema\n-- select create_permanent_temp_table('another_temp_table');\n\n-- don't forget to commit: PostgreSQL DDL is transactional\ncommit;\n```\n\nTo drop the emulated temporary table, use the `drop_permanent_temp_table` function:\n\n```sql\n-- drop my_schema.another_temp_table\nselect drop_permanent_temp_table('another_temp_table', 'my_schema');\n\n-- or drop another_temp_table in the current schema\n-- select drop_permanent_temp_table('another_temp_table');\n\ncommit;\n```\n\n# How does it work\n\nThis library combines a few ideas to emulate Oracle-style temporary tables. First, let's define a view and use it instead of a temporary table. A view is a static object and it's defined within a schema, so it supports the `schema.table` notation used in our Oracle queries. A view can have `instead of` triggers which can create temporary table as needed. There are two problems, however:\n\n* A view on a temporary table is automatically created as temporary, even if we omit the `temporary` keyword. Hence, the restrictions of temporary tables still apply, and we can't use schema-qualified names.\n* There are no triggers on `select`, so we can't `select` from a view if the temporary table is not yet created.\n\nOk, we can't just create a view on a temporary table, so let's explore another option: we can define a function returning a table. A function is not temporary, it's defined within a schema, it can create the temporary table as needed and select and return rows from it. The function would look like this (note the `returns table` part of the definition):\n\n```sql\n-- let's do our experiments in a separate schema\ncreate schema if not exists stage;\n\ncreate or replace function stage.select_temp_idname() returns table(id bigint, name varchar) as $$\nbegin\n\tcreate temporary table if not exists test_temp_idname(id bigint, name varchar) on commit drop;\n\treturn query select * from test_temp_idname;\nend;\n$$ language plpgsql;\n```\n\nThis approach indeed works. We can select from a function, we can access it via schema-qualified name, and we don't have to create a temporary table before accessing it:\n\n```sql\nselect * from stage.select_temp_idname()\n\n-- id | name\n-- ---+-----\n```\n\nStill, it's not quite usable:\n\n* We have to add parentheses() after the function name, so we can't just leave Oracle queries as is, and\n* Rows returned by a function are read-only.\n\nTo finally fix this, we combine both approaches, a view and a function. The view selects rows from the function, and we can make it updatable by means of the `instead of` triggers.\n\n# The complete sample code of a permanent temp table\n\nHere is a working sample:\n\n```sql\ncreate or replace function stage.select_temp_idname() returns table(id bigint, name varchar) as $$\nbegin\n\tcreate temporary table if not exists test_temp_idname(id bigint, name varchar) on commit drop;\n\treturn query select * from test_temp_idname;\nend;\n$$ language plpgsql;\n\ncreate or replace view stage.temp_idname as \n\tselect * from stage.select_temp_idname();\n\ncreate or replace function stage.temp_idname_insert() returns trigger as $$\nbegin\n\tcreate temporary table if not exists test_temp_idname(id bigint, name varchar) on commit drop;\n\tinsert into test_temp_idname(id, name) values (new.id, new.name);\n\treturn new;\nend;\n$$ language plpgsql;\n\ndrop trigger if exists temp_idname_insert on stage.temp_idname;\ncreate trigger temp_idname_insert \n\tinstead of insert on stage.temp_idname\n\tfor each row\n\texecute procedure stage.temp_idname_insert();\n```\n\nFinally, we can use the table just like Oracle:\n\n```sql\nselect * from stage.temp_idname\n\n-- NOTICE: 42P07: relation \"test_temp_idname\" already exists, skipping\n-- id | name\n-- ---+-----\n\ninsert into stage.temp_idname(id, name) values (1, 'one'), (2, 'two')\n\n-- NOTICE: 42P07: relation \"test_temp_idname\" already exists, skipping\n-- (2 rows affected)\n\nselect * from stage.temp_idname\n\n-- NOTICE: 42P07: relation \"test_temp_idname\" already exists, skipping\n-- id | name\n-- ---+-----\n-- 1  | one\n-- 2  | two\n```\n\nOne minor thing that annoys me is that pesky notice: relation already exists, skipping. We get the notice every time we access the emulated temporary table via `select` or `insert` statements. Notices can be suppressed using the `client_min_messages` setting:\n\n```sql\nset client_min_messages = error\n```\n\nBut that affects all notices, even meaningful ones. Luckily, Postgres allows specifying settings per function, so that when we enter a function, Postgres applies these settings reverting them back on exit. This way we suppress our notices without affecting the client's session-level setting:\n\n```sql\ncreate or replace function stage.select_temp_idname() returns table(id bigint, name varchar) as $$\nbegin\n\tcreate temporary table if not exists test_temp_idname(id bigint, name varchar) on commit drop;\n\treturn query select * from test_temp_idname;\nend;\n$$ language plpgsql set client_min_messages = error;\n```\n\n# Creating permanent temporary tables\n\nLet's recap what's needed to create a permanent temporary table residing in a schema:\n\n1. A function returning the contents of a temporary table\n2. A view on the function\n3. Instead of insert/update/delete trigger on the view\n4. Trigger function that does the job of updating the table\n\nTo delete the temporary table, we just drop the (1) and (4) functions with cascade options, and the rest is cleaned up automatically.\n\nIt's a bit cumbersome to create these each time we need a temporary table, so let's create a function that does the job. Here, we have a new challenge: specifying the table structure can be quite tricky. Suppose we have a function like this:\n\n```sql\nselect create_permanent_temp_table(\n\tp_schema =\u003e 'stage', \n\tp_table_name =\u003e 'complex_temp_table', \n\tp_table_structure =\u003e '\n\t\tid bigint,\n\t\tname character varying (256),\n\t\tdate timestamp(0) with time zone\n\t',\n\tp_table_pk =\u003e ...\n\tp_table_pk_columns =\u003e ...\n\tp_table_indexes =\u003e ...\n\tetc.\n);\n```\n\nThe function have to parse table structure, list of primary key columns, indexes, etc. If the function doesn't validate the provided code, it's vulnerable to SQL injection, but validating the code turns out to require a full-blown SQL parser (for example, columns can have default values specified by arbitrary expressions). Worse, the table specification can change in the future, the syntax will evolve over time, etc. I'd like to avoid that kind of complexity in my utility code, so is there a better way?\n\nThe alternative approach that came to my mind is to convert an ordinal temporary table into a permanent one. We start with creating a temporary table using native PostgreSQL syntax, then we inspect the structure of the table and recreate it as a permanent object:\n\n```sql\n-- create a table as usual\ncreate temporary table if not exists complex_temp_table\n(\n    id bigint,\n    name character varying (256),\n    date timestamp(0) with time zone,\n    constraint complex_temp_table_pk primary key(id)\n    -- or just: primary key (id)\n)\non commit drop;\n\n-- convert temp table into permanent one\nselect create_permanent_temp_table(p_schema =\u003e 'stage', p_table_name =\u003e 'complex_temp_table');\n```\n\n# Reverse engineering a temporary table\n\nInspecting the table structure to generate the `create table` statement usually involves a few queries to the `information_schema` views:\n\n1. Table properties — `information_schema.tables`\n2. Column names and types — `information_schema.columns`\n3. Primary key — `information_schema.constraint_table_usage`, `constraint_column_usage`, `key_column_usage`.\n \nHere's how to list the columns of the 'complex_temp_table' table:\n\n```sql\nselect c.column_name, c.data_type, c.character_maximum_length,\n\tc.numeric_precision, c.datetime_precision \nfrom information_schema.tables t\njoin information_schema.columns c on c.table_name = t.table_name and c.table_schema = t.table_schema\nwhere t.table_name = 'complex_temp_table'\norder by c.ordinal_position\n\n-- column_name | data_type   | char_max_length | num_precision | date_precision\n-- ------------+-------------+-----------------+---------------+----------------\n-- id          | bigint      | null            | 64            | null\n-- name        | varchar     | 256             | null          | null\n-- date        | timestamptz | null            | null          | 0\n```\n\nAlso, there are lots Postgres-specific tables, views and functions in pg_catalog chema, such as format_type function (these are non-standard, however). Querying these tables often works faster than the standard information_schema views because the views combine multiple data sources. As we don't really need to be ANSI SQL standard-compliant, let's use native Postgres tables. Here is how we generate a `create table` statement:\n\n```sql\nselect format(\n\tE'create temporary table %I\\n(\\n%s\\n);\\n',\n\tc.relname,\n\tstring_agg(\n\t\tformat(E'\\t%I %s %s',\n\t\t\ta.attname,\n\t\t\tpg_catalog.format_type(a.atttypid, a.atttypmod),\n\t\t\tcase when a.attnotnull then 'not null' else '' end\n\t\t), E',\\n'\n\t\torder by a.attnum\n\t)) as sql\nfrom pg_catalog.pg_class c\n\tjoin pg_catalog.pg_attribute a on a.attrelid = c.oid and a.attnum \u003e 0\n\tjoin pg_catalog.pg_type t on a.atttypid = t.oid\nwhere c.relname = 'complex_temp_table' and c.relpersistence = 't'\ngroup by c.relname\n\n-- create temporary table complex_temp_table\n-- (\n--     id bigint not null,\n--     name character varying(256) null,\n--     date timestamp(0) with time zone null\n-- );\n```\n\nThe next challenge is the primary key clause. Note that keys can be compound (i.e. consisting of several columns). Primary keys are listed in `pg_constraint` table as constraints of type 'p', and `conkey` array contains table attributes making the key. Here is a query returning the primary keys and their columns of all temporary tables:\n\n```sql\nselect c.relname table_name, cc.conname primary_key_name, a.attname column_name\nfrom pg_catalog.pg_constraint cc\n\tjoin pg_catalog.pg_class c on c.oid = cc.conrelid\n\tjoin pg_catalog.pg_attribute a on a.attrelid = cc.conrelid and a.attnum = any(cc.conkey)\nwhere cc.contype = 'p' and c.relpersistence = 't'\norder by cc.conrelid, a.attname\n\n-- table_name         | primary_key_name      | column_name\n-- -------------------+-----------------------+------------ \n-- complex_temp_table | complex_temp_table_pk | id\n```\n\n\u003ePoints of interest: `pg_attribute` join condition includes `any` clause which means that we look for `attnum` values listed in the `conkey` array: `a.attnum = any(cc.conkey)`.\n\nFinally, let's combine the two queries to get the table definition including the primary key. To make sure it handles the compound keys, let's create another temporary table with more than one primary key column:\n\n```sql\n-- sample table\ncreate temporary table if not exists another_temp_table\n(\n    first_name varchar,\n    last_name varchar,\n    date timestamp(0) with time zone,\n    primary key(first_name, last_name)\n)\non commit drop;\n\n-- the combined query\nwith pkey as\n(\n\tselect cc.conrelid, format(E',\n\tconstraint %I primary key(%s)', cc.conname,\n\t\tstring_agg(a.attname, ', ' order by array_position(cc.conkey, a.attnum))) pkey\n\tfrom pg_catalog.pg_constraint cc\n\t\tjoin pg_catalog.pg_class c on c.oid = cc.conrelid\n\t\tjoin pg_catalog.pg_attribute a on a.attrelid = cc.conrelid and a.attnum = any(cc.conkey)\n\twhere cc.contype = 'p'\n\tgroup by cc.conrelid, cc.conname\n)\nselect format(E'create temporary table %I\\n(\\n%s%s\\n);\\n',\n\tc.relname,\n\tstring_agg(\n\t\tformat(E'\\t%I %s%s',\n\t\t\ta.attname,\n\t\t\tpg_catalog.format_type(a.atttypid, a.atttypmod),\n\t\t\tcase when a.attnotnull then ' not null' else '' end\n\t\t), E',\\n'\n\t\torder by a.attnum\n\t),\n\t(select pkey from pkey where pkey.conrelid = c.oid)) as sql\nfrom pg_catalog.pg_class c\n\tjoin pg_catalog.pg_attribute a on a.attrelid = c.oid and a.attnum \u003e 0\n\tjoin pg_catalog.pg_type t on a.atttypid = t.oid\nwhere c.relname = 'another_temp_table' and c.relpersistence = 't'\ngroup by c.oid, c.relname;\n\n-- create temporary table another_temp_table\n-- (\n--     first_name character varying not null,\n--     last_name character varying not null,\n--     date timestamp(0) with time zone,\n--     constraint another_temp_table_pkey primary key(first_name, last_name)\n-- );\n```\n\n\u003ePoints of interest: `string_agg` function takes the `order by` clause to preserve the order or primary key columns and the order of table columns (these two don't always use the same order).\n\nThe query also handles tables with no defined primary key (try yourself creating different temporary tables and see how it works).\n\n# Instead of insert/update/delete trigger\n\nSimple views in PostgreSQL are usually updatable by default (a view is automatically updatable if it doesn't have [joins, group by and unions](https://www.postgresql.org/docs/current/static/sql-createview.html)). But the view we created is not simple: it gets its data from a function, not from a table, so it requires the instead of triggers. All three triggers can be implemented using a single function that looks like this:\n\n```sql\ncreate or replace function temp_tag_idlist_iud() returns trigger as $$\nbegin\n\t-- temporary table definition (skipped)\n\tcreate temporary table if not exists temp_id_name_table ...;\n\n\tif tg_op = 'INSERT' then\n\t\tinsert into temp_id_name_table(id, name) \n\t\tvalues (new.id, new.name);\n\t\treturn new;\n\telsif tg_op = 'UPDATE' then\n\t\tupdate temp_id_name_table \n\t\tset id = new.id, name = new.name\n\t\twhere id = old.id;\n\t\treturn new;\n\telsif tg_op = 'DELETE' then\n\t\tdelete from temp_id_name_table \n\t\twhere id = old.id;\n\t\treturn old;\n\tend if;\nend;\n$$ language plpgsql set client_min_messages to error;\n```\n\nTrigger function uses the built-in `tg_op` variable to distinguish between different operations handled by the trigger. To generate the important part of the trigger we need to prepare\nseveral lists of columns like the following:\n\n* id, name (comma-separated list of all columns)\n* new.id, new.name (comma-separated list of all columns, prepended with `new`)\n* id = new.id, name = new.name (list of `x = new.x` expressions for all columns)\n* id = old.id (list of `x = old.x` expressions, primary key columns only)\n* id bigint, name varchar (list of all columns and their types).\n\nWe can use either `information_schema.columns` view or `pg_attributes` table to prepare these lists of columns:\n\n```sql\n-- generate the lists of columns\nselect\n\tstring_agg(a.attname, ', ') as all_columns,\n\tstring_agg(format('new.%I', a.attname), ', ') as new_columns,\n\tstring_agg(format('%I = new.%I', a.attname, a.attname), ', ') as assignments,\n\tstring_agg(format('%I %s', a.attname, \n\t\tpg_catalog.format_type(a.atttypid, a.atttypmod)), ', ') as column_types\nfrom pg_catalog.pg_class c\n\tjoin pg_catalog.pg_attribute a on a.attrelid = c.oid and a.attnum \u003e 0\nwhere c.relname = 'another_temp_table' and c.relpersistence = 't';\n\n-- generate the list of primary key columns\nselect string_agg(format('%I = old.%I', a.attname, a.attname), ' and ' \n\torder by array_position(cc.conkey, a.attnum)) as old_columns\nfrom pg_catalog.pg_constraint cc\n\tjoin pg_catalog.pg_class c on c.oid = cc.conrelid\n\tjoin pg_catalog.pg_attribute a on a.attrelid = cc.conrelid and a.attnum = any(cc.conkey)\nwhere cc.contype = 'p' and c.relname = 'another_temp_table' and c.relpersistence = 't'\ngroup by cc.conrelid, cc.conname;\n```\n\n# Assembling the pieces together\n\nThe rest of the job is straightforward:\n\n* check if the given temporary table exists\n* rename the existing temporary table to avoid the conflict with the view\n* generate temporary table definition as returned by the query above\n* generate the trigger function with insert, update and delete statements\n* format the boilerplate code using the table name and other generated parts\n* execute the generated code.\n\nThe view we're creating will have the same name as the source temporary table. So, to avoid the name conflict we'll rename the original temporary table by adding a suffix. The structure of our function will be similar to this (see the repository for the full source code):\n\n```sql\ncreate or replace function create_permanent_temp_table(p_table_name varchar, p_schema varchar) returns void as $$\ndeclare\n\tv_table_name varchar := p_table_name || '$tmp';\n\tv_trigger_name varchar := p_table_name || '$iud';\n\tv_final_statement text;\n\tv_table_statement text; -- create temporary table...\n\tv_all_column_list text; -- id, name, ...\n\tv_new_column_list text; -- new.id, new.name, ...\n\tv_assignment_list text; -- id = new.id, name = new.name, ...\n\tv_cols_types_list text; -- id bigint, name varchar, ...\n\tv_old_column_list text; -- id = old.id\nbegin\n\t-- check if the temporary table exists\n\tif not exists(select 1 from pg_class where relname = p_table_name and relpersistence = 't') then\n\t\traise exception 'Temporary table % does not exist.', p_table_name;\n\tend if;\n\t\n\t-- generate the temporary table statement\n\twith pkey as ...\n\tselect format...\n\tinto v_table_statement...;\n\n\t-- generate the lists of columns\n\tselect ...\n\tinto v_all_column_list, v_new_column_list, v_assignment_list...;\n\n\t-- generate the list of primary key columns\n\tselect ...\n\tinto v_old_column_list...;\n\n\t-- generate the statements to create permanent temporary table\n\tv_final_statement := format(....);\n\n\t-- execute the statement\n\texecute v_final_statement;\nend;\n$$ language plpgsql;\n```\n\nNote: when converting a temporary table into a permanent one, we're preserving its current contents. Now let's check how it works:\n\n```sql\ncreate temporary table if not exists another_temp_table\n(\n    first_name varchar,\n    last_name varchar,\n    date timestamp(0) with time zone,\n    primary key(first_name, last_name)\n)\non commit drop;\n\n-- populate the table with a few initial rows\ninsert into\n\tanother_temp_table(first_name, last_name, date)\nvalues\n\t('Jean-Paul', 'Sartre', date'1905-06-21'),\n\t('Albert', 'Camus', date'1913-11-07');\n\n-- convert the table into the permanent one\nselect create_permanent_temp_table('another_temp_table', 'stage');\n\n-- check if the contents still exists\nselect * from stage.another_temp_table;\n\n-- first_name | last_name | date\n-- -----------+-----------+-------------\n-- Jean-Paul  | Sartre    | 1905-06-21\n-- Albert     | Camus     | 1913-11-07\n-- \n-- 2 rows affected\n\n-- commit to create the permanent table\n-- note that it will discard all current rows\ncommit;\n\nselect * from stage.another_temp_table;\n\n-- first_name | last_name | date\n-- -----------+-----------+-------------\n-- 0 rows affected\n\n-- try insert/update/delete operations\ninsert into\n\tstage.another_temp_table(first_name, last_name, date)\nvalues\n\t('Jean-Paul', 'Sartre', date'1905-06-21'),\n\t('Albert', 'Camus', date'1913-11-07');\n\n-- 2 rows affected\n\nupdate stage.another_temp_table \nset date = now() \nwhere first_name like '%bert%';\n\n-- 1 row affected\n\ndelete from stage.another_temp_table\nwhere last_name = 'Sartre';\n\n-- 1 row affected\n\nselect * from stage.another_temp_table;\n\n-- first_name | last_name | date\n-- -----------+-----------+-------------\n-- Albert     | Camus     | 2017-03-13\n-- \n-- 1 row affected\n\ncommit;\n\nselect * from stage.another_temp_table;\n\n-- first_name | last_name | date\n-- -----------+-----------+-------------\n-- 0 rows selected\n```\n\nThe library also has the `drop_permanent_temp_table` function which is very simple. It just checks that two functions exists, validates that their signatures, then generates and executes two `drop function ... cascade` statements.\n\n# Unit tests\n\nUnit tests for the library use [PGUnit framework](https://github.com/adrianandrei-ca/pgunit) installed in the dedicated `pgunit` schema. Make sure to create the test functions in the same schema as the library functions. To run all tests, use the following code:\n\n```sql\nselect * from pgunit.test_run_suite('pg_global_temp_tables');\n\n-- test_name                                 | suc... | failed | err...| err...| duration\n---------------------------------------------+--------+--------+-------+-------+-----------------\n-- test_case_pg_global_temp_tables_create_f..| 1      | 0      | 0     | OK    | 00:00:00.0137540\n-- test_case_pg_global_temp_tables_create_s..| 1      | 0      | 0     | OK    | 00:00:00.1048550\n-- test_case_pg_global_temp_tables_drop_fai..| 1      | 0      | 0     | OK    | 00:00:00.0153330\n-- test_case_pg_global_temp_tables_drop_suc..| 1      | 0      | 0     | OK    | 00:00:00.1008250\n```\n \n---\n\n# Copyright and License\n\nCopyright (c) 2017, Alexey Yakovlev\n\nPermission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies.\n\nIN NO EVENT SHALL ALEXEY YAKOVLEV BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF ALEXEY YAKOVLEV HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nALEXEY YAKOVLEV SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN \"AS IS\" BASIS, AND ALEXEY YAKOVLEV HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyallie%2Fpg_global_temp_tables","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyallie%2Fpg_global_temp_tables","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyallie%2Fpg_global_temp_tables/lists"}