CVE-2021-33204 (published 2021-05-19T17:15Z)

In the pg_partman (aka PG Partition Manager) extension before 4.5.1 for PostgreSQL, arbitrary code execution can be achieved via SECURITY DEFINER functions because an explicit search_path is not set.

Severity: HIGH
CVSS v2 base score: 7.5 (AV:N/AC:L/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 9.8, CRITICAL (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/pgpartman/pg_partman
commit_details: repo_url: https://github.com/pgpartman/pg_partman
author: Keith Fiske
author_date: 2021-05-11 13:28:02-04:00
msg: v4.5.1 Fix security vulnerability and id sub-partitioning. See CHANGELOG for more details.
repository: repo_url: https://github.com/pgpartman/pg_partman
repo_name: pgpartman/pg_partman
description: Partition management extension for PostgreSQL
repo_language: PLpgSQL
owner: pgpartman
file_changes: filename: CHANGELOG.txt
change_type: ModificationType.MODIFY
diff: @@ -1,3 +1,16 @@
+4.5.1
+NEW FEATURES
+============
+-- Allow relation options set on the template table to be inherited on the child table. As of PG13 and earlier, relation options set on the parent are not being set on the child tables. Unknown if PG14 will handle this yet or not (Github PR #348).
+
+BUG FIXES
+=========
+-- Fixed security issue that could allow arbitrary code execution using SECURITY DEFINER functions. Set explicit search_path to avoid this. Thanks to Github user @tapioaiven of Aiven Ltd for reporting the issue.
+
+-- Fixed several bugs in sub-partitioning when using a mixture of epoch and regular integer partitioning in the same partition set (Github Issue #357).
+
+
+
 4.5.0
 NEW FEATURES
 ============

programming_language: Markdown
filename: META.json
change_type: ModificationType.MODIFY
diff: @@ -1,9 +1,9 @@
 {
     "name": "pg_partman",
     "abstract": "Extension to manage partitioned tables by time or ID",
-    "version": "4.5.0",
+    "version": "4.5.1",
     "maintainer": [ 
-        "Keith Fiske <keith@omniti.com>"
+        "Keith Fiske <keith@keithf4.com>"
     ],
     "license": "postgresql",
     "generated_by": "Keith Fiske",
@@ -20,19 +20,19 @@
     },
     "provides": {
         "pg_partman": {
-            "file": "sql/pg_partman--4.5.0.sql",
+            "file": "sql/pg_partman--4.5.1.sql",
             "docfile": "doc/pg_partman.md",
-            "version": "4.5.0",
+            "version": "4.5.1",
             "abstract": "Extension to manage partitioned tables by time or ID"
         }
     },
     "resources": {
         "bugtracker": {
-            "web": "https://github.com/keithf4/pg_partman/issues"
+            "web": "https://github.com/pgpartman/pg_partman/issues"
         },
         "repository": {
-            "url": "git://github.com/keithf4/pg_partman.git" ,
-            "web": "https://github.com/keithf4/pg_partman",
+            "url": "git://github.com/pgpartman/pg_partman.git" ,
+            "web": "https://github.com/pgpartman/pg_partman",
             "type": "git"
         }
     },    

programming_language: JavaScript
filename: pg_partman.control
change_type: ModificationType.MODIFY
diff: @@ -1,3 +1,3 @@
-default_version = '4.5.0'
+default_version = '4.5.1'
 comment = 'Extension to manage partitioned tables by time or ID'
 relocatable = false

programming_language: SQL
filename: check_name_length.sql
change_type: ModificationType.MODIFY
diff: @@ -1,5 +1,6 @@
 CREATE FUNCTION @extschema@.check_name_length (p_object_name text, p_suffix text DEFAULT NULL, p_table_partition boolean DEFAULT FALSE) RETURNS text
     LANGUAGE plpgsql IMMUTABLE SECURITY DEFINER
+    SET search_path TO pg_catalog, pg_temp
     AS $$
 DECLARE
     v_new_length    int;

programming_language: SQL
filename: create_parent.sql
change_type: ModificationType.MODIFY
diff: @@ -32,6 +32,7 @@ v_datetime_string               text;
 v_default_partition             text;
 v_higher_control_type           text;
 v_higher_parent_control         text;
+v_higher_parent_epoch           text;
 v_higher_parent_schema          text := split_part(p_parent_table, '.', 1);
 v_higher_parent_table           text := split_part(p_parent_table, '.', 2);
 v_id_interval                   bigint;
@@ -562,8 +563,8 @@ IF v_control_type = 'id' AND p_epoch = 'none' THEN
             JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
             WHERE n.nspname = v_higher_parent_schema::name
             AND c.relname = v_higher_parent_table::name
-        ) SELECT n.nspname, c.relname, p.control
-        INTO v_higher_parent_schema, v_higher_parent_table, v_higher_parent_control
+        ) SELECT n.nspname, c.relname, p.control, p.epoch
+        INTO v_higher_parent_schema, v_higher_parent_table, v_higher_parent_control, v_higher_parent_epoch
         FROM pg_catalog.pg_class c
         JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
         JOIN top_oid t ON c.oid = t.top_parent_oid
@@ -572,7 +573,7 @@ IF v_control_type = 'id' AND p_epoch = 'none' THEN
         IF v_higher_parent_table IS NOT NULL THEN
             SELECT general_type INTO v_higher_control_type
             FROM @extschema@.check_control_type(v_higher_parent_schema, v_higher_parent_table, v_higher_parent_control);
-            IF v_higher_control_type <> 'id' THEN
+            IF v_higher_control_type <> 'id' or (v_higher_control_type = 'id' AND v_higher_parent_epoch <> 'none') THEN
                 -- The parent above the p_parent_table parameter is not partitioned by ID
                 --   so don't check for max values in parents that aren't partitioned by ID.
                 -- This avoids missing child tables in subpartition sets that have differing ID data

programming_language: SQL
filename: create_sub_parent.sql
change_type: ModificationType.MODIFY
diff: @@ -176,7 +176,7 @@ LOOP
             RAISE EXCEPTION 'Due to conflicting data boundaries between ISO weeks and any larger interval of time, pg_partman cannot support a sub-partition interval of weekly';
         END IF;
 
-    ELSIF v_control_parent_type = 'id' AND v_control_sub_type = 'id' THEN
+    ELSIF v_control_parent_type = 'id' AND v_control_sub_type = 'id' AND v_parent_epoch = 'none' AND p_epoch = 'none' THEN
         IF p_interval::bigint >= v_parent_interval::bigint THEN
             EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false');
             RAISE EXCEPTION 'Sub-partition interval cannot be greater than or equal to the given parent interval';

programming_language: SQL
filename: inherit_template_properties.sql
change_type: ModificationType.MODIFY
diff: @@ -14,6 +14,7 @@ v_inherit_fk            boolean;
 v_parent_index_list     record;
 v_parent_oid            oid;
 v_parent_table          text;
+v_relopt                record;
 v_sql                   text;
 v_template_oid          oid;
 v_template_schemaname   text;
@@ -119,14 +120,14 @@ IF current_setting('server_version_num')::int >= 100000 THEN
 
                 IF v_parent_index_list.indisprimary AND v_index_list.indisprimary THEN
                     IF v_parent_index_list.indkey_names = v_index_list.indkey_names THEN
-                        RAISE DEBUG 'Ignoring duplicate primary key on template table: % ', v_index_list.indkey_names;
+                        RAISE DEBUG 'inherit_template_properties: Ignoring duplicate primary key on template table: % ', v_index_list.indkey_names;
                         v_dupe_found := true;
                         CONTINUE; -- only continue within this nested loop
                     END IF;
                 END IF;
 
                 IF v_parent_index_list.statement = v_index_list.statement THEN
-                    RAISE DEBUG 'Ignoring duplicate index on template table: %', v_index_list.statement;
+                    RAISE DEBUG 'inherit_template_properties: Ignoring duplicate index on template table: %', v_index_list.statement;
                     v_dupe_found := true;
                     CONTINUE; -- only continue within this nested loop
                 END IF;
@@ -147,7 +148,7 @@ IF current_setting('server_version_num')::int >= 100000 THEN
             IF v_index_list.tablespace_name IS NOT NULL THEN
                 v_sql := v_sql || format(' USING INDEX TABLESPACE %I', v_index_list.tablespace_name);
             END IF;
-            RAISE DEBUG 'Create pk: %', v_sql;
+            RAISE DEBUG 'inherit_template_properties: Create pk: %', v_sql;
             EXECUTE v_sql;
         ELSE
             -- statement column should be just the portion of the index definition that defines what it actually is
@@ -156,7 +157,7 @@ IF current_setting('server_version_num')::int >= 100000 THEN
                 v_sql := v_sql || format(' TABLESPACE %I', v_index_list.tablespace_name);
             END IF;
 
-            RAISE DEBUG 'Create index: %', v_sql;
+            RAISE DEBUG 'inherit_template_properties: Create index: %', v_sql;
             EXECUTE v_sql;
 
         END IF;
@@ -176,7 +177,7 @@ IF current_setting('server_version_num')::int >= 100000 AND current_setting('ser
             AND contype = 'f'
         LOOP
             v_sql := format('ALTER TABLE %I.%I ADD %s', v_child_schema, v_child_tablename, v_fk_list.constraint_def);
-            RAISE DEBUG 'Create FK: %', v_sql;
+            RAISE DEBUG 'inherit_template_properties: Create FK: %', v_sql;
             EXECUTE v_sql;
         END LOOP;
     END IF;
@@ -186,7 +187,7 @@ END IF;
 -- Tablespace inheritance on PG11 and earlier
 IF current_setting('server_version_num')::int < 120000 AND v_template_tablespace IS NOT NULL THEN
     v_sql := format('ALTER TABLE %I.%I SET TABLESPACE %I', v_child_schema, v_child_tablename, v_template_tablespace);
-    RAISE DEBUG 'Alter tablespace: %', v_sql;
+    RAISE DEBUG 'inherit_template_properties: Alter tablespace: %', v_sql;
     EXECUTE v_sql;
 END IF;
 
@@ -206,14 +207,27 @@ AND c.relname = v_child_tablename::name;
 
 IF v_template_unlogged = 'u' AND v_child_unlogged = 'p'  THEN
     v_sql := format ('ALTER TABLE %I.%I SET UNLOGGED', v_child_schema, v_child_tablename);
-    RAISE DEBUG 'Alter UNLOGGED: %', v_sql;
+    RAISE DEBUG 'inherit_template_properties: Alter UNLOGGED: %', v_sql;
     EXECUTE v_sql;     
 ELSIF v_template_unlogged = 'p' AND v_child_unlogged = 'u'  THEN
     v_sql := format ('ALTER TABLE %I.%I SET LOGGED', v_child_schema, v_child_tablename);
-    RAISE DEBUG 'Alter UNLOGGED: %', v_sql;
+    RAISE DEBUG 'inherit_template_properties: Alter UNLOGGED: %', v_sql;
     EXECUTE v_sql;     
 END IF;
 
+-- Relation options are not being inherited for PG <= 13
+FOR v_relopt IN
+    SELECT unnest(reloptions) as value
+    FROM pg_catalog.pg_class
+    WHERE oid = v_template_oid
+LOOP
+    v_sql := format('ALTER TABLE %I.%I SET (%s)'
+                    , v_child_schema
+                    , v_child_tablename
+                    , v_relopt.value);
+    RAISE DEBUG 'inherit_template_properties: Set relopts: %', v_sql;
+    EXECUTE v_sql;
+END LOOP;
 RETURN true;
 
 END

programming_language: SQL
filename: tables.sql
change_type: ModificationType.MODIFY
diff: @@ -142,6 +142,7 @@ CHECK (@extschema@.check_automatic_maintenance_value(sub_automatic_maintenance))
  */
 CREATE FUNCTION @extschema@.check_epoch_type (p_type text) RETURNS boolean
     LANGUAGE plpgsql IMMUTABLE SECURITY DEFINER
+    SET search_path TO pg_catalog, pg_temp
     AS $$
 DECLARE
 v_result    boolean;
@@ -165,6 +166,7 @@ CHECK (@extschema@.check_epoch_type(sub_epoch));
  */
 CREATE OR REPLACE FUNCTION @extschema@.check_partition_type (p_type text) RETURNS boolean
     LANGUAGE plpgsql IMMUTABLE SECURITY DEFINER
+    SET search_path TO pg_catalog, pg_temp
     AS $$
 DECLARE
 v_result    boolean;
@@ -174,6 +176,7 @@ BEGIN
 END
 $$;
 
+
 ALTER TABLE @extschema@.part_config
 ADD CONSTRAINT part_config_type_check 
 CHECK (@extschema@.check_partition_type(partition_type));

programming_language: SQL
filename: test-time-daily-generated.sql
change_type: ModificationType.MODIFY
diff: @@ -1,6 +1,6 @@
 -- ########## TIME DAILY TESTS ##########
 -- Other tests: 
-    -- Test using default template table. Initial child tables will have no indexes. New tables after template has indexes added should.
+    -- Test generated always columns
 
 \set ON_ERROR_ROLLBACK 1
 \set ON_ERROR_STOP true

programming_language: SQL
filename: test-time-epoch-id-subpart-native.sql
change_type: ModificationType.ADD
diff: @@ -0,0 +1,778 @@
+-- ########## TIME PARENT / ID SUBPARENT NATIVE TESTS ##########
+-- Leaving pk/fk test out of this one so it can be used to test the native support in 11+
+
+\set ON_ERROR_ROLLBACK 1
+\set ON_ERROR_STOP true
+
+BEGIN;
+SELECT set_config('search_path','partman, public',false);
+
+SELECT plan(306);
+CREATE SCHEMA partman_test;
+
+-- Add back primary key when native supports it
+--CREATE TABLE partman_test.time_taptest_table (col1 int primary key, col2 text, col3 timestamptz NOT NULL DEFAULT now());
+CREATE TABLE partman_test.time_taptest_table (
+    col1 int 
+    , col2 text
+    , col3 int NOT NULL DEFAULT extract('epoch' from CURRENT_TIMESTAMP)::int) 
+    PARTITION BY RANGE (col3);
+CREATE TABLE partman_test.undo_taptest (LIKE partman_test.time_taptest_table INCLUDING ALL);
+
+SELECT create_parent('partman_test.time_taptest_table', 'col3', 'native', 'daily', p_epoch := 'seconds');
+INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), extract('epoch' from CURRENT_TIMESTAMP)::int);
+
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||' exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist');
+
+-- Restore back when native supports primary key
+--SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 
+--    'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'));
+--SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 
+--    'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'));
+--SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 
+--    'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'));
+--SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 
+--    'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'));
+--SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 
+--    'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'));
+--SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 
+--    'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'));
+--SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 
+--    'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'));
+--SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 
+--    'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'));
+--SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 
+--    'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'));
+--SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 
+--    'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'));
+
+SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had data moved to partition');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[10], 'Check count from parent table');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 
+    ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'));
+
+-- Create subpartition 
+SELECT partman.create_sub_parent('partman_test.time_taptest_table', 'col1', 'native', '20', p_native_check := 'yes');
+--Reinsert data due to child table destruction
+INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), extract('epoch' from CURRENT_TIMESTAMP)::int);
+
+SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it');
+SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 
+    'Check that subparent table has had no data inserted to it');
+SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p0', ARRAY[10], 
+    'Check count from partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p0');
+
+SELECT is_partitioned('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' is natively partitioned');
+SELECT is_partitioned('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' is natively partitioned');
+SELECT is_partitioned('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' is natively partitioned');
+SELECT is_partitioned('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' is natively partitioned');
+SELECT is_partitioned('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' is natively partitioned');
+SELECT isnt_partitioned('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' is not natively partitioned');
+SELECT is_partitioned('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||' is natively partitioned');
+SELECT is_partitioned('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' is natively partitioned');
+SELECT is_partitioned('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' is natively partitioned');
+SELECT is_partitioned('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' is natively partitioned');
+SELECT isnt_partitioned('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' is not natively partitioned');
+
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p0', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p0 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p20', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p20 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p40', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p40 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p60', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p60 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p80', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p80 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p100', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p100 does not exist');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p0', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p0 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p20', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p20 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p40', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p40 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p60', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p60 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p80', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p80 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p100', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p100 does not exist');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p0', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p0 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p20', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p20 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p40', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p40 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p60', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p60 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p80', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p80 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p0', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p0 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p20', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p20 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p40', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p40 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p60', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p60 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p80', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p80 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p0', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p0 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p20', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p20 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p40', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p40 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p60', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p60 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p80', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p80 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p0', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p0 does not exist');
+
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p0', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p0 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p20', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p20 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p40', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p40 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p60', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p60 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p80', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p80 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p100', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p100 does not exist');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||'_p0', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||'_p0 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||'_p20', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||'_p20 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||'_p40', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||'_p40 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||'_p60', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||'_p60 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||'_p80', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||'_p80 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p0', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p0 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p20', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p20 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p40', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p40 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p60', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p60 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p80', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p80 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p0', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p0 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p20', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p20 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p40', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p40 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p60', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p60 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p80', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p80 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||'_p0', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||'_p0 does not exist');
+
+INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,35), extract('epoch' from CURRENT_TIMESTAMP)::int);
+INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,35), extract('epoch' from CURRENT_TIMESTAMP + '1 day'::interval)::int);
+INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,35), extract('epoch' from CURRENT_TIMESTAMP + '2 days'::interval)::int);
+INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,35), extract('epoch' from CURRENT_TIMESTAMP + '3 days'::interval)::int);
+INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,35), extract('epoch' from CURRENT_TIMESTAMP + '4 days'::interval)::int);
+INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,35), extract('epoch' from CURRENT_TIMESTAMP - '1 day'::interval)::int);
+INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,35), extract('epoch' from CURRENT_TIMESTAMP - '2 days'::interval)::int);
+INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,35), extract('epoch' from CURRENT_TIMESTAMP - '3 days'::interval)::int);
+INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,35), extract('epoch' from CURRENT_TIMESTAMP - '4 days'::interval)::int);
+
+SELECT run_maintenance();
+
+-- Check row counts from all levels
+SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has no data');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[315], 'Check count from parent table');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 
+    ARRAY[35], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'));
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 
+    ARRAY[35], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'));
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 
+    ARRAY[35], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'));
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 
+    ARRAY[35], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'));
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 
+    ARRAY[35], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'));
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 
+    ARRAY[35], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'));
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 
+    ARRAY[35], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'));
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 
+    ARRAY[35], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'));
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 
+    ARRAY[35], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'));
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p0', 
+    ARRAY[19], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p0');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p20', 
+    ARRAY[16], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p20');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p0', 
+    ARRAY[19], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p0');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p20', 
+    ARRAY[16], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p20');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p0', 
+    ARRAY[19], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p0');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p20', 
+    ARRAY[16], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p20');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p0', 
+    ARRAY[19], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p0');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p20', 
+    ARRAY[16], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p20');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p0', 
+    ARRAY[19], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p0');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p20', 
+    ARRAY[16], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p20');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p0', 
+    ARRAY[19], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p0');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p20', 
+    ARRAY[16], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p20');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||'_p0', 
+    ARRAY[19], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||'_p0');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||'_p20', 
+    ARRAY[16], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||'_p20');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p0', 
+    ARRAY[19], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p0');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p20', 
+    ARRAY[16], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p20');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p0', 
+    ARRAY[19], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p0');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p20', 
+    ARRAY[16], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p20');
+
+-- Check that new serial child partition was created for each time sub-parent
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p100', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p100 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p100', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p100 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p100', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p100 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p100', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p100 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p100', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p100 exists');
+UPDATE part_config SET premake = 5, optimize_trigger = 5 WHERE parent_table = 'partman_test.time_taptest_table';
+
+SELECT run_maintenance();
+
+-- Check for +5,+6,+7,+8,+9 days (since +4 days data exists) new subparent & children. Should only have 0,20,40,60,80 because subparent is empty and starting from zero
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p0', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p0 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p20', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p20 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p40', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p40 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p60', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p60 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p80', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p80 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p100', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p100 does not exist');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||'_p0', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||'_p0 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||'_p20', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||'_p20 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||'_p40', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||'_p40 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||'_p60', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||'_p60 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||'_p80', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||'_p80 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||'_p100', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||'_p100 does not exist');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||'_p0', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||'_p0 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||'_p20', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||'_p20 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||'_p40', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||'_p40 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||'_p60', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||'_p60 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||'_p80', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||'_p80 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||'_p100', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||'_p100 does not exist');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||'_p0', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||'_p0 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||'_p20', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||'_p20 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||'_p40', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||'_p40 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||'_p60', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||'_p60 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||'_p80', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||'_p80 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||'_p100', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||'_p100 does not exist');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||'_p0', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||'_p0 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||'_p20', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||'_p20 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||'_p40', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||'_p40 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||'_p60', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||'_p60 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||'_p80', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||'_p80 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||'_p100', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||'_p100 does not exist');
+
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||'_p0', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||'_p0 does not exist');
+
+
+-- insert batch 2
+INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(36,50), extract('epoch' from CURRENT_TIMESTAMP)::int);
+INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(36,50), extract('epoch' from CURRENT_TIMESTAMP + '1 day'::interval)::int);
+INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(36,50), extract('epoch' from CURRENT_TIMESTAMP + '2 days'::interval)::int);
+INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(36,50), extract('epoch' from CURRENT_TIMESTAMP + '3 days'::interval)::int);
+INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(36,50), extract('epoch' from CURRENT_TIMESTAMP + '4 days'::interval)::int);
+INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,50), extract('epoch' from CURRENT_TIMESTAMP + '5 days'::interval)::int);
+INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(36,50), extract('epoch' from CURRENT_TIMESTAMP - '1 day'::interval)::int);
+INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(36,50), extract('epoch' from CURRENT_TIMESTAMP - '2 days'::interval)::int);
+INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(36,50), extract('epoch' from CURRENT_TIMESTAMP - '3 days'::interval)::int);
+INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(36,50), extract('epoch' from CURRENT_TIMESTAMP - '4 days'::interval)::int);
+SELECT run_maintenance();
+
+-- All sub partition sets should be the same now
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p100', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p100 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p120', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p120 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p120', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p120 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p120', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p120 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p120', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p120 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p120', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p120 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p120', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p120 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p120', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p120 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||'_p120', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||'_p120 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p120', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p120 exists');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p120', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p120 exists');
+
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p140', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p140 does not exist');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p140', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p140 does not exist');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p140', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p140 does not exist');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p140', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p140 does not exist');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p140', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p140 does not exist');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p140', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p140 does not exist');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p140', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p140 does not exist');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||'_p140', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||'_p140 does not exist');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p140', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p140 does not exist');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p140', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p140 does not exist');
+
+-- Check row counts from all levels
+SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has no data');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[500], 'Check count from parent table');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 
+    ARRAY[50], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'));
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 
+    ARRAY[50], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'));
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 
+    ARRAY[50], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'));
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 
+    ARRAY[50], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'));
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 
+    ARRAY[50], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'));
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 
+    ARRAY[50], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'));
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 
+    ARRAY[50], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'));
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 
+    ARRAY[50], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'));
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 
+    ARRAY[50], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'));
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 
+    ARRAY[50], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'));
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p0', 
+    ARRAY[19], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p0');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p20', 
+    ARRAY[20], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p20');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p40', 
+    ARRAY[11], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p40');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p0', 
+    ARRAY[19], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p0');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p20', 
+    ARRAY[20], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p20');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p40', 
+    ARRAY[11], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p40');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p0', 
+    ARRAY[19], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p0');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p20', 
+    ARRAY[20], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p20');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p40', 
+    ARRAY[11], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p40');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p0', 
+    ARRAY[19], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p0');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p20', 
+    ARRAY[20], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p20');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p40', 
+    ARRAY[11], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p40');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p0', 
+    ARRAY[19], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p0');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p20', 
+    ARRAY[20], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p20');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p40', 
+    ARRAY[11], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p40');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p0', 
+    ARRAY[19], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p0');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p20', 
+    ARRAY[20], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p20');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p40', 
+    ARRAY[11], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p40');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p0', 
+    ARRAY[19], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p0');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p20', 
+    ARRAY[20], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p20');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p40', 
+    ARRAY[11], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p40');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||'_p0', 
+    ARRAY[19], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||'_p0');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||'_p20', 
+    ARRAY[20], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||'_p20');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||'_p40', 
+    ARRAY[11], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||'_p40');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p0', 
+    ARRAY[19], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p0');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p20', 
+    ARRAY[20], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p20');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p40', 
+    ARRAY[11], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p40');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p0', 
+    ARRAY[19], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p0');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p20', 
+    ARRAY[20], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p20');
+SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p40', 
+    ARRAY[11], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p40');
+
+-- Insert data outside of id subpartition scope, but still in top time partition scope
+-- Re-add this when default partition is supported
+--INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP);
+--INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '1 day'::interval);
+--INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '2 days'::interval);
+--INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '3 days'::interval);
+--INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '4 days'::interval);
+--INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '5 days'::interval);
+--INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP - '1 day'::interval);
+--INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP - '2 days'::interval);
+--INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP - '3 days'::interval);
+--INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP - '4 days'::interval);
+--SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that top parent table has no data even tho sub-parents should now have some.');
+--SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 
+--    ARRAY[11], 'Check count from ONLY time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'));
+--SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 
+--    ARRAY[11], 'Check count from ONLY time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' (out of scope test)');
+--SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 
+--    ARRAY[11], 'Check count from ONLY time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' (out of scope test)');
+--SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 
+--    ARRAY[11], 'Check count from ONLY time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' (out of scope test)');
+--SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 
+--    ARRAY[11], 'Check count from ONLY time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' (out of scope test)');
+--SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 
+--    ARRAY[11], 'Check count from ONLY time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' (out of scope test)');
+--SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 
+--    ARRAY[11], 'Check count from ONLY time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||' (out of scope test)');
+--SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 
+--    ARRAY[11], 'Check count from ONLY time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' (out of scope test)');
+--SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 
+--    ARRAY[11], 'Check count from ONLY time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' (out of scope test)');
+--SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 
+--    ARRAY[11], 'Check count from ONLY time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' (out of scope test)');
+
+-- Insert data outside top time partition scope
+--INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(211,220), CURRENT_TIMESTAMP - '20 days'::interval);
+--SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[10], 'Check count from ONLY time_taptest_table (out of scope test)');
+-- Remove above data so that retention can be tested
+--DELETE FROM partman_test.time_taptest_table WHERE col1 >= 200 and col1 <= 220;
+
+-- END section for testing default partition
+
+-- Test dropping without retention set
+SELECT drop_partition_id ('partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), '20', p_keep_table := false);
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p0', 'Check that partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p0 was dropped with direct drop call');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p20', 'Check that partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p20 was not dropped with direct drop call');
+
+UPDATE part_config SET retention = '2 days', retention_keep_table = false WHERE parent_table = 'partman_test.time_taptest_table';
+UPDATE part_config SET retention = '25', retention_keep_table = false WHERE parent_table = 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD');
+UPDATE part_config SET retention = '25', retention_keep_table = false WHERE parent_table = 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD');
+UPDATE part_config SET retention = '25', retention_keep_table = false WHERE parent_table = 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD');
+UPDATE part_config SET retention = '25', retention_keep_table = false WHERE parent_table = 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD');
+UPDATE part_config SET retention = '25', retention_keep_table = false WHERE parent_table = 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD');
+UPDATE part_config SET retention = '25', retention_keep_table = false WHERE parent_table = 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD');
+UPDATE part_config SET retention = '25', retention_keep_table = false WHERE parent_table = 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD');
+UPDATE part_config SET retention = '25', retention_keep_table = false WHERE parent_table = 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD');
+UPDATE part_config SET retention = '25', retention_keep_table = false WHERE parent_table = 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD');
+
+-- Test dropping with it set
+SELECT drop_partition_id ('partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), p_keep_table := false);
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p0', 'Check that partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p0 was dropped with direct drop call');
+SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p20', 'Check that partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p20 was not dropped with direct drop call');
+
+-- Test retention with run_maintenance()
+SELECT run_maintenance();
+
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' was dropped');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p0', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p0 was dropped');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p20', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p20 was dropped');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p40', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p40 was dropped');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p60', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p60 was dropped');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p80', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p80 was dropped');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p100', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p100 was dropped');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p120', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p120 was dropped');
+
+SELECT is_empty('SELECT parent_table FROM part_config WHERE parent_table = ''time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'''',
+        'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' was removed from part_config');
+SELECT is_empty('SELECT parent_table FROM part_config WHERE parent_table = ''time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p0''',
+        'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p0 was removed from part_config');
+SELECT is_empty('SELECT parent_table FROM part_config WHERE parent_table = ''time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p20''',
+        'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p20 was removed from part_config');
+SELECT is_empty('SELECT parent_table FROM part_config WHERE parent_table = ''time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p40''',
+        'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p40 was removed from part_config');
+SELECT is_empty('SELECT parent_table FROM part_config WHERE parent_table = ''time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p60''',
+        'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p60 was removed from part_config');
+SELECT is_empty('SELECT parent_table FROM part_config WHERE parent_table = ''time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p80''',
+        'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p80 was removed from part_config');
+SELECT is_empty('SELECT parent_table FROM part_config WHERE parent_table = ''time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p100''',
+        'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p100 was removed from part_config');
+SELECT is_empty('SELECT parent_table FROM part_config WHERE parent_table = ''time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p120''',
+        'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||'_p120 was removed from part_config');
+
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' was dropped');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p0', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p0 was dropped');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p20', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p20 was dropped');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p40', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p40 was dropped');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p60', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p60 was dropped');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p80', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p80 was dropped');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p100', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p100 was dropped');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p120', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p120 was dropped');
+
+
+SELECT is_empty('SELECT parent_table FROM part_config WHERE parent_table = ''time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'''',
+        'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' was removed from part_config');
+SELECT is_empty('SELECT parent_table FROM part_config WHERE parent_table = ''time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p0''',
+        'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p0 was removed from part_config');
+SELECT is_empty('SELECT parent_table FROM part_config WHERE parent_table = ''time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p20''',
+        'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p20 was removed from part_config');
+SELECT is_empty('SELECT parent_table FROM part_config WHERE parent_table = ''time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p40''',
+        'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p40 was removed from part_config');
+SELECT is_empty('SELECT parent_table FROM part_config WHERE parent_table = ''time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p60''',
+        'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p60 was removed from part_config');
+SELECT is_empty('SELECT parent_table FROM part_config WHERE parent_table = ''time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p80''',
+        'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p80 was removed from part_config');
+SELECT is_empty('SELECT parent_table FROM part_config WHERE parent_table = ''time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p100''',
+        'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p100 was removed from part_config');
+SELECT is_empty('SELECT parent_table FROM part_config WHERE parent_table = ''time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p120''',
+        'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||'_p120 was removed from part_config');
+
+-- First undo all subpartitions
+SELECT undo_partition('partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 20, p_target_table := 'partman_test.undo_taptest', p_keep_table := false);
+SELECT undo_partition('partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 20, p_target_table := 'partman_test.undo_taptest', p_keep_table := false);
+SELECT undo_partition('partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 20, p_target_table := 'partman_test.undo_taptest', p_keep_table := false);
+SELECT undo_partition('partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 20, p_target_table := 'partman_test.undo_taptest', p_keep_table := false);
+SELECT undo_partition('partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 20, p_target_table := 'partman_test.undo_taptest', p_keep_table := false);
+SELECT undo_partition('partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 20, p_target_table := 'partman_test.undo_taptest', p_keep_table := false);
+SELECT undo_partition('partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 20, p_target_table := 'partman_test.undo_taptest', p_keep_table := false);
+SELECT undo_partition('partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 20, p_target_table := 'partman_test.undo_taptest', p_keep_table := false);
+SELECT undo_partition('partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 20, p_target_table := 'partman_test.undo_taptest', p_keep_table := false);
+SELECT undo_partition('partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 20, p_target_table := 'partman_test.undo_taptest', p_keep_table := false);
+SELECT undo_partition('partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 20, p_target_table := 'partman_test.undo_taptest', p_keep_table := false);
+SELECT undo_partition('partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 20, p_target_table := 'partman_test.undo_taptest', p_keep_table := false);
+
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p0', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p0 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p20', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p20 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p40', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p40 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p60', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p60 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p80', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p80 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p100', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p100 does not exist');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p0', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p0 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p20', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p20 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p40', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p40 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p60', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p60 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p80', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p80 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p100', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'_p100 does not exist');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||'_p0', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||'_p0 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||'_p20', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||'_p20 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||'_p40', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||'_p40 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||'_p60', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||'_p60 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||'_p80', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||'_p80 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p0', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p0 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p20', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p20 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p40', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p40 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p60', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p60 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p80', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p80 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p100', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p100 does not exist');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p0', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p0 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p20', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p20 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p40', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p40 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p60', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p60 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p80', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p80 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p0', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p0 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p20', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p20 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p40', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p40 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p60', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p60 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p80', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p80 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p0', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p0 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p20', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p20 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p40', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p40 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p60', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p60 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p80', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p80 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p0', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p0 does not exist');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p0', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p0 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p20', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p20 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p40', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p40 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p60', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p60 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p80', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p80 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p100', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'_p100 does not exist');
+
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p100', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'_p100 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p100', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'_p100 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p100', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'_p100 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p100', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'_p100 exists');
+SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p100', 
+    'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'_p100 exists');
+
+SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'''', 
+    'Check that partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' was removed from part_config');
+SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'''', 
+    'Check that partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' was removed from part_config');
+SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'''', 
+    'Check that partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' was removed from part_config');
+SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'''', 
+    'Check that partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' was removed from part_config');
+SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'''', 
+    'Check that partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' was removed from part_config');
+SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'''', 
+    'Check that partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' was removed from part_config');
+SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||'''', 
+    'Check that partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' was removed from part_config');
+SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||'''', 
+    'Check that partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' was removed from part_config');
+SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||'''', 
+    'Check that partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' was removed from part_config');
+SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||'''', 
+    'Check that partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||' was removed from part_config');
+SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||'''', 
+    'Check that partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||' was removed from part_config');
+SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||'''', 
+    'Check that partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' was removed from part_config');
+
+SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[0], 'Check that top parent is still empty');
+-- Rows are missing data for col1 values 1-19 due to retention drop above
+-- REDO this when default is supported to move data there
+--SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY[31], 'Check data got move to partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD') );
+--SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), ARRAY[31], 'Check data got move to partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD') );
+--SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), ARRAY[31], 'Check data got move to partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD') );
+--SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), ARRAY[31], 'Check data got move to partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD') );
+--SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY[31], 'Check data got move to partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD') );
+--SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), ARRAY[31], 'Check data got move to partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD') );
+--SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), ARRAY[31], 'Check data got move to partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD') );
+
+SELECT results_eq('SELECT count(*)::int FROM partman_test.undo_taptest', ARRAY[248], 'Check count from target of undo_partition');
+
+--Unable to undo_partition any further because col1 is duplicated in child tables, but parent key has it as a primary key. So done testing! :D
+
+SELECT * FROM finish();
+ROLLBACK;

programming_language: SQL
filename: pg_partman--4.5.0--4.5.1.sql
change_type: ModificationType.ADD
diff: @@ -0,0 +1,1375 @@
+-- Allow relation options set on the template table to be inherited on the child table. As of PG13 and earlier, relation options set on the parent are not being set on the child tables. Unknown if PG14 will handle this yet or not (Github PR #348).
+-- Fixed security issue that could allow arbitrary code execution using SECURITY DEFINER functions. Set explicit search_path to avoid this. Thanks to Github user @tapioaiven of Aiven Ltd for reporting the issue.
+-- Fixed several bugs in sub-partitioning when using a mixture of epoch and regular integer partitioning in the same partition set (Github Issue #357).
+
+
+CREATE OR REPLACE FUNCTION @extschema@.inherit_template_properties (p_parent_table text, p_child_schema text, p_child_tablename text) RETURNS boolean
+    LANGUAGE plpgsql 
+    AS $$
+DECLARE
+
+v_child_relkind         char;
+v_child_schema          text;
+v_child_tablename       text;
+v_child_unlogged        char;
+v_dupe_found            boolean := false;
+v_fk_list               record;
+v_index_list            record;
+v_inherit_fk            boolean;
+v_parent_index_list     record;
+v_parent_oid            oid;
+v_parent_table          text;
+v_relopt                record;
+v_sql                   text;
+v_template_oid          oid;
+v_template_schemaname   text;
+v_template_table        text;
+v_template_tablename    name;
+v_template_tablespace   name;
+v_template_unlogged     char;
+
+BEGIN
+/*
+ * Function to inherit the properties of the template table to newly created child tables.
+ * Currently used for PostgreSQL 10 to inherit indexes and FKs since that is not natively available
+ * For PG11, used to inherit non-partition-key unique indexes & primary keys
+ */
+
+SELECT parent_table, template_table, inherit_fk
+INTO v_parent_table, v_template_table, v_inherit_fk
+FROM @extschema@.part_config
+WHERE parent_table = p_parent_table;
+IF v_parent_table IS NULL THEN
+    RAISE EXCEPTION 'Given parent table has no configuration in pg_partman: %', p_parent_table;
+ELSIF v_template_table IS NULL THEN
+    RAISE EXCEPTION 'No template table set in configuration for given parent table: %', p_parent_table;
+END IF;
+
+SELECT c.oid INTO v_parent_oid
+FROM pg_catalog.pg_class c
+JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
+WHERE n.nspname = split_part(p_parent_table, '.', 1)::name
+AND c.relname = split_part(p_parent_table, '.', 2)::name;
+    IF v_parent_oid IS NULL THEN
+        RAISE EXCEPTION 'Unable to find given parent table in system catalogs: %', p_parent_table;
+    END IF;
+ 
+SELECT n.nspname, c.relname, c.relkind INTO v_child_schema, v_child_tablename, v_child_relkind
+FROM pg_catalog.pg_class c
+JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
+WHERE n.nspname = p_child_schema::name
+AND c.relname = p_child_tablename::name;
+    IF v_child_tablename IS NULL THEN
+        RAISE EXCEPTION 'Unable to find given child table in system catalogs: %.%', v_child_schema, v_child_tablename;
+    END IF;
+       
+IF v_child_relkind = 'p' THEN
+    -- Subpartitioned parent, do not apply properties
+    RAISE DEBUG 'inherit_template_properties: found given child is subpartition parent, so properties not inherited';
+    RETURN false;
+END IF;
+
+v_template_schemaname := split_part(v_template_table, '.', 1)::name;
+v_template_tablename :=  split_part(v_template_table, '.', 2)::name;
+
+SELECT c.oid, ts.spcname INTO v_template_oid, v_template_tablespace
+FROM pg_catalog.pg_class c
+JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
+LEFT OUTER JOIN pg_catalog.pg_tablespace ts ON c.reltablespace = ts.oid
+WHERE n.nspname = v_template_schemaname
+AND c.relname = v_template_tablename;
+    IF v_template_oid IS NULL THEN
+        RAISE EXCEPTION 'Unable to find configured template table in system catalogs: %', v_template_table;
+    END IF;
+
+-- Index creation (Required for all indexes in PG10. Only for non-unique, non-partition key indexes in PG11)
+IF current_setting('server_version_num')::int >= 100000 THEN
+    FOR v_index_list IN 
+        SELECT
+        array_to_string(regexp_matches(pg_get_indexdef(indexrelid), ' USING .*'),',') AS statement
+        , i.indisprimary
+        , i.indisunique
+        , ( SELECT array_agg( a.attname ORDER by x.r )
+            FROM pg_catalog.pg_attribute a
+            JOIN ( SELECT k, row_number() over () as r
+                    FROM unnest(i.indkey) k ) as x
+            ON a.attnum = x.k AND a.attrelid = i.indrelid
+        ) AS indkey_names
+        , c.relname AS index_name
+        , ts.spcname AS tablespace_name
+        FROM pg_catalog.pg_index i
+        JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid
+        LEFT OUTER JOIN pg_catalog.pg_tablespace ts ON c.reltablespace = ts.oid
+        WHERE i.indrelid = v_template_oid
+        AND i.indisvalid
+        ORDER BY 1
+    LOOP
+        v_dupe_found := false;
+
+        IF current_setting('server_version_num')::int >= 110000 THEN
+            FOR v_parent_index_list IN 
+                SELECT
+                array_to_string(regexp_matches(pg_get_indexdef(indexrelid), ' USING .*'),',') AS statement
+                , i.indisprimary
+                , ( SELECT array_agg( a.attname ORDER by x.r )
+                    FROM pg_catalog.pg_attribute a
+                    JOIN ( SELECT k, row_number() over () as r
+                            FROM unnest(i.indkey) k ) as x
+                    ON a.attnum = x.k AND a.attrelid = i.indrelid
+                ) AS indkey_names
+                FROM pg_catalog.pg_index i
+                WHERE i.indrelid = v_parent_oid
+                AND i.indisvalid
+                ORDER BY 1
+            LOOP
+
+                IF v_parent_index_list.indisprimary AND v_index_list.indisprimary THEN
+                    IF v_parent_index_list.indkey_names = v_index_list.indkey_names THEN
+                        RAISE DEBUG 'inherit_template_properties: Ignoring duplicate primary key on template table: % ', v_index_list.indkey_names;
+                        v_dupe_found := true;
+                        CONTINUE; -- only continue within this nested loop
+                    END IF;
+                END IF;
+
+                IF v_parent_index_list.statement = v_index_list.statement THEN
+                    RAISE DEBUG 'inherit_template_properties: Ignoring duplicate index on template table: %', v_index_list.statement;
+                    v_dupe_found := true;
+                    CONTINUE; -- only continue within this nested loop
+                END IF;
+
+            END LOOP; -- end parent index loop
+        END IF; -- End PG11 check
+
+        IF v_dupe_found = true THEN
+            -- Only used in PG11 and should skip trying to create indexes that already existed on the parent
+            CONTINUE;
+        END IF;
+
+        IF v_index_list.indisprimary THEN
+            v_sql := format('ALTER TABLE %I.%I ADD PRIMARY KEY (%s)'
+                            , v_child_schema
+                            , v_child_tablename
+                            , '"' || array_to_string(v_index_list.indkey_names, '","') || '"');
+            IF v_index_list.tablespace_name IS NOT NULL THEN
+                v_sql := v_sql || format(' USING INDEX TABLESPACE %I', v_index_list.tablespace_name);
+            END IF;
+            RAISE DEBUG 'inherit_template_properties: Create pk: %', v_sql;
+            EXECUTE v_sql;
+        ELSE
+            -- statement column should be just the portion of the index definition that defines what it actually is
+            v_sql := format('CREATE %s INDEX ON %I.%I %s', CASE WHEN v_index_list.indisunique = TRUE THEN 'UNIQUE' ELSE '' END, v_child_schema, v_child_tablename, v_index_list.statement);
+            IF v_index_list.tablespace_name IS NOT NULL THEN
+                v_sql := v_sql || format(' TABLESPACE %I', v_index_list.tablespace_name);
+            END IF;
+
+            RAISE DEBUG 'inherit_template_properties: Create index: %', v_sql;
+            EXECUTE v_sql;
+
+        END IF;
+
+    END LOOP;
+END IF; 
+-- End index creation
+
+-- Foreign key creation (PG10 only)
+IF current_setting('server_version_num')::int >= 100000 AND current_setting('server_version_num')::int < 110000 THEN
+    IF v_inherit_fk THEN
+        FOR v_fk_list IN 
+            SELECT pg_get_constraintdef(con.oid) AS constraint_def
+            FROM pg_catalog.pg_constraint con
+            JOIN pg_catalog.pg_class c ON con.conrelid = c.oid
+            WHERE c.oid = v_template_oid
+            AND contype = 'f'
+        LOOP
+            v_sql := format('ALTER TABLE %I.%I ADD %s', v_child_schema, v_child_tablename, v_fk_list.constraint_def);
+            RAISE DEBUG 'inherit_template_properties: Create FK: %', v_sql;
+            EXECUTE v_sql;
+        END LOOP;
+    END IF;
+END IF;
+-- End foreign key creation
+
+-- Tablespace inheritance on PG11 and earlier
+IF current_setting('server_version_num')::int < 120000 AND v_template_tablespace IS NOT NULL THEN
+    v_sql := format('ALTER TABLE %I.%I SET TABLESPACE %I', v_child_schema, v_child_tablename, v_template_tablespace);
+    RAISE DEBUG 'inherit_template_properties: Alter tablespace: %', v_sql;
+    EXECUTE v_sql;
+END IF;
+
+-- UNLOGGED status. Currently waiting on final stance of how native will handle this property being changed for its children. 
+-- See release notes for v4.2.0
+SELECT relpersistence INTO v_template_unlogged
+FROM pg_catalog.pg_class c
+JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
+WHERE n.nspname = v_template_schemaname
+AND c.relname = v_template_tablename;
+
+SELECT relpersistence INTO v_child_unlogged
+FROM pg_catalog.pg_class c
+JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
+WHERE n.nspname = v_child_schema::name
+AND c.relname = v_child_tablename::name;
+
+IF v_template_unlogged = 'u' AND v_child_unlogged = 'p'  THEN
+    v_sql := format ('ALTER TABLE %I.%I SET UNLOGGED', v_child_schema, v_child_tablename);
+    RAISE DEBUG 'inherit_template_properties: Alter UNLOGGED: %', v_sql;
+    EXECUTE v_sql;     
+ELSIF v_template_unlogged = 'p' AND v_child_unlogged = 'u'  THEN
+    v_sql := format ('ALTER TABLE %I.%I SET LOGGED', v_child_schema, v_child_tablename);
+    RAISE DEBUG 'inherit_template_properties: Alter UNLOGGED: %', v_sql;
+    EXECUTE v_sql;     
+END IF;
+
+-- Relation options are not being inherited for PG <= 13
+FOR v_relopt IN
+    SELECT unnest(reloptions) as value
+    FROM pg_catalog.pg_class
+    WHERE oid = v_template_oid
+LOOP
+    v_sql := format('ALTER TABLE %I.%I SET (%s)'
+                    , v_child_schema
+                    , v_child_tablename
+                    , v_relopt.value);
+    RAISE DEBUG 'inherit_template_properties: Set relopts: %', v_sql;
+    EXECUTE v_sql;
+END LOOP;
+RETURN true;
+
+END
+$$;
+
+
+CREATE OR REPLACE FUNCTION @extschema@.check_name_length (p_object_name text, p_suffix text DEFAULT NULL, p_table_partition boolean DEFAULT FALSE) RETURNS text
+    LANGUAGE plpgsql IMMUTABLE SECURITY DEFINER
+    SET search_path TO pg_catalog, pg_temp
+    AS $$
+DECLARE
+    v_new_length    int;
+    v_new_name      text;
+BEGIN
+/*
+ * Truncate the name of the given object if it is greater than the postgres default max (63 characters).
+ * Also appends given suffix and schema if given and truncates the name so that the entire suffix will fit.
+ * Returns original name (with suffix if given) if it doesn't require truncation
+ * Retains SECURITY DEFINER since it is called by trigger functions and did not want to break installations prior to 4.0.0
+ */
+
+IF p_table_partition IS TRUE AND (p_suffix IS NULL) THEN
+    RAISE EXCEPTION 'Table partition name requires a suffix value';
+END IF;
+
+IF p_table_partition THEN  -- 61 characters to account for _p in partition name
+    IF char_length(p_object_name) + char_length(p_suffix) >= 61 THEN
+        v_new_length := 61 - char_length(p_suffix);
+        v_new_name := substring(p_object_name from 1 for v_new_length) || '_p' || p_suffix; 
+    ELSE
+        v_new_name := p_object_name||'_p'||p_suffix;
+    END IF;
+ELSE
+    IF char_length(p_object_name) + char_length(COALESCE(p_suffix, '')) >= 63 THEN
+        v_new_length := 63 - char_length(COALESCE(p_suffix, ''));
+        v_new_name := substring(p_object_name from 1 for v_new_length) || COALESCE(p_suffix, ''); 
+    ELSE
+        v_new_name := p_object_name||COALESCE(p_suffix, '');
+    END IF;
+END IF;
+
+RETURN v_new_name;
+
+END
+$$;
+
+
+CREATE OR REPLACE FUNCTION @extschema@.create_sub_parent(
+    p_top_parent text
+    , p_control text
+    , p_type text
+    , p_interval text
+    , p_native_check text DEFAULT NULL
+    , p_constraint_cols text[] DEFAULT NULL 
+    , p_premake int DEFAULT 4
+    , p_start_partition text DEFAULT NULL
+    , p_inherit_fk boolean DEFAULT true
+    , p_epoch text DEFAULT 'none' 
+    , p_upsert text DEFAULT ''
+    , p_trigger_return_null boolean DEFAULT true
+    , p_jobmon boolean DEFAULT true) 
+RETURNS boolean
+    LANGUAGE plpgsql 
+    AS $$
+DECLARE
+
+v_child_interval        interval;
+v_child_start_id        bigint;
+v_child_start_time      timestamptz;
+v_control               text;
+v_control_parent_type   text;
+v_control_sub_type      text;
+v_last_partition        text;
+v_new_search_path       text := '@extschema@,pg_temp';
+v_old_search_path       text;
+v_parent_epoch          text;
+v_parent_interval       text;
+v_parent_relkind        char;
+v_parent_schema         text;
+v_parent_tablename      text;
+v_parent_type           text;
+v_part_col              text;
+v_partition_id_array    bigint[];
+v_partition_time_array  timestamptz[];
+v_relkind               char;
+v_recreate_child        boolean := false;
+v_row                   record;
+v_row_last_part         record;
+v_run_maint             boolean;
+v_sql                   text;
+v_success               boolean := false;
+v_template_table        text;
+v_top_type              text;
+
+BEGIN
+/*
+ * Create a partition set that is a subpartition of an already existing partition set.
+ * Given the parent table of any current partition set, it will turn all existing children into parent tables of their own partition sets
+ *      using the configuration options given as parameters to this function.
+ * Uses another config table that allows for turning all future child partitions into a new parent automatically.
+ */
+
+SELECT n.nspname, c.relname, c.relkind INTO v_parent_schema, v_parent_tablename, v_parent_relkind
+FROM pg_catalog.pg_class c
+JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
+WHERE n.nspname = split_part(p_top_parent, '.', 1)::name
+AND c.relname = split_part(p_top_parent, '.', 2)::name;
+    IF v_parent_tablename IS NULL THEN
+        RAISE EXCEPTION 'Unable to find given parent table in system catalogs. Please create parent table first: %', p_top_parent;
+    END IF;
+
+IF NOT @extschema@.check_partition_type(p_type) THEN
+    RAISE EXCEPTION '% is not a valid partitioning type', p_type;
+END IF;
+
+IF v_parent_relkind = 'p' AND p_type <> 'native' THEN
+    RAISE EXCEPTION 'Cannot create a non-native sub-partition of a native parent table. All levels of a sub-partition set must be either all native or all non-native';
+END IF;
+ 
+SELECT partition_type, partition_interval, control, automatic_maintenance, epoch, template_table
+INTO v_parent_type, v_parent_interval, v_control, v_run_maint, v_parent_epoch, v_template_table
+FROM @extschema@.part_config 
+WHERE parent_table = p_top_parent;
+IF v_parent_type IS NULL THEN
+    RAISE EXCEPTION 'Cannot subpartition a table that is not managed by pg_partman already. Given top parent table not found in @extschema@.part_config: %', p_top_parent;
+END IF;
+
+IF p_type = 'native' AND (lower(p_native_check) <> 'yes' OR p_native_check IS NULL) THEN
+    RAISE EXCEPTION 'The sub-partitioning of a natively partitioned table is a DESTRUCTIVE process unless all child tables are already natively subpartitioned. All child tables, and therefore ALL DATA, may be destroyed since the parent table must be declared as partitioned on first creation and cannot be altered later. See docs for more info. Set p_native_check parameter to "yes" if you are sure this is ok.';
+END IF;
+
+IF p_upsert <> '' THEN
+    IF current_setting('server_version_num')::int < 90500 THEN
+        RAISE EXCEPTION 'INSERT ... ON CONFLICT (UPSERT) feature is only supported in PostgreSQL 9.5 and later';
+    END IF;
+    IF p_type = 'native' THEN
+        RAISE EXCEPTION 'Native partitioning does not currently support upsert. Use pg_partman''s partitioning methods instead if this is required';
+    END IF;
+END IF;
+
+SELECT general_type INTO v_control_parent_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control);
+
+SELECT current_setting('search_path') INTO v_old_search_path;
+EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_new_search_path, 'false');
+
+-- Add the given parameters to the part_config_sub table first in case create_partition_* functions are called below 
+-- All sub-partition parents must use the same template table for native partitioning, so ensure the one from the given parent is obtained and used.
+INSERT INTO @extschema@.part_config_sub (
+    sub_parent
+    , sub_control
+    , sub_partition_type
+    , sub_partition_interval
+    , sub_constraint_cols
+    , sub_premake
+    , sub_inherit_fk
+    , sub_automatic_maintenance
+    , sub_epoch
+    , sub_upsert
+    , sub_jobmon
+    , sub_trigger_return_null
+    , sub_template_table)
+VALUES (
+    p_top_parent
+    , p_control
+    , p_type
+    , p_interval
+    , p_constraint_cols
+    , p_premake
+    , p_inherit_fk
+    , 'on' 
+    , p_epoch
+    , p_upsert
+    , p_jobmon
+    , p_trigger_return_null
+    , v_template_table);
+
+FOR v_row IN 
+    -- Loop through all current children to turn them into partitioned tables
+    SELECT partition_schemaname AS child_schema, partition_tablename AS child_tablename FROM @extschema@.show_partitions(p_top_parent)
+LOOP
+
+    SELECT general_type INTO v_control_sub_type FROM @extschema@.check_control_type(v_row.child_schema, v_row.child_tablename, p_control);
+
+    SELECT c.relkind INTO v_relkind
+    FROM pg_catalog.pg_class c
+    JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
+    WHERE n.nspname = v_row.child_schema
+    AND c.relname = v_row.child_tablename;
+
+    -- If both parent and sub-parent are the same partition type (time/id), ensure boundaries of sub-parent are within parent
+    IF (v_control_parent_type = 'time' AND v_control_sub_type = 'time') OR
+       (v_control_parent_type = 'id' AND v_parent_epoch <> 'none' AND v_control_sub_type = 'id' AND p_epoch <> 'none') THEN
+        CASE
+            WHEN p_interval = 'yearly' THEN
+                v_child_interval := '1 year';
+            WHEN p_interval = 'quarterly' THEN
+                v_child_interval := '3 months';
+            WHEN p_interval = 'monthly' THEN
+                v_child_interval := '1 month';
+            WHEN p_interval  = 'weekly' THEN
+                v_child_interval := '1 week';
+            WHEN p_interval = 'daily' THEN
+                v_child_interval := '1 day';
+            WHEN p_interval = 'hourly' THEN
+                v_child_interval := '1 hour';
+            WHEN p_interval = 'half-hour' THEN
+                v_child_interval := '30 mins';
+            WHEN p_interval = 'quarter-hour' THEN
+                v_child_interval := '15 mins';
+            ELSE
+                v_child_interval := p_interval::interval;
+                IF v_child_interval < '1 second'::interval THEN
+                    RAISE EXCEPTION 'Partitioning interval must be 1 second or greater';
+                END IF;
+        END CASE;
+
+        IF v_child_interval >= v_parent_interval::interval THEN
+            EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false');
+            RAISE EXCEPTION 'Sub-partition interval cannot be greater than or equal to the given parent interval';
+        END IF;
+        IF v_child_interval = '1 week' AND v_parent_interval::interval > '1 week'::interval THEN
+            EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false');
+            RAISE EXCEPTION 'Due to conflicting data boundaries between ISO weeks and any larger interval of time, pg_partman cannot support a sub-partition interval of weekly';
+        END IF;
+
+    ELSIF v_control_parent_type = 'id' AND v_control_sub_type = 'id' AND v_parent_epoch = 'none' AND p_epoch = 'none' THEN
+        IF p_interval::bigint >= v_parent_interval::bigint THEN
+            EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false');
+            RAISE EXCEPTION 'Sub-partition interval cannot be greater than or equal to the given parent interval';
+        END IF;
+    END IF;
+      
+    IF p_type = 'native' THEN
+        IF v_relkind <> 'p' THEN 
+            -- Not natively partitioned already. Drop it and recreate as such.
+            RAISE WARNING 'Child table % is not natively partitioned. Dropping and recreating with native partitioning'
+                            , v_row.child_schema||'.'||v_row.child_tablename;
+            SELECT child_start_time, child_start_id INTO v_child_start_time, v_child_start_id
+            FROM @extschema@.show_partition_info(v_row.child_schema||'.'||v_row.child_tablename
+                                                    , v_parent_interval
+                                                    , p_top_parent);
+            EXECUTE format('DROP TABLE %I.%I', v_row.child_schema, v_row.child_tablename);
+            v_recreate_child := true;
+
+            IF v_child_start_id IS NOT NULL THEN
+                v_partition_id_array[0] := v_child_start_id;
+                PERFORM @extschema@.create_partition_id(p_top_parent, v_partition_id_array, true, p_start_partition);
+            ELSIF v_child_start_time IS NOT NULL THEN
+                v_partition_time_array[0] := v_child_start_time;
+                PERFORM @extschema@.create_partition_time(p_top_parent, v_partition_time_array, true, p_start_partition);
+            END IF;
+        ELSE
+            SELECT a.attname
+            INTO v_part_col
+            FROM pg_attribute a
+            JOIN pg_class c ON a.attrelid = c.oid
+            JOIN pg_namespace n ON c.relnamespace = n.oid
+            WHERE n.nspname = v_row.child_schema::name
+            AND c.relname = v_row.child_tablename::name
+            AND attnum IN (SELECT unnest(partattrs) FROM pg_partitioned_table p WHERE a.attrelid = p.partrelid);
+
+            IF p_control <> v_part_col THEN
+                RAISE EXCEPTION 'Attempted to natively sub-partition an existing table that has the partition column (%) defined differently than the control column given (%)', v_part_col, p_control;
+            ELSE -- Child table is already natively subpartitioned properly. Skip the rest.
+                CONTINUE;
+            END IF;
+        END IF; -- end 'p' relkind check
+
+    END IF; -- end native check
+
+    IF v_recreate_child = false THEN
+    -- Always call create_parent() if child table wasn't recreated above.
+    -- If it was, the create_partition_*() functions called above also call create_parent if any of the tables
+    --  it creates are in the part_config_sub table. Since it was inserted there above,
+    --  it should call it appropriately
+        v_sql := format('SELECT @extschema@.create_parent(
+                 p_parent_table := %L
+                , p_control := %L
+                , p_type := %L
+                , p_interval := %L
+                , p_constraint_cols := %L
+                , p_premake := %L
+                , p_automatic_maintenance := %L
+                , p_start_partition := %L
+                , p_inherit_fk := %L
+                , p_epoch := %L
+                , p_upsert := %L
+                , p_trigger_return_null := %L
+                , p_template_table := %L
+                , p_jobmon := %L)'
+            , v_row.child_schema||'.'||v_row.child_tablename
+            , p_control
+            , p_type
+            , p_interval
+            , p_constraint_cols
+            , p_premake
+            , 'on'
+            , p_start_partition
+            , p_inherit_fk
+            , p_epoch
+            , p_upsert
+            , p_trigger_return_null
+            , v_template_table
+            , p_jobmon);
+        EXECUTE v_sql;
+    END IF; -- end recreate check
+
+END LOOP;
+
+v_success := true;
+
+EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false');
+
+RETURN v_success;
+
+END
+$$;
+
+
+CREATE OR REPLACE FUNCTION @extschema@.check_epoch_type (p_type text) RETURNS boolean
+    LANGUAGE plpgsql IMMUTABLE SECURITY DEFINER
+    SET search_path TO pg_catalog, pg_temp
+    AS $$
+DECLARE
+v_result    boolean;
+BEGIN
+    SELECT p_type IN ('none', 'seconds', 'milliseconds') INTO v_result;
+    RETURN v_result;
+END
+$$;
+
+
+CREATE OR REPLACE FUNCTION @extschema@.check_partition_type (p_type text) RETURNS boolean
+    LANGUAGE plpgsql IMMUTABLE SECURITY DEFINER
+    SET search_path TO pg_catalog, pg_temp
+    AS $$
+DECLARE
+v_result    boolean;
+BEGIN
+    SELECT p_type IN ('partman', 'time-custom', 'native') INTO v_result;
+    RETURN v_result;
+END
+$$;
+
+
+CREATE OR REPLACE FUNCTION @extschema@.create_parent(
+    p_parent_table text
+    , p_control text
+    , p_type text
+    , p_interval text
+    , p_constraint_cols text[] DEFAULT NULL 
+    , p_premake int DEFAULT 4
+    , p_automatic_maintenance text DEFAULT 'on' 
+    , p_start_partition text DEFAULT NULL
+    , p_inherit_fk boolean DEFAULT true
+    , p_epoch text DEFAULT 'none' 
+    , p_upsert text DEFAULT ''
+    , p_publications text[] DEFAULT NULL
+    , p_trigger_return_null boolean DEFAULT true
+    , p_template_table text DEFAULT NULL
+    , p_jobmon boolean DEFAULT true) 
+RETURNS boolean 
+    LANGUAGE plpgsql 
+    AS $$
+DECLARE
+
+ex_context                      text;
+ex_detail                       text;
+ex_hint                         text;
+ex_message                      text;
+v_partattrs                     smallint[];
+v_base_timestamp                timestamptz;
+v_count                         int := 1;
+v_control_type                  text;
+v_control_exact_type            text;
+v_datetime_string               text;
+v_default_partition             text;
+v_higher_control_type           text;
+v_higher_parent_control         text;
+v_higher_parent_epoch           text;
+v_higher_parent_schema          text := split_part(p_parent_table, '.', 1);
+v_higher_parent_table           text := split_part(p_parent_table, '.', 2);
+v_id_interval                   bigint;
+v_inherit_privileges            boolean := false;
+v_job_id                        bigint;
+v_jobmon_schema                 text;
+v_last_partition_created        boolean;
+v_max                           bigint;
+v_native_sub_control            text;
+v_notnull                       boolean;
+v_new_search_path               text := '@extschema@,pg_temp';
+v_old_search_path               text;
+v_parent_owner                  text;
+v_parent_partition_id           bigint;
+v_parent_partition_timestamp    timestamptz;
+v_parent_schema                 text;
+v_parent_tablename              text;
+v_parent_tablespace             text;
+v_part_col                      text;
+v_part_type                     text;
+v_partition_time                timestamptz;
+v_partition_time_array          timestamptz[];
+v_partition_id_array            bigint[];
+v_partstrat                     char;
+v_publication_exists            text;
+v_row                           record;
+v_sql                           text;
+v_start_time                    timestamptz;
+v_starting_partition_id         bigint;
+v_step_id                       bigint;
+v_step_overflow_id              bigint;
+v_sub_parent                    text;
+v_success                       boolean := false;
+v_template_schema               text;
+v_template_tablename            text;
+v_time_interval                 interval;
+v_top_datetime_string           text;
+v_top_parent_schema             text := split_part(p_parent_table, '.', 1);
+v_top_parent_table              text := split_part(p_parent_table, '.', 2);
+v_unlogged                      char;
+
+BEGIN
+/*
+ * Function to turn a table into the parent of a partition set
+ */
+
+IF position('.' in p_parent_table) = 0  THEN
+    RAISE EXCEPTION 'Parent table must be schema qualified';
+END IF;
+
+IF p_upsert <> '' THEN
+    IF current_setting('server_version_num')::int < 90500 THEN
+        RAISE EXCEPTION 'INSERT ... ON CONFLICT (UPSERT) feature is only supported in PostgreSQL 9.5 and later';
+    END IF;
+    IF p_type = 'native' THEN
+        RAISE EXCEPTION 'Native partitioning does not currently support upsert. Use pg_partman''s partitioning methods instead if this is required';
+    END IF;
+END IF;
+
+SELECT n.nspname, c.relname, t.spcname, c.relpersistence
+INTO v_parent_schema, v_parent_tablename, v_parent_tablespace, v_unlogged
+FROM pg_catalog.pg_class c
+JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
+LEFT OUTER JOIN pg_catalog.pg_tablespace t ON c.reltablespace = t.oid
+WHERE n.nspname = split_part(p_parent_table, '.', 1)::name
+AND c.relname = split_part(p_parent_table, '.', 2)::name;
+    IF v_parent_tablename IS NULL THEN
+        RAISE EXCEPTION 'Unable to find given parent table in system catalogs. Please create parent table first: %', p_parent_table;
+    END IF;
+    
+SELECT attnotnull INTO v_notnull 
+FROM pg_catalog.pg_attribute a
+JOIN pg_catalog.pg_class c ON a.attrelid = c.oid
+JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
+WHERE c.relname = v_parent_tablename::name
+AND n.nspname = v_parent_schema::name
+AND a.attname = p_control::name;
+    IF p_type <> 'native' AND (v_notnull = false OR v_notnull IS NULL) THEN
+        RAISE EXCEPTION 'Control column given (%) for parent table (%) does not exist or must be set to NOT NULL', p_control, p_parent_table;
+    END IF;
+
+SELECT general_type, exact_type INTO v_control_type, v_control_exact_type
+FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, p_control);
+
+IF v_control_type IS NULL THEN
+    RAISE EXCEPTION 'pg_partman only supports partitioning of data types that are integer or date/timestamp. Supplied column is of type %', v_control_exact_type;
+END IF;
+
+IF (p_epoch <> 'none' AND v_control_type <> 'id') THEN
+    RAISE EXCEPTION 'p_epoch can only be used with an integer based control column and does not work for native partitioning';
+END IF;
+
+
+IF NOT @extschema@.check_partition_type(p_type) THEN
+    RAISE EXCEPTION '% is not a valid partitioning type for pg_partman', p_type;
+END IF;
+
+IF p_type = 'native' THEN
+
+    IF current_setting('server_version_num')::int < 100000 THEN
+        RAISE EXCEPTION 'Native partitioning only available in PostgreSQL versions 10.0+';
+    END IF;
+    -- Check if given parent table has been already set up as a partitioned table and is ranged
+    SELECT p.partstrat, partattrs INTO v_partstrat, v_partattrs
+    FROM pg_catalog.pg_partitioned_table p
+    JOIN pg_catalog.pg_class c ON p.partrelid = c.oid
+    JOIN pg_namespace n ON c.relnamespace = n.oid
+    WHERE n.nspname = v_parent_schema::name 
+    AND c.relname = v_parent_tablename::name;
+
+    IF v_partstrat <> 'r' OR v_partstrat IS NULL THEN
+        RAISE EXCEPTION 'When using native partitioning, you must have created the given parent table as ranged (not list) partitioned already. Ex: CREATE TABLE ... PARTITION BY RANGE ...)';
+    END IF;
+
+    IF array_length(v_partattrs, 1) > 1 THEN
+        RAISE NOTICE 'pg_partman only supports single column native partitioning at this time. Found % columns in given parent definition.', array_length(v_partattrs, 1);
+    END IF;
+
+    SELECT a.attname, t.typname
+    INTO v_part_col, v_part_type
+    FROM pg_attribute a
+    JOIN pg_class c ON a.attrelid = c.oid
+    JOIN pg_namespace n ON c.relnamespace = n.oid
+    JOIN pg_type t ON a.atttypid = t.oid
+    WHERE n.nspname = v_parent_schema::name
+    AND c.relname = v_parent_tablename::name
+    AND attnum IN (SELECT unnest(partattrs) FROM pg_partitioned_table p WHERE a.attrelid = p.partrelid);
+
+    IF p_control <> v_part_col OR v_control_exact_type <> v_part_type THEN
+        RAISE EXCEPTION 'Control column and type given in arguments (%, %) does not match the control column and type of the given native partition set (%, %)', p_control, v_control_exact_type, v_part_col, v_part_type;
+    END IF;
+
+    -- Check that control column is a usable type for pg_partman.
+    IF v_control_type NOT IN ('time', 'id') THEN
+        RAISE EXCEPTION 'Only date/time or integer types are allowed for the control column with native partitioning.';
+    END IF;
+
+    -- Table to handle properties not natively inherited yet (indexes, fks, etc)
+    IF p_template_table IS NULL THEN
+        v_template_schema := '@extschema@';
+        v_template_tablename := @extschema@.check_name_length('template_'||v_parent_schema||'_'||v_parent_tablename);
+        EXECUTE format('CREATE TABLE IF NOT EXISTS %I.%I (LIKE %I.%I)', '@extschema@', v_template_tablename, v_parent_schema, v_parent_tablename);
+
+        SELECT pg_get_userbyid(c.relowner) INTO v_parent_owner 
+        FROM pg_catalog.pg_class c
+        JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
+        WHERE n.nspname = v_parent_schema::name 
+        AND c.relname = v_parent_tablename::name;
+
+        EXECUTE format('ALTER TABLE %I.%I OWNER TO %I'
+                , '@extschema@' 
+                , v_template_tablename 
+                , v_parent_owner);
+    ELSE
+        SELECT n.nspname, c.relname INTO v_template_schema, v_template_tablename
+        FROM pg_catalog.pg_class c
+        JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
+        WHERE n.nspname = split_part(p_template_table, '.', 1)::name
+        AND c.relname = split_part(p_template_table, '.', 2)::name;
+            IF v_template_tablename IS NULL THEN
+                RAISE EXCEPTION 'Unable to find given template table in system catalogs (%). Please create template table first or leave parameter NULL to have a default one created for you.', p_parent_table;
+            END IF;
+    END IF;
+
+ELSE -- if not native 
+
+    IF current_setting('server_version_num')::int >= 100000 THEN
+        SELECT p.partstrat INTO v_partstrat
+        FROM pg_catalog.pg_partitioned_table p
+        JOIN pg_catalog.pg_class c ON p.partrelid = c.oid
+        JOIN pg_namespace n ON c.relnamespace = n.oid
+        WHERE n.nspname = v_parent_schema::name 
+        AND c.relname = v_parent_tablename::name;
+    END IF;
+
+    IF v_partstrat IS NOT NULL THEN
+        RAISE EXCEPTION 'Given parent table has been set up with native partitioning therefore cannot be used with pg_partman''s other partitioning types. Either recreate table non-native or set the type argument to ''native''';
+    END IF;
+
+END IF; -- end if "native" check
+
+
+IF p_publications IS NOT NULL THEN
+    IF current_setting('server_version_num')::int < 100000 THEN
+        RAISE EXCEPTION 'p_publications argument not null but CREATE PUBLICATION is only available in PostgreSQL versions 10.0+';
+    END IF;
+    IF p_publications = '{}' THEN
+        RAISE EXCEPTION 'p_publications cannot be an empty set';
+    END IF;
+    FOR v_row IN 
+        SELECT unnest(p_publications) AS pubname
+    LOOP
+        SELECT pubname INTO v_publication_exists FROM pg_catalog.pg_publication where pubname = v_row.pubname::name;
+        IF v_publication_exists IS NULL THEN
+            RAISE EXCEPTION 'Given publication name (%) does not exist in system catalog. Ensure it is created first.', v_row.pubname;
+        END IF;
+    END LOOP;
+END IF;
+
+-- Only inherit parent ownership/privileges on non-native sets by default
+-- This is false by default so initial partition set creation doesn't require superuser.
+IF p_type = 'native' THEN
+    v_inherit_privileges = false;
+ELSE
+    v_inherit_privileges  = true;
+END IF;
+
+SELECT current_setting('search_path') INTO v_old_search_path;
+IF p_jobmon THEN
+    SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon'::name AND e.extnamespace = n.oid;
+    IF v_jobmon_schema IS NOT NULL THEN
+        v_new_search_path := '@extschema@,'||v_jobmon_schema||',pg_temp';
+    END IF;
+END IF;
+EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_new_search_path, 'false');
+
+EXECUTE format('LOCK TABLE %I.%I IN ACCESS EXCLUSIVE MODE', v_parent_schema, v_parent_tablename);
+
+IF v_jobmon_schema IS NOT NULL THEN
+    v_job_id := add_job(format('PARTMAN SETUP PARENT: %s', p_parent_table));
+    v_step_id := add_step(v_job_id, format('Creating initial partitions on new parent table: %s', p_parent_table));
+END IF;
+
+-- If this parent table has siblings that are also partitioned (subpartitions), ensure this parent gets added to part_config_sub table so future maintenance will subpartition it
+-- Just doing in a loop to avoid having to assign a bunch of variables (should only run once, if at all; constraint should enforce only one value.)
+FOR v_row IN 
+    WITH parent_table AS (
+        SELECT h.inhparent AS parent_oid
+        FROM pg_catalog.pg_inherits h
+        JOIN pg_catalog.pg_class c ON h.inhrelid = c.oid
+        JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
+        WHERE c.relname = v_parent_tablename::name
+        AND n.nspname = v_parent_schema::name
+    ), sibling_children AS (
+        SELECT i.inhrelid::regclass::text AS tablename 
+        FROM pg_inherits i
+        JOIN parent_table p ON i.inhparent = p.parent_oid
+    )
+    -- This column list must be kept consistent between: 
+    --   create_parent, check_subpart_sameconfig, create_partition_id, create_partition_time, dump_partitioned_table_definition and table definition
+    SELECT DISTINCT sub_partition_type
+        , sub_control
+        , sub_partition_interval
+        , sub_constraint_cols
+        , sub_premake
+        , sub_inherit_fk
+        , sub_retention
+        , sub_retention_schema
+        , sub_retention_keep_table
+        , sub_retention_keep_index
+        , sub_automatic_maintenance
+        , sub_epoch
+        , sub_optimize_trigger
+        , sub_optimize_constraint
+        , sub_infinite_time_partitions
+        , sub_jobmon
+        , sub_trigger_exception_handling
+        , sub_upsert
+        , sub_trigger_return_null
+        , sub_template_table
+        , sub_inherit_privileges
+        , sub_constraint_valid
+        , sub_subscription_refresh
+    FROM @extschema@.part_config_sub a
+    JOIN sibling_children b on a.sub_parent = b.tablename LIMIT 1
+LOOP
+    INSERT INTO @extschema@.part_config_sub (
+        sub_parent
+        , sub_partition_type
+        , sub_control
+        , sub_partition_interval
+        , sub_constraint_cols
+        , sub_premake
+        , sub_inherit_fk
+        , sub_retention
+        , sub_retention_schema
+        , sub_retention_keep_table
+        , sub_retention_keep_index
+        , sub_automatic_maintenance
+        , sub_epoch
+        , sub_optimize_trigger
+        , sub_optimize_constraint
+        , sub_infinite_time_partitions
+        , sub_jobmon
+        , sub_trigger_exception_handling
+        , sub_upsert
+        , sub_trigger_return_null
+        , sub_template_table
+        , sub_inherit_privileges
+        , sub_constraint_valid
+        , sub_subscription_refresh)
+    VALUES (
+        p_parent_table
+        , v_row.sub_partition_type
+        , v_row.sub_control
+        , v_row.sub_partition_interval
+        , v_row.sub_constraint_cols
+        , v_row.sub_premake
+        , v_row.sub_inherit_fk
+        , v_row.sub_retention
+        , v_row.sub_retention_schema
+        , v_row.sub_retention_keep_table
+        , v_row.sub_retention_keep_index
+        , v_row.sub_automatic_maintenance
+        , v_row.sub_epoch
+        , v_row.sub_optimize_trigger
+        , v_row.sub_optimize_constraint
+        , v_row.sub_infinite_time_partitions
+        , v_row.sub_jobmon
+        , v_row.sub_trigger_exception_handling
+        , v_row.sub_upsert
+        , v_row.sub_trigger_return_null
+        , v_row.sub_template_table
+        , v_row.sub_inherit_privileges
+        , v_row.sub_constraint_valid
+        , v_row.sub_subscription_refresh);
+
+    -- Set this equal to sibling configs so that newly created child table 
+    -- privileges are set properly below during initial setup.
+    -- This setting is special because it applies immediately to the new child 
+    -- tables of a given parent, not just during maintenance like most other settings.
+    v_inherit_privileges = v_row.sub_inherit_privileges;
+END LOOP;
+
+IF v_control_type = 'time' OR (v_control_type = 'id' AND p_epoch <> 'none') THEN
+
+    CASE
+        WHEN p_interval = 'yearly' THEN
+            v_time_interval := '1 year';
+        WHEN p_interval = 'quarterly' THEN
+            v_time_interval := '3 months';
+        WHEN p_interval = 'monthly' THEN
+            v_time_interval := '1 month';
+        WHEN p_interval  = 'weekly' THEN
+            v_time_interval := '1 week';
+        WHEN p_interval = 'daily' THEN
+            v_time_interval := '1 day';
+        WHEN p_interval = 'hourly' THEN
+            v_time_interval := '1 hour';
+        WHEN p_interval = 'half-hour' THEN
+            v_time_interval := '30 mins';
+        WHEN p_interval = 'quarter-hour' THEN
+            v_time_interval := '15 mins';
+        ELSE
+            IF p_type <> 'native' THEN
+                -- Reset for use as part_config type value below
+                p_type = 'time-custom';
+            END IF;
+            v_time_interval := p_interval::interval;
+            IF v_time_interval < '1 second'::interval THEN
+                RAISE EXCEPTION 'Partitioning interval must be 1 second or greater';
+            END IF;
+    END CASE;
+
+   -- First partition is either the min premake or p_start_partition
+    v_start_time := COALESCE(p_start_partition::timestamptz, CURRENT_TIMESTAMP - (v_time_interval * p_premake));
+
+    IF v_time_interval >= '1 year' THEN
+        v_base_timestamp := date_trunc('year', v_start_time);
+        IF v_time_interval >= '10 years' THEN
+            v_base_timestamp := date_trunc('decade', v_start_time);
+            IF v_time_interval >= '100 years' THEN
+                v_base_timestamp := date_trunc('century', v_start_time);
+                IF v_time_interval >= '1000 years' THEN
+                    v_base_timestamp := date_trunc('millennium', v_start_time);
+                END IF; -- 1000
+            END IF; -- 100
+        END IF; -- 10
+    END IF; -- 1
+
+    v_datetime_string := 'YYYY';
+    IF v_time_interval < '1 year' THEN
+        IF p_interval = 'quarterly' THEN
+            v_base_timestamp := date_trunc('quarter', v_start_time);
+            v_datetime_string = 'YYYY"q"Q';
+        ELSE
+            v_base_timestamp := date_trunc('month', v_start_time); 
+            v_datetime_string := v_datetime_string || '_MM';
+        END IF;
+        IF v_time_interval < '1 month' THEN
+            IF p_interval = 'weekly' THEN
+                v_base_timestamp := date_trunc('week', v_start_time);
+                v_datetime_string := 'IYYY"w"IW';
+            ELSE 
+                v_base_timestamp := date_trunc('day', v_start_time);
+                v_datetime_string := v_datetime_string || '_DD';
+            END IF;
+            IF v_time_interval < '1 day' THEN
+                v_base_timestamp := date_trunc('hour', v_start_time);
+                v_datetime_string := v_datetime_string || '_HH24MI';
+                IF v_time_interval < '1 minute' THEN
+                    v_base_timestamp := date_trunc('minute', v_start_time);
+                    v_datetime_string := v_datetime_string || 'SS';
+                END IF; -- minute
+            END IF; -- day
+        END IF; -- month
+    END IF; -- year
+
+    v_partition_time_array := array_append(v_partition_time_array, v_base_timestamp);
+    LOOP
+        -- If current loop value is less than or equal to the value of the max premake, add time to array.
+        IF (v_base_timestamp + (v_time_interval * v_count)) < (CURRENT_TIMESTAMP + (v_time_interval * p_premake)) THEN
+            BEGIN
+                v_partition_time := (v_base_timestamp + (v_time_interval * v_count))::timestamptz;
+                v_partition_time_array := array_append(v_partition_time_array, v_partition_time);
+            EXCEPTION WHEN datetime_field_overflow THEN
+                RAISE WARNING 'Attempted partition time interval is outside PostgreSQL''s supported time range. 
+                    Child partition creation after time % skipped', v_partition_time;
+                v_step_overflow_id := add_step(v_job_id, 'Attempted partition time interval is outside PostgreSQL''s supported time range.');
+                PERFORM update_step(v_step_overflow_id, 'CRITICAL', 'Child partition creation after time '||v_partition_time||' skipped');
+                CONTINUE;
+            END;
+        ELSE
+            EXIT; -- all needed partitions added to array. Exit the loop.
+        END IF;
+        v_count := v_count + 1;
+    END LOOP;
+
+    INSERT INTO @extschema@.part_config (
+        parent_table
+        , partition_type
+        , partition_interval
+        , epoch
+        , control
+        , premake
+        , constraint_cols
+        , datetime_string
+        , automatic_maintenance
+        , inherit_fk
+        , jobmon 
+        , upsert
+        , trigger_return_null
+        , template_table
+        , publications
+        , inherit_privileges)
+    VALUES (
+        p_parent_table
+        , p_type
+        , v_time_interval
+        , p_epoch
+        , p_control
+        , p_premake
+        , p_constraint_cols
+        , v_datetime_string
+        , p_automatic_maintenance
+        , p_inherit_fk
+        , p_jobmon
+        , p_upsert
+        , p_trigger_return_null
+        , v_template_schema||'.'||v_template_tablename
+        , p_publications
+        , v_inherit_privileges); 
+
+    RAISE DEBUG 'create_parent: v_partition_time_array: %', v_partition_time_array;
+
+    v_last_partition_created := @extschema@.create_partition_time(p_parent_table, v_partition_time_array, false);
+
+    IF v_last_partition_created = false THEN 
+        -- This can happen with subpartitioning when future or past partitions prevent child creation because they're out of range of the parent
+        -- First see if this parent is a subpartition managed by pg_partman
+        WITH top_oid AS (
+            SELECT i.inhparent AS top_parent_oid
+            FROM pg_catalog.pg_inherits i
+            JOIN pg_catalog.pg_class c ON c.oid = i.inhrelid
+            JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
+            WHERE c.relname = v_parent_tablename::name
+            AND n.nspname = v_parent_schema::name
+        ) SELECT n.nspname, c.relname 
+        INTO v_top_parent_schema, v_top_parent_table 
+        FROM pg_catalog.pg_class c
+        JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
+        JOIN top_oid t ON c.oid = t.top_parent_oid
+        JOIN @extschema@.part_config p ON p.parent_table = n.nspname||'.'||c.relname;
+
+        IF v_top_parent_table IS NOT NULL THEN
+            -- If so create the lowest possible partition that is within the boundary of the parent
+            SELECT child_start_time INTO v_parent_partition_timestamp FROM @extschema@.show_partition_info(p_parent_table, p_parent_table := v_top_parent_schema||'.'||v_top_parent_table);
+            IF v_base_timestamp >= v_parent_partition_timestamp THEN
+                WHILE v_base_timestamp >= v_parent_partition_timestamp LOOP
+                    v_base_timestamp := v_base_timestamp - v_time_interval;
+                END LOOP;
+                v_base_timestamp := v_base_timestamp + v_time_interval; -- add one back since while loop set it one lower than is needed
+            ELSIF v_base_timestamp < v_parent_partition_timestamp THEN
+                WHILE v_base_timestamp < v_parent_partition_timestamp LOOP
+                    v_base_timestamp := v_base_timestamp + v_time_interval;
+                END LOOP;
+                -- Don't need to remove one since new starting time will fit in top parent interval
+            END IF;
+            v_partition_time_array := NULL;
+            v_partition_time_array := array_append(v_partition_time_array, v_base_timestamp);
+            v_last_partition_created := @extschema@.create_partition_time(p_parent_table, v_partition_time_array, false);
+        ELSE
+            RAISE WARNING 'No child tables created. Check that all child tables did not already exist and may not have been part of partition set. Given parent has still been configured with pg_partman, but may not have expected children. Please review schema and config to confirm things are ok.';
+
+            IF v_jobmon_schema IS NOT NULL THEN
+                PERFORM update_step(v_step_id, 'OK', 'Done');
+                IF v_step_overflow_id IS NOT NULL THEN
+                    PERFORM fail_job(v_job_id);
+                ELSE
+                    PERFORM close_job(v_job_id);
+                END IF;
+            END IF;
+
+            EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false');
+
+            RETURN v_success;
+        END IF; 
+    END IF; -- End v_last_partition IF
+
+    IF v_jobmon_schema IS NOT NULL THEN
+        PERFORM update_step(v_step_id, 'OK', format('Time partitions premade: %s', p_premake));
+    END IF;
+
+END IF;
+
+IF v_control_type = 'id' AND p_epoch = 'none' THEN
+    v_id_interval := p_interval::bigint;
+    IF p_type <> 'native' AND v_id_interval < 10 THEN
+        RAISE EXCEPTION 'Interval for serial, non-native partitioning must be greater than or equal to 10';
+    END IF;
+
+    -- Check if parent table is a subpartition of an already existing id partition set managed by pg_partman. 
+    WHILE v_higher_parent_table IS NOT NULL LOOP -- initially set in DECLARE
+        WITH top_oid AS (
+            SELECT i.inhparent AS top_parent_oid
+            FROM pg_catalog.pg_inherits i
+            JOIN pg_catalog.pg_class c ON c.oid = i.inhrelid
+            JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
+            WHERE n.nspname = v_higher_parent_schema::name
+            AND c.relname = v_higher_parent_table::name
+        ) SELECT n.nspname, c.relname, p.control, p.epoch
+        INTO v_higher_parent_schema, v_higher_parent_table, v_higher_parent_control, v_higher_parent_epoch
+        FROM pg_catalog.pg_class c
+        JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
+        JOIN top_oid t ON c.oid = t.top_parent_oid
+        JOIN @extschema@.part_config p ON p.parent_table = n.nspname||'.'||c.relname;
+
+        IF v_higher_parent_table IS NOT NULL THEN
+            SELECT general_type INTO v_higher_control_type
+            FROM @extschema@.check_control_type(v_higher_parent_schema, v_higher_parent_table, v_higher_parent_control);
+            IF v_higher_control_type <> 'id' or (v_higher_control_type = 'id' AND v_higher_parent_epoch <> 'none') THEN
+                -- The parent above the p_parent_table parameter is not partitioned by ID
+                --   so don't check for max values in parents that aren't partitioned by ID.
+                -- This avoids missing child tables in subpartition sets that have differing ID data
+                EXIT;
+            END IF;
+            -- v_top_parent initially set in DECLARE
+            v_top_parent_schema := v_higher_parent_schema;
+            v_top_parent_table := v_higher_parent_table;
+        END IF;
+    END LOOP;
+
+    -- If custom start partition is set, use that.
+    -- If custom start is not set and there is already data, start partitioning with the highest current value and ensure it's grabbed from highest top parent table
+    IF p_start_partition IS NOT NULL THEN
+        v_max := p_start_partition::bigint;
+    ELSE
+        v_sql := format('SELECT COALESCE(max(%I)::bigint, 0) FROM %I.%I LIMIT 1'
+                    , p_control
+                    , v_top_parent_schema
+                    , v_top_parent_table);
+        EXECUTE v_sql INTO v_max;
+    END IF;
+
+    v_starting_partition_id := v_max - (v_max % v_id_interval);
+    FOR i IN 0..p_premake LOOP
+        -- Only make previous partitions if ID value is less than the starting value and positive (and custom start partition wasn't set)
+        IF p_start_partition IS NULL AND 
+            (v_starting_partition_id - (v_id_interval*i)) > 0 AND 
+            (v_starting_partition_id - (v_id_interval*i)) < v_starting_partition_id 
+        THEN
+            v_partition_id_array = array_append(v_partition_id_array, (v_starting_partition_id - v_id_interval*i));
+        END IF; 
+        v_partition_id_array = array_append(v_partition_id_array, (v_id_interval*i) + v_starting_partition_id);
+    END LOOP;
+
+    INSERT INTO @extschema@.part_config (
+        parent_table
+        , partition_type
+        , partition_interval
+        , control
+        , premake
+        , constraint_cols
+        , automatic_maintenance
+        , inherit_fk
+        , jobmon
+        , upsert
+        , trigger_return_null
+        , template_table
+        , publications
+        , inherit_privileges)
+    VALUES (
+        p_parent_table
+        , p_type
+        , v_id_interval
+        , p_control
+        , p_premake
+        , p_constraint_cols
+        , p_automatic_maintenance 
+        , p_inherit_fk
+        , p_jobmon
+        , p_upsert
+        , p_trigger_return_null
+        , v_template_schema||'.'||v_template_tablename
+        , p_publications
+        , v_inherit_privileges); 
+
+    v_last_partition_created := @extschema@.create_partition_id(p_parent_table, v_partition_id_array, false);
+
+    IF v_last_partition_created = false THEN
+        -- This can happen with subpartitioning when future or past partitions prevent child creation because they're out of range of the parent
+        -- See if it's actually a subpartition of a parent id partition
+        WITH top_oid AS (
+            SELECT i.inhparent AS top_parent_oid
+            FROM pg_catalog.pg_inherits i
+            JOIN pg_catalog.pg_class c ON c.oid = i.inhrelid
+            JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
+            WHERE c.relname = v_parent_tablename::name
+            AND n.nspname = v_parent_schema::name
+        ) SELECT n.nspname||'.'||c.relname
+        INTO v_top_parent_table
+        FROM pg_catalog.pg_class c
+        JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
+        JOIN top_oid t ON c.oid = t.top_parent_oid
+        JOIN @extschema@.part_config p ON p.parent_table = n.nspname||'.'||c.relname;
+
+        IF v_top_parent_table IS NOT NULL THEN
+            -- Create the lowest possible partition that is within the boundary of the parent
+             SELECT child_start_id INTO v_parent_partition_id FROM @extschema@.show_partition_info(p_parent_table, p_parent_table := v_top_parent_table);
+            IF v_starting_partition_id >= v_parent_partition_id THEN
+                WHILE v_starting_partition_id >= v_parent_partition_id LOOP
+                    v_starting_partition_id := v_starting_partition_id - v_id_interval;
+                END LOOP;
+                v_starting_partition_id := v_starting_partition_id + v_id_interval; -- add one back since while loop set it one lower than is needed
+            ELSIF v_starting_partition_id < v_parent_partition_id THEN
+                WHILE v_starting_partition_id < v_parent_partition_id LOOP
+                    v_starting_partition_id := v_starting_partition_id + v_id_interval;
+                END LOOP;
+                -- Don't need to remove one since new starting id will fit in top parent interval
+            END IF;
+            v_partition_id_array = NULL;
+            v_partition_id_array = array_append(v_partition_id_array, v_starting_partition_id);
+            v_last_partition_created := @extschema@.create_partition_id(p_parent_table, v_partition_id_array, false);
+        ELSE
+            -- Currently unknown edge case if code gets here
+            RAISE WARNING 'No child tables created. Check that all child tables did not already exist and may not have been part of partition set. Given parent has still been configured with pg_partman, but may not have expected children. Please review schema and config to confirm things are ok.';
+            IF v_jobmon_schema IS NOT NULL THEN
+                PERFORM update_step(v_step_id, 'OK', 'Done');
+                IF v_step_overflow_id IS NOT NULL THEN
+                    PERFORM fail_job(v_job_id);
+                ELSE
+                    PERFORM close_job(v_job_id);
+                END IF;
+            END IF;
+
+            EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false');
+
+            RETURN v_success;
+        END IF;
+    END IF; -- End v_last_partition_created IF
+
+END IF; -- End IF id
+
+IF p_type = 'native' AND current_setting('server_version_num')::int >= 110000 THEN
+    -- Add default partition to native sets in PG11+
+
+    v_default_partition := @extschema@.check_name_length(v_parent_tablename, '_default', FALSE);
+    v_sql := 'CREATE'; 
+
+    -- Left this here as reminder to revisit once native figures out how it is handling changing unlogged stats
+    -- Currently handed via template table below
+    /* 
+    IF v_unlogged = 'u' THEN
+         v_sql := v_sql ||' UNLOGGED';
+    END IF;
+    */
+
+    -- Same INCLUDING list is used in create_partition_*(). INDEXES is handled when partition is attached if it's supported.
+    v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING STORAGE INCLUDING COMMENTS '
+        , v_parent_schema, v_default_partition, v_parent_schema, v_parent_tablename);
+    IF current_setting('server_version_num')::int >= 120000 THEN
+        v_sql := v_sql || ' INCLUDING GENERATED ';
+    END IF;
+    v_sql := v_sql || ')';
+    EXECUTE v_sql;
+    v_sql := format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I DEFAULT'
+        , v_parent_schema, v_parent_tablename, v_parent_schema, v_default_partition);
+    EXECUTE v_sql;
+
+    IF current_setting('server_version_num')::int >= 120000 AND v_parent_tablespace IS NOT NULL THEN
+        -- Tablespace managed via inherit_template_properties() call below if PG11 or earliser
+        EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_default_partition, v_parent_tablespace);
+    END IF;
+
+    -- Manage template inherited properies
+    PERFORM @extschema@.inherit_template_properties(p_parent_table, v_parent_schema, v_default_partition);
+
+END IF;
+
+IF p_type <> 'native' THEN
+    IF v_jobmon_schema IS NOT NULL  THEN
+        v_step_id := add_step(v_job_id, 'Creating partition function');
+    END IF;
+    IF v_control_type = 'time' OR (v_control_type = 'id' AND p_epoch <> 'none') THEN
+        PERFORM @extschema@.create_function_time(p_parent_table, v_job_id);
+        IF v_jobmon_schema IS NOT NULL THEN
+            PERFORM update_step(v_step_id, 'OK', 'Time function created');
+        END IF;
+    ELSIF v_control_type = 'id' THEN
+        PERFORM @extschema@.create_function_id(p_parent_table, v_job_id);  
+        IF v_jobmon_schema IS NOT NULL THEN
+            PERFORM update_step(v_step_id, 'OK', 'ID function created');
+        END IF;
+    END IF;
+
+    IF v_jobmon_schema IS NOT NULL THEN
+        v_step_id := add_step(v_job_id, 'Creating partition trigger');
+    END IF;
+    PERFORM @extschema@.create_trigger(p_parent_table);
+END IF; -- end native check
+
+
+IF v_jobmon_schema IS NOT NULL THEN
+    PERFORM update_step(v_step_id, 'OK', 'Done');
+    IF v_step_overflow_id IS NOT NULL THEN
+        PERFORM fail_job(v_job_id);
+    ELSE
+        PERFORM close_job(v_job_id);
+    END IF;
+END IF;
+
+EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false');
+
+v_success := true;
+
+RETURN v_success;
+
+EXCEPTION
+    WHEN OTHERS THEN
+        GET STACKED DIAGNOSTICS ex_message = MESSAGE_TEXT,
+                                ex_context = PG_EXCEPTION_CONTEXT,
+                                ex_detail = PG_EXCEPTION_DETAIL,
+                                ex_hint = PG_EXCEPTION_HINT;
+        IF v_jobmon_schema IS NOT NULL THEN
+            IF v_job_id IS NULL THEN
+                EXECUTE format('SELECT %I.add_job(''PARTMAN CREATE PARENT: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id;
+                EXECUTE format('SELECT %I.add_step(%s, ''Partition creation for table '||p_parent_table||' failed'')', v_jobmon_schema, v_job_id, p_parent_table) INTO v_step_id;
+            ELSIF v_step_id IS NULL THEN
+                EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before first step logged'')', v_jobmon_schema, v_job_id) INTO v_step_id;
+            END IF;
+            EXECUTE format('SELECT %I.update_step(%s, ''CRITICAL'', %L)', v_jobmon_schema, v_step_id, 'ERROR: '||coalesce(SQLERRM,'unknown'));
+            EXECUTE format('SELECT %I.fail_job(%s)', v_jobmon_schema, v_job_id);
+        END IF;
+        RAISE EXCEPTION '%
+CONTEXT: %
+DETAIL: %
+HINT: %', ex_message, ex_context, ex_detail, ex_hint;
+END
+$$;
+

programming_language: SQL