pax_global_header00006660000000000000000000000064141276170610014517gustar00rootroot0000000000000052 comment=7cd0ef91f7c58560824e5452e11f76a232ed64f9 pg_partman-4.6.0/000077500000000000000000000000001412761706100136565ustar00rootroot00000000000000pg_partman-4.6.0/.gitignore000066400000000000000000000000601412761706100156420ustar00rootroot00000000000000ignore/* *.swp *.swo .deps/* *.o *.so sql/*.sql pg_partman-4.6.0/CHANGELOG.txt000066400000000000000000003413651412761706100157220ustar00rootroot000000000000004.6.0 NEW FEATURES ============ -- Support for PostgreSQL 14 added (Github Issue #392). -- Added p_drop_cascade option to undo_partition()/undo_partition_proc() to allow undoing sub-partition sets from parent tables higher in the inheritance tree. Only applies when p_keep_tables is set to false. Note this causes all child tables below a sub-partition parent to be dropped when that parent is dropped. -- Better support NOTIFY commands with the extension's built-in background worker. Thank you to @eisaev on Github for this fix. (GitHub Issue #384). -- Deprecate pg_partman's specialized UPSERT support for native partitioning in PG11+. Raise error for users to use built-in INSERT ON CONFLICT feature instead. -- Added new optional parameter to drop_partition_time() to tell partman to use a different reference timestamp from which to determine which partitions should be affected. Thanks to @wesselvdv on Github for this feature (Github PR#386). BUG FIXES ========= -- Better handle custom intervals that don't fall on typical datetime boundaries (monthly, daily, etc). Previously if you chose a custom interval that did not initially align on a typical boundary, pg_partman would always try and truncate the initial table's datetime start value to one of those typical boundaries. This could lead to child table constraints that were not expected. A new parameter to create_parent(), 'p_date_trunc_interval' has been added to allow you to tell pg_partman how to truncate the initial datetime value to better meet custom interval needs. This accepts any valid interval value that the built-in 'date_trunc()' function would accept. (Ex: 9 week interval starting on 2021-04-05 would need to be truncated by 'week' vs 'month'. Otherwise pg_partman would truncate by 'month' and the partitions would start on 2021-04-01 and result in unexpected child table boundaries). Many thanks to @wesselvdv on Github for reporting this issue and the many fixes and tests that went in to solving this (Github PR#371, Issue #369). -- Fixed show_partition_info() to properly return boundary values no matter what custom interval may be used for the partition set. Update documentation to note that the show_partition_info() boundary results are INCLUSIVE for returned start values and EXCLUSIVE for returned end values (Github PR#372, Github Issue #369). -- When dropping tables for retention, DROP...CASCADE is no longer the default. This avoids foreign key references TO the partition set getting dropped if any child table is dropped. Set the option "drop_cascade_fk" in part_config to allow the old behavior if desired. This cascade is only allowed with non-native, trigger-based partitioning and is not supported with sub-partitioning at this time (Github Issue #365). -- Fixed edge case in sub-partitioning where an additional subpartition child table could be created that is just outside the max boundary limit of its parent table boundaries. -- Better handling of large interval values in ID partitioning (bigint). Thanks to @calebj on Github for reporting the issue and providing a fix (Github Issue #388). 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 ========= -- CVE-2021-33204 - 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. https://nvd.nist.gov/vuln/detail/CVE-2021-33204 -- 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 ============ -- Officially supported minimal version is now PostgreSQL 9.6. PostgreSQL 9.6 support will be ending the next pg_partman release after PostgreSQL 14 is released. -- Removed p_debug from functions changed in this update. Slowly migrating all functions away from parameter to using debug logging level instead. BUG FIXES ========= -- Fixed issue when upgrading from 4.4.0 to 4.4.1 on PG10 and below. Upgrade path now goes directly from 4.4.0 to 4.5.0 and skips 4.4.1. All changes in 4.4.1 have been included in 4.5.0 where necessary. (Github Issue #342) -- Added the parameter `p_ignored_columns` to partition_data_time(), partition_data_id(), partition_data_proc(), undo_partition() and undo_partition_proc(). The parameter is an array that can list one or more columns to ignore their values when moving the data. This allows for moving data out of the default/parent table when generated columns are in use. (Github Issue #336) -- Fixed the p_start_partition parameter working properly with id->timestamp sub-partitioning. (Github Issue #341) 4.4.1 NEW FEATURES ============ -- Added documentation for how to use pg_partman with native partitioning. BUG FIXES ========= -- Fix long present typos mostly in comments, error messages, & docs. (Github PR #315 & #319) -- Update description of the pg_partman_bgw.dbname GUC to state that it is not actually optional. -- Fix syntax error in reapply_indexes.py -- Fixed wrong variable usage in undo_partition_proc() exception. 4.4.0 NEW FEATURES ============ -- No longer requires superuser in order to grant privileges directly to child tables (setting inherit_privileges to true or running apply_privileges() directly). Thanks to Stephen Frost for suggesting this fix. This now makes pg_partman no longer require superuser for any operation other than initial installation. BUG FIXES ========= -- Added new config option to allow maintenance to refresh logical replication subscriptions. This is to fix a shortcoming where logical replication is used to replicate partition sets between databases, but not having a way to automatically account for new tables being added on the publisher side. Set the subscription name via the part_config.subscription_refresh column by just setting the name of the subscription. For sub-partitioning, part_config_sub.sub_subscription_refresh is also available. (Github Issue #307) -- Fix partition_data_time() function (called by partition_data_proc()) to work with epoch time partitioning properly when moving data out of the default partition with native partitioning. Thanks to @sheeju on Github for reporting the issue and providing fix. (Github PR #309) -- Fixed handling of mixed case/non-standard character object names during maintenance runs with native partitioning on PG11 or greater. Thanks to @sheeju on Github for reporting the issue and providing fix. (Github PR #309) -- Fixed columns in sub_part_config that were not being kept consistent between configurations for sub-partition tables that were themselves further sub-partitioned. You may possibly have some configurations that are inconsistent due to these columns being missed during previous maintenance runs. The following fix for the consistency check should find this your next maintenance run and throw an error. -- Fixed consistency check to ensure all parent tables that are part of the same sub-partition set have the same configuration in the sub_part_config table. If you encounter an error about inconsistent data in sub-partition configs after updating to this version, please carefully read the error message that is produced on how to fix it. -- Fixed broken error message from maintenance runs (record "v_row" has no field "sub_parent") when the above consistency check tried to produce an error message to indicate that the sub-partition configurations had an issue. 4.3.1 BUG FIXES ========= -- Add support for inheriting GENERATED columns from the parent table to the child tables. Thanks to @Dishwasha on Github for reporting the issue! (Github PR #302) -- Fix handling of data existing in the default partition with time-based partitioning when that data is further ahead of any data that exists in non-default children. New child tables may not have been created as expected. Thanks to @adrianlzt on Github for reporting the issue! (Github Issue #303) 4.3.0 NEW FEATURES ============ -- New documentation for how to migrate a trigger-based partition set managed by pg_partman to a native set - doc/migrate_to_native.md (Github Issue #282) -- Added new table matrix to the pg_partman.md documentation for which properties are handled via the special pg_partman template table depending on your version of PG. Please review this matrix carefully when updating the major version of PG to see which template properties may need to be applied to the real parent for inheritance to keep working as expected. -- Tablespace inheritance for PG12+ is now handled via the real parent table of the partition set, no longer via the pg_partman template table. Note that tablespaces for primary/unique keys that are not part of the partition key must still be handled via the template where those indexes are defined. For PG11 and earlier, tablespace inheritance must still be handled via the template table. If you are upgrading from PG11 or earlier to PG12+, ensure the tablespace is properly set on the parent table for inheritance to work as expected. (Github Issue #274) -- Added new function dump_partitioned_table_definition() to provide a means of replaying partition setup statements in another database or during a dump/restore. Thanks to James Coleman (jcoleman) for providing this new feature! (Github Issue #260, PR #291) BUG FIXES ========= -- Fixed maintenance runs with retention, or direct calls to drop_partition*() functions, failing if there were foreign keys pointing to the child table that was to be dropped. (Github Issue #294) -- Fixed partition_data*() functions failing if they are run in the same session multiple times. Temporary tables were not being cleaned up properly. (Github Issue #295) -- Changed child_end_time value of internal show_partition_info() function to show the non-inclusive end time boundary value. Previously it would subtract one second to try and show the actual values being stored in the partition, but this was causing issues trying to build boundary values when trying to migrate to native partitioning. This was previously an undocumented function, so its API was not guaranteed to be stable. It is now documented so any further changes will be fully documented. 4.2.2 -- Fix bug in template table index inheritance that prevented any additional indexes from being copied after finding an index definition that exists on both the template and parent tables. Github Pull Request #278. 4.2.1 -- Fix bug in partition_data_time causing never-ending loop while calculating partition range for natively partitioned tables. Github issue #273 4.2.0 -- Preliminary compatability with PostgreSQL 12. -- IMPORTANT SUPPORT CHANGE: As of PostgreSQL 12's stable release, version 9.4 of PostgreSQL will no longer officially be supported by pg_partman. It will likely continue to function without any issue, but any bugs tied specifically to that version and lower will no longer be fixed and new features that are not compatible with 9.4 may be introduced. -- IMPORTANT CHANGE: The UNLOGGED status of a partition set in native partitioning has changed to be managed via the template table, no longer the parent table. -- Currently setting a natively partitioned parent table as UNLOGGED/LOGGED is not a property that can be changed with an ALTER TABLE. So if the state is later changed to LOGGED (or vice versa), this property will not change for future child tables. Current child tables were never handled via this manner before and always had to be fixed manually. -- If any of your native partition sets are UNLOGGED, be sure to set this property on the template table before upgrading to this version. Otherwise new child tables may be created as LOGGED. -- pg_partman will maintain this behavior until core PostgreSQL determines a definative way that UNLOGGED changes will be handled. -- See reported bug at https://www.postgresql.org/message-id/flat/15954-b61523bed4b110c4%40postgresql.org -- As of PostgreSQL 12, pg_partman will no longer support inheriting OIDs since the special column behavior has been removed. See the release notes for PG12 for more information on this change. The feature has not been deprecated from pg_partman for older versions of PostgreSQL, but all tests involving the feature have been removed and any bugs involving it will likely not be fixed unless it is a compatability issue with 12+. It is recommended to remove any dependencies on special OID behavior that you previously had in your partition sets at this time. -- Fixed bug that prevented pg_partman from creating child tables in PG12+ due to useage of pg_class.relhasoids column. (Github Issue #256) -- A new configuration option, "constraint_valid", has been added to the part_config(_sub) table to control whether the additonal constraints that pg_partman can manage (see "Constraint Exclusion" section in pg_partman.md) are added in a NOT VALID state. By default the constraints are added as VALID as they were before. Note that if this config is changed to "false", constraint exclusion will not work until the constraints are validated. -- Fixed bug where primary key/unique indexes applied to pre-existing template tables were not being applied to the DEFAULT partition created in PG11+ native partitioning. Thanks to @dtseiler on Github for testing and reporting this. (Github Issue #266) -- Fixed bug that was causing error in the retention management of time-based partitioning sets. If retention would attempt to remove the last child table, maintenance would throw an error about a missing table instead of just a warning about the last table attempting to be dropped. Thanks to @LaVoCG on Github for the examples to reproduce this scenario. (Github Issues #227 & #237) -- Changed default option to false on PG11+ for whether the maintenance background worker will run an ANALYZE whenever a child table is created. If this is desired, please set the option pg_partman_bgw.analyze to true in your postgresql.conf. (Github Issue #262) -- Properly throw an error with create_parent() when the data type of the partition column is numeric. Previously would not report any errors but would also not add the proper data to the part_config table so future maintenance would not work. Currently testing to see if numeric partitioning can be properly supported in pg_partman. (Github Issue #265) 4.1.0 -- For PostgreSQL 10+ and native partitioning, privilege inheritance is now optional and turned off by default. This now allows pg_partman to run fully as a non-superuser when using native partitioning. (Github Issue #231) -- This means that all interaction with these partition sets must be done through the parent table. -- A new column in the part_config table, inherit_privileges, is available if you require direct access to the child tables. Note that this will then require superuser at some level (superuser ownership of pg_partman functions for SECURITY DEFINER or running maintenance as a superuser). Just set this option to true and run the reapply_privileges() function to set all child privileges to match the parent. From then on, all new child tables will inherit the parent's privileges. -- Note that for non-native partitioning on all versions, privileges will continue to be inherited to avoid backward compatability issues and will therefore require a superuser as the above bullet notes. -- All existing partition sets have had their configuration values set to true so they will continue to act as they did before this update. -- Updated GRANTS that are required for a non-superuser are in the INSTALLATION section of the readme. -- The partition_data*() functions now work with the new DEFAULT table feature of PG11. Running these functions will now move data out of the default partition and into the proper child tables, creating them if necessary (Github Issue #230). -- Note that normal maintenance can fail if data goes into the default table since data that exists in the default cannot have a corresponding child created without taking extra steps. Those extra steps are now handled by partition_data*() and, once that is run, maintenance should be able to proceed as normal. -- Renamed check_parent() function to check_default(). This function now checks both the parent table in trigger-based partition sets as well as the default table introduced in PostgreSQL 11. -- The reapply_indexes.py script now only works on trigger-based partition sets or native partition sets in PG10. This script will likely never work with native partitioning in PG11+ without being extremely prone to unexpected behavior, so it is currently not supported at all. Be aware that non-unique index inheritance is built in for PG11+ and that should be used if possible (Github Issue #232). -- Fixed partition_data_proc() not working for epoch time partitioned sets (Github Pull Request #239). -- Use SplitIdentifierString() in BGW for more recent versions of PostgreSQL (10.5+) (Github Pull Request #241) -- Fixed show_partition_info() to account for quarterly/3month partitioning that may not use the "q" naming convention (Github Issue #252, PR #251). -- Set the drop indexes retention feature to only be usable with trigger-based partitioning or for native, only on PG10 and lower. Natively inherited indexes cannot be dropped from children. 4.0.0 -- Initial release to provide support PostgreSQL 11. All PG11 functionality is subject to change before its final stable release. -- IMPORTANT POSTGRESQL 11 UPGRADE STEPS -- All foreign keys, any non-unique indexes, and any unique indexes that include the partition key must be created on the actual parent table and removed from the template table for those property inheritances to continue working. Foreign key inheritance from the template table no longer works at all. -- Any unique indexes or primary keys that do not include the partition column must still use the template table in order to be inherited to child tables. -- Indexes defined on the parent will take precidence over those defined on the template table. Duplicate indexes should not happen. If they do, please report this as a bug. -- Note that if this version is installed on PG10 and older, the procedures will not be created. A migration script to update pg_partman to include those will be made available once PG11 stable has been released. -- Major change in privilege/security of this extension. SECURITY DEFINER is no longer in use to cause most of pg_partman's functions to run as the owner of the called function. This is mostly due to the requirements to support PROCEDUREs in PG11, but it's also generally a better idea for security in the long run. -- It is recommended to create a dedicated role for pg_partman maintenance runs and give it the privileges outlined in the setup section of the top level README. -- The role running maintenance must now have permissions to create tables in the target schema. If you start seeing permission errors after upgrading, this is most likely what needs to be fixed. -- The role running maintenance must now always be the owner of the tables it is managing. This shouldn't be too drastic of a change since previously it was making the maintenace function owner the owner of the partitioned tables. However, if you've changed partition table ownership after initial creation, you may run into issues until you set the pg_partman role as the owner. -- If you are using logical replication, the partman maintenance role must be the owner of the publication in use. -- As a bonus, the ALTER DEFAULT PRIVILEGES feature should now be much easier to use since you have a dedicated role for maintaining partitions. Check the documentation for this feature so that you can set any read/write privileges you want new partition sets to have at the time they are initially created. -- Two functions retain their SECURITY DEFINER status: -- check_name_length() - Used inside the trigger function and I don't want to break existing installations. It's an IMMUTABLE function so the security implications aren't that great. -- apply_privileges() - Requires access to pg_authid & also potentially changes object ownership. -- If you're using pg_jobmon, privileges for that extension are managed independently of pg_partman. See that extension's documentation for privileges it requires. -- For PostgreSQL 11+, started conversion of some python scripts into PROCEDUREs that are now installed within the database. Scripts are still available for pre-11 versions, but will be phased out as soon as PG10 is no longer under support. -- Note that the user that calls these procedures must have permissions to write to the partition set and create/drop partitions in the set. -- partition_data.py script is now the partition_data_proc() PROCEDURE. -- undo_partition.py script is now the undo_partition_proc() PROCEDURE. -- reapply_constraints.py is now the reapply_constraints_proc() PROCEDURE. -- reapply_foreign_keys.py not converted since it's not needed for PG11+. Foreign keys can be applied to the parent and will automatically be inherited. -- reapply_indexes.py was not converted yet because concurrent index creation is not yet possible inside a PROCEDURE. -- Unable to turn autovacuum off/on reliably in PROCEDURE as was previously done with python scripts. If autovac is an issue when (un)partitioning large tables, recommend manually disabling it. -- Consolidated undo_partition_time(), undo_partition_id(), and undo_partition_native() functions into a single undo_partition() function. -- The old undo_partition() function that just copied data from child tables to the parent has been removed. The only utility this really had was a possible way to support undoing partition sets not maintained by pg_partman. With native partitioning now in place, its continued usability is now questionable and it was also causing confusion since people were using it and not the previous ones specialized for pg_partman. Can incorporate back in if someone can provide a further beneficial use case. -- For PG11+, a DEFAULT partition is automatically created as part of the partition set to handle any data that does not have a matching child table. -- It's just given a _default suffix on the existing parent table name. -- Support within pg_partman for moving data out of the default partition is not included in 4.0.0, but will be included in a future release. -- New parameter to show_partitions() can include the DEFAULT in its output, but it's not output unless specifically requested since it's not commonly needed in internal code where it's mostly used. -- For native partitioning, added option to give a "source" table to the partition_data*() functions that is different from the parent table of the partition set. This assists with partitioning an existing table to a natively partitioned table. -- For PostgreSQL 11+, run_maintenance_proc() has been added and should cause much less contention when running against many large partition sets in a single call. -- For PG11+, this is the preferred maintenance method to run over the old run_maintenance() function. The old one will continue to work for the next few major releases, but please update all pg_partman maintenence calls in your environment to use the new procedure. -- Simplified --type argument for undo_partition.py script for PG versions 10 and below. Similar to create_parent(), values are now 'partman' or 'native'. -- Maintenance that creates new partitions on PG11+ and is using native partitioning will no longer analyze the partition sets by default. This should greatly cut down on maintenance run time and contention issues. If you're noticing any odd query plans, especially if you're using additionally configured constraints, you may need to schedule some manual analyzes or you can set the p_analyze parameter in either the function or procedure call to "true". -- Fixed bug in run_maintenance() with subpartitioning where it would throw an error if retention dropped one of the parent tables undergoing maintenance. -- Fixed template table inheritance for PG10 not working on minor version PostgreSQL 10.0 (but you really shouldn't be running this version anymore). -- Fixed compilation errors for background worker against PostgreSQL 11. 3.2.1 -- Fix bug with inheriting indexes from template tables with native partitioning. Was throwing syntax error related to format() function. Thanks to iwarford on github for reporting the issue (Github Issue #223). 3.2.0 -- Added option to include newly created child tables in a subscription for use in logical replication. PG10+ Only. (Github PR #218). -- Signature of create_parent() function has changed, so ensure calls are accounting for new argument. -- Does not currently work for subpartition tables if they are natively partitioned. -- Fix so that unique indexes that exist on the the template table are created on child tables. PG10+ native partitioning only. (GitHub PR #220, Issue #225). -- Fix so that indexes inherited from template table are put into the same tablespace as defined on the template table. PG10+ native partitioning only. (Github Issue #223). 3.1.3 -- Allow tablespace inheritance from the template table in PostgreSQL 10+. Note that PG10 currently completely ignores the TABLESPACE flag to the CREATE TABLE command on a native partition parent table. So a natively partitioned parent can never live in a different tablespace other than the default. Tablespaces must be declared for each child table and pg_partman can now do that using the template table the same way it does for indexes and foreign keys (Github Issue #212). -- Obey the inherit_fk config option for natively partitioned tables. Previously foreign keys would always be inherited even if this was set to false. -- Do not allow empty string value for retention_schema in configuration tables. If this update causes an error related to this, please ensure your retention schema is actually set or leave the column value NULL (Github #214). -- Moved the pg_partman github repository to its own dedicated organization at http://github.com/pgpartman. Please update all links that previously referred to the keithf4 personal repository. 3.1.2 -- Fixed reapply_indexes.py to work properly with native partitioning. Previously it would recreate all indexes on the template table. If you've run this script on a natively partitioned set already, please review the indexes that exist on your template table and ensure only the ones you need exist. You will likely have to re-run the script again to get the child indexes set properly and you will need to set the the --recreate_all option. If your primary key has changed, also set the --primary option. Thanks to Jawshua on Github for finding the bug and providing a fix. (Github Pull Request #206). -- Added note to documentation (pg_partman.md) about support for the new IDENTITY feature in PG10. 3.1.1 -- Fixed custom time intervals to work properly with native partitioning. (Github Issue #200) -- Fixed template table owner to be the same as the partitioned table owner when the template table is not manually set during native partition creation. Note this will not change any existing template table ownerships, it only affects newly created native partitions sets. (Github Issue #203) 3.1.0 -- Add support for index & foreign key inheritance in native partitioning for PostgreSQL 10. This is done using a template table that is automatically created in the same schema the pg_partman was installed to. The template table is based off the given parent and any indexes or foreign keys that you would like to be created on child tables can be created on this table. Note that the initial child tables created when create_parent() is called will not have these indexes or FKs unless you run reapply_indexes.py and/or reapply_foreign_keys.py after creating them on the template table. You can also have the initial child tables contain indexes/FKs if you premake the template table and provide it as a parameter to create_parent(). -- Updated reapply_indexes.py and reapply_foreign_keys.py scripts to handle template table feature for native partitioning -- IMPORTANT NOTE: This is hopefully only a temporary option until there is built in support for applying these table properties natively. Once native support for these features is added, this template table will be phased out quickly. -- Retention policy will no longer allow the final child table of a partition set to be dropped. pg_partman always requires at least one child table to work properly. If retention attempts to remove the final child table in a partition set, a WARNING is issued but the child table is not dropped. (Github Issue #189) -- Fixed bug in partition trigger function creation for time-based partitioning where the target tables defined in the trigger code itself may not match the child tables that exist. This could occur only if the create_function_time() function was called independently of normal maintenance and the child tables were behind "now" as of when the function was ran. The target tables in the function are now based on the data that exists in the table as long as infinite_time_partitions is not set. If that is set, the tables defined in the trigger are always based on the current time as of when the function itself is run (same behavior as before). This function is rarely called independently and this is not an issue that would occur with normal maintenance runs since the partition trigger function is never rewritten unless a new child table is actually created. (Github Issue #176) -- The show_partition_name() function now returns the child table schema as a separate field. 3.0.2 -- Allow maintenance background worker to restart itself if it crashes. Previously if the main background worker process exited for any reason, the only way to restart it was to restart the whole postgresql instance. Now it will try to restart itself every 10 minutes. A warning will be issued into the logs if this event occurs. (Github Issue #180). -- NOTE: You will have to restart the PostgreSQL cluster to load the new library with the auto-restart option. -- Remove internal version checking function and use simpler comparison method using integer based version number (Pull Request #183). -- Apply fixes from running plpgsql_check (https://github.com/okbob/plpgsql_check/) 3.0.0 & 3.0.1 -- IMPORTANT NOTE: The initial version of pg_partman 3 had to be split into two updates due to changing both the data in the config table as well as adding constraints on that data. Depending on the data contained in the config table, doing this in a single-transaction update may not work (you may see an error about pending trigger events). If this is the case, please update to version 3.0.1 in a separate transaction from your update to 3.0.0. Example: /* BEGIN; ALTER EXTENSION pg_partman UPDATE TO '3.0.0'; COMMIT; BEGIN; ALTER EXTENSION pg_partman UPDATE TO '3.0.1'; COMMIT; */ -- Update 3.0.1 MUST be installed for version 3.x of pg_partman to work properly. As long as these updates are run within a few seconds of each other, there should be no issues. -- In preparation for native partitioning support, the "partition_type" parameter to create_parent() and create_sub_parent() has been simplified. -- The two main, valid values are now: "partman" or "native". "partman" is the same pre-3.0 partitioning method that has always existed. "native" uses the new native partitioning method that comes with PostgreSQL 10. See below for important information about native partitioning. -- Whether a table is partitioned by time or id/serial is now automatically determined by the data type of the partition column. -- smallint, integer, & bigint (int2, int4, int8 respectively) are automatically id/serial partitioning (unless the epoch flag is set) -- timestamptz, timestamp, & date are automatically time -- If the preset time intervals in the documentation are used (daily, weekly, etc), then the more efficient, static trigger function will be used. If any other time interval value is given, then the slower, but more flexible "time-custom" trigger will be used (See "Custom Time Interval Considerations" section of documentation). -- "time-custom" is also still a valid value, but not necessary unless you want to be explicit in your partitioning method. -- Added preliminary support for native partitioning methods that will be introduced in PostgreSQL 10. Note this feature is still in very early testing, and could change before the official 10.0 release. But since it's a significant new feature, I want to stay ahead of it and allow real-world testing as early as possible for those interested in it and also make sure it works with pg_partman's own trigger-based partitioning methods as well. (Github Issue #171) -- Use partition_type "native" in the create_parent() or create_sub_parent() functions. See pg_partman.md for more info -- Native Sub-partitioning is a DESTRUCTIVE processes and cannot be run on a table that has any data without losing that data. This is because a table has to be declared natively partitioned at creation (no ALTER statement exists) and the current child tables that must become parents have to be dropped and recreated as such. -- A new parameter flag for create_sub_parent() is included that must be set to "yes" to help ensure this is not accidentally done. -- Features NOT (yet) available in native: -- Indexes are not currently possible to create on a partitioned parent table. This means there is currently no automatic index support for native partitioning in pg_partman. Waiting to see what final implementation will be like before adding code to handle this. -- Data that does not match any children causes an error and is not preserved in the parent. -- Foreign keys from partition set to other tables (FKs from other tables to partition sets not supported at all for any partition methods). -- Removed automatic partition creation via trigger at 50% for id/serial partitioning. Was making trigger function generation code harder to maintain and is just a bad practice in general to be making child partitions on the fly with data insertion. This is also not possible with native partitioning, so rather phase out the feature now. Now must use automatic partitioning and schedule frequently enough to keep up with ingestion rate or manually call run_maintenance() via some other means to maintain new partition creation for id-based partition sets. -- The next maintenance run of any id-partitioned table will have its function remove the 50% code-block. Unable to do it as part of the extension update since that would then make the trigger function part of the extension. -- If you encounter any issues caused by this update and the removal of the 50% code, call create_function_id() to recreate the trigger. This function has been added to pg_partman.md for reference. -- Changed config column & function arguments from "use_run_maintenance" to "automatic_maintenance". This makes the option clearer since manual maintenance can still use the run_maintenance() function. -- Valid values are currently: "on", "off" -- A value of "on" means that a call of run_maintenance() with no specific table parameter given will cause maintenance to automatically run for that partition set. -- A value of "off" means that maintenance will not run in the above situation, but if you give a specific table parameter to run_maintenance(), maintenance can still be run manually for it. -- Note that all partition sets in part_config have set this value to "on" as part of the 3.0.0 upgrade. Since the semantics of the column value have changed, I went with the safe option of turning maintenance for all partitions sets on vs trying to figure out what the intention of previous settings were. Review your configuration after upgrading and the new meanings for this column's value to ensure things are set up properly for your database. This is most relevant if you had this turned off to avoid automatic serial partition creation at 50%. Now that all serial partitioning requires run_maintenance(), this meaning is no longer relevant. -- The undo_partition plpgsql functions now return both the number of partitions undone as well as the number of rows copied/moved. Result is returned as a record set, so these functions can be queried as a table to have columns/rows returned. The value of undone partitions will be -1 when an issue is encountered while running the function. -- Improved performance if infinite_time_partitions flag is set to true. No longer obtains max value of partition set needlessly. (Github Pull Request #169) -- Added create_partition_time(), create_partition_id(), create_function_time(), create_function_id() to documenation. Had thought they were only really useful internally, but turns out they can be pretty useful in general to fill in missing children gaps and manually fix partition trigger function. (Github Issue #161). -- Fixed bug with upgrading from version 1.x to 2.x of pg_partman if your version of PostgreSQL had been less than 9.2 at the time the 1.6.0 version of pg_partman came out. The custom_time_partitions table was only created if PostgreSQL was >= than 9.2 since it relies on the range data type. If you then later upgraded to 9.2 or greater, the custom time table was never created and can cause issue with upgrading as well as if a custom time partition set is ever created. Since v2.x of pg_partman only works with PostgreSQL 9.4 and later, forcing this table creation as of v2.x should no longer be an issue (Github Issue #159). -- Fixed bug in sub-partitioning that did not check for sub-partitions being equal to or larger than the top parent's interval. Also did not properly disallow weekly sub-partitions. If any of your current partition sets have weekly sub-partitions, this won't break them, but you should be aware that time boundaries will not line up properly in some cases due to the nature of ISO weeks. It is recommended that you repartition in a different pattern. -- Fixed bug when undoing a subpartition set that did not remove the entry in the part_config_sub table. (Github Issue #168). -- If the parent table given as an argument to the undo function had an entry in part_config_sub, it is now always removed. -- By default, if you undo a child table that is also partitioned, it will not stop additional sibling children of the parent partition set from being subpartitioned unless that parent is also undone. -- To handle the above situation where you may not be removing the parent but don't want any additional subpartitioned children, a new function has been added: stop_sub_partition(). -- Fixed bug in undo_partition.py that would stop undoing the partition set if it hit a child table with zero rows. Script now returns the number of partitions undone instead of the rowcount moved. -- Also fixed bug in undo_partition.py that would mask any python errors and let script run as if nothing bad happened. -- Fixed bug in sub-partitioning when a parent was partitioned by time and its children were partitioned by ID. In certain situations, all ID sub-sets were not getting their parent table created. -- Removed unused p_analyze parameter from create_function_id(). -- Began moving function description comments into the functions themselves so they're visible in the database as well. 2.6.4 -- Note, if you installed update 2.6.3 prior to the release of 2.6.4, this update will not change anything, but is still required to be installed to allow the extension version update chain to work. -- While upgrading to version 2.6.3 or later, if you encounter the error: "cannot ALTER TABLE "part_config_sub" because it has pending trigger events" (or for the part_config table), then you must install that update by itself first and then install the update to 2.6.4 separately in a different transaction. Changing the values of a column in the same transaction as altering it to add/remove a constraint can cause the pending triggers error. The only way around that is to do the steps as separate transactions, which this update separates out from the previous one that caused the issue. Unfortunately, all extension updates run in a single transaction unless you upgrade manually to a specific version to control the transaction state between versions. (Github Issue #167). See the updates/pg_partman--2.6.3--2.6.4.sql file for more specific instructions on how to deal with this issue. 2.6.3 -- IMPORTANT NOTE: Installing the new C library requires a restart of the database cluster. Even if you are not running 9.6, it is recommended to restart after this update to ensure the latest library file is loaded into the database if you have compiled the C library. -- Fixed pg_partman background worker to work with PostgreSQL 9.6. If configured for multiple databases in a single cluster, the background worker processes will no longer start up and run in parallel. Unfortunately, changes in background worker code have made that harder to do at this time. Note it never ran in parallel on a single database before, it would only do that for multiple databases. (Github Issue #141). -- Allow option for millisecond epoch partitioning. (Github Issue #150, Also requested back in #75) -- The "epoch" parameter to any function and the column in the part_config table has changed from boolean to text and only accepts the values: "seconds", "milliseconds", "none". -- The default is "none". -- Any current epoch partition sets will automatically have this value set to "seconds". 2.6.2 -- Refactor of time functions in relation to epoch partitioning to improve maintainability of the code. Much thanks to Michael Rasmussen (Trekoid)! (Github Pull Request #143) -- Some code cleanup for python scripts. Much thanks to Rodolphe QuiƩdeville (rodo)! (Github Pull Request #148) 2.6.1 -- WARNING: The pg_partman background worker is currently incompatible with PostgreSQL 9.6. It runs fine in 9.4 and 9.5. I'm still working on trying to figure out what the issue is. If you must upgrade to 9.6 before this issue is fixed, you can disable the background worker by removing the entry for it in shared_preload_libraries and restarting postgres. Instead schedule the run_maintenance() plpgsql function to run via another scheduler method (cron). -- NOTE: A restart of the database is required to put the background worker code changes in place once the new library file is installed. -- Add p_analyze parameter to partition_data_id() and partition_data_time() functions to allow moving data out of the parent to be faster if an analyze is not required after each child table is created. If set to false, highly recommended that an analyze on the entire partitions set is run after moving data. (Github Pull Request #137) -- Also allows better control of automatic analyzing of new partitions for serial partition sets by allowing the p_analyze parameter to run_maintenance() to be passed further down the function call hierarchy. -- Updated partition_data.py to pass this parameter as FALSE when calling these functions. The script does a VACUUM ANALYZE when it completes all batches anyway, so an analyze per child table is not necessary. -- Improve performance during privilege inheritance when creating new partitions or when running reapply_privileges() function. Most noticable on databases with many objects (Github Issue #139). -- Fixed buffer overflow in background worker code if database name was too long (Github Pull Request #140). -- Fixed bug in background worker code that could cause improper initialization of dynamic bgw processes (Github Pull Request #140). -- Account for non-lowercase schemename for pg_partman's schema in background worker (Github PullRequest #140). 2.6.0 -- New function to aid in column removal: drop_partition_column(). -- Depending on when a column was added (before or after setting up inheritance for partitioning), dropping it on the parent may or may not drop the column from all children. -- Using this function will ensure the column is always dropped. -- Uses IF EXISTS clause to cleanly work even if column is missing from children already. Will give off warnings if column doesn't exist, but not return errors. -- This function can work for any inheritance set to drop columns, not just those maintained by pg_partman. -- Issue mentioned in blog post by Andrew Dunstan and also borrowed code from there for this function - http://adpgtech.blogspot.com/2015/07/dropping-columns-on-partitioned-tables.html -- Added new configuration option to control if the partitioning trigger returns NULL or NEW. Default is to return NULL. See documentation for further use of this option (Github Issue #128 & Pull Request #127). -- New parameter to create_parent() & create_sub_parent() so be sure to account for that in existing function calls -- Account for all new config columns when checking subpartition configuration consistency. If you suddenly start getting errors about subpartition configuration mismatches, ensure all sibling subpartition parents are configured exactly the same. See error returned by this check for more details on how to fix this. -- Added --version option to vacuum_maintenance.py 2.5.1 -- Fix check for ensuring upsert feature can only be used in 9.5 in create_parent() & create_sub_parent(). Was causing erroneous failures with versions prior to 9.5. Thanks to Death_Syn & ilmari on #postgresql freenode for reporting the issue. 2.5.0 -- Added very limited support for INSERT ... ON CONFLICT (upsert) in the partitioning trigger. For situations where only new data is being inserted, this can provide significant performance improvements. However, major limitations are that the constraint violations that would trigger the ON CONFLICT clause only occur on a per child table basis. This is a known limitation for inheritance in general. Constraints DO NOT apply across all tables in an inheritance set (Ex. Primary keys are only enforced for each individual child table and not for all tables in the partition set. Data duplication is possible). The included python script "check_unique_constraint.py" can help mitigate duplication, but cannot prevent it. Of a larger concern is an ON CONFLICT DO UPDATE clause which may not fire and cause wildly inconsistent data if not accounted for. These limitations will likely never be overcome in this extension until global indexes or constraints for inheritance sets are supported in PostgreSQL. It is recommended you test this feature out extensively before implementing in production and monitor it carefully. Many thanks to MikaelUlvesjo for contributing work on this issue (Github Issue #105 & Pull Request #122). -- Added check to part_config_sub to ensure premake > 0. Makes it consistent with part_config table. -- Allow internal check_version() function to work with test releases of postgres. If it's an alpha, beta or rc release it ignores the current version, so you're on your own if things fail due to version feature mismatches. 2.4.1 -- Fixed partition_data_id() function to allow intervals larger than a 32-bit integer (bigint) (Github Pull Request #125). -- Fixed vacuum_maintenance.py to work with timestamptz now that all extension time data types were fixed in v2.4.0 -- Fixed pgtap failure in postgres 9.5 for id subpartitioning test (Github Issue #82). 2.4.0 -- IMPORTANT NOTICE: As of this version (2.4.0) support for the 1.x series has been dropped. No further fixes (bugs, features, etc) will be backported and 1.8.8 will be the last version in the 1.x series. If you encounter a bug, the only supported method of fixing it will be to upgrade to the 2.x series where it may already have been fixed or you can submit an Issue on Github. -- Change data type of time-based variables in many functions from "timestamp without time zone (timestamp)" to "timestamp with timezone (timestamptz)". Previously there was a mix of both being used and was causing inconsistencies on timezone edge cases. This was also affecting the data type used when creating the constraints on child tables (they may have had improper timezone info). This should allow constraints on child tables to work more consistently whether the partition column is either timestamp or timestamptz (Github Issue #118). Note this will not recreate the constraints on existing child tables, but if you weren't having any issues before, it shouldn't cause any problems. If you need to have the constraints fixed, you can either do it manually for all existing child tables or unpartition and repartition the set again. -- If you're concerned about this causing issues, you can set the "trigger_exception_handling" configuration option to true which causes any errors encountered when inserting data to child tables to put the data into the parent. Note this will override any other exception handling that uses these tables, but should ensure no data goes missing for normal writes. -- Allow constraint exclusion to work better with epoch partitioning. Previously the only constraint was based around the time value of the epoch column and constraint exclusion would not work if the integer value was queried. This adds an additional constraint to the child tables so that exclusion should work for both time & integer based queries when epoch partitioning is used.(Github Issue #108). -- Fixed bug where if any time-custom partitions were set up, their configuration could possibly be used for any time based partition set (Github Pull Request #120). 2.3.4 -- IMPORTANT NOTICE: The next minor version (2.4.0) will officially drop support for the 1.x series. No further fixes (bugs, features, etc) will be backported and 1.8.8 will likely be the last version in the 1.x series. If you encounter a bug, the only supported method of fixing it will be to upgrade to the 2.x series where it may already have been fixed or you can submit an Issue on Github. -- Properly handle SECURITY DEFINER functions in a safe manner by resetting search_path as recommended in the PG docs (http://www.postgresql.org/docs/current/static/sql-createfunction.html#SQL-CREATEFUNCTION-SECURITY) (Github Pull Request #108). -- Made the exception handling in the partitioning trigger introduced in v2.3.3 optional. This was causing unexpected behavior whenever an exception was being handled by some other external process when it used a partitioned table. The trigger exception handling would take over and not allow the external process to handle it. New configuration option "trigger_exception_handling" has been added to the part_config (and part_config_sub) table to enable/disable this feature. It has been set to false by default to avoid unexpected default behavior which means any partition sets that had a trigger created by v2.3.3 will have the exception handling removed the next time it is updated. If you'd like to keep this feature, set the config table column to true. -- Added parameter p_exact_count to check_parent() to allow faster checks for data in parent table. If set to false, will only look for a single row existing in the parent instead of trying to get an exact count which could take a long time if a lot of data is in the parent (Github Pull Request #114). -- Further optimization to catalog lookups to improve performance on larger databases (Github Issue #107). 2.3.3 -- Fixed bug in partition_data_time() that was causing it to fail with custom time partition sets. This bug was introduced in v2.3.2. -- New constraint on partman config tables does not allow constraint_cols column to contain the control column. If you get this constraint volation during extension update, please review your part_config (and part_config_sub) table for this condition. You also likely have additional, unnecessary constraints on your child partitions that should be reviewed and removed. (Github Issue #100). -- Further optimizations for databases with many tables and large partition sets. Thanks to https://github.com/Crack for the contributions here (Github Issue #107). -- If an error is encountered when the partition trigger is run, pg_partman's trigger function now catches the exception and writes the row to the parent instead of just throwing an error. Reduces chance of losing data when encountering issues. Gives a WARNING in the log when this happens along with the original error that caused the issue. Thanks to David Turon for this suggestion (https://github.com/dturon/pg_partman/blob/lbox-trigger-support/sql/functions/create_function_id.sql#L192). -- Note this feature will not go into affect immediately upon extension update. It requires recreating the trigger function, which occurs when a new child table is created. You can force this by running create_function_id(parent_table) or create_function_time(parent_table). 2.3.2 -- Fixed issue where additional constraints would not get applied on all older partitions if more than one new partition was made for a single partition set in a single maintenance run. You can fix this by using the reapply_constraints.py script to remove then reapply all additional constraints on older tables. Or if you know the specific children that are missing a constraint, you can run apply_constraints() and pass it the child table argument. -- Improved performance of create_parent() when p_start_partition is set. Was previously doing a full seq scan of the parent table which could keep it locked longer than intended (Github Pull Reqest #99). -- New function that returns partition data about a given child name: show_partition_info(). -- Returns start/end values that are contained in a given child table. -- Can pass an optional interval value to see start/end values that are different than the currently configured interval. -- Added new options to vacuum_maintenance.py script -- New --all option tells it to run against all tables managed by pg_partman. -- New --type option sets whether to run against time or id based partition sets managed by pg_partman. -- New --interval option tells the script to only run against child tables older than the given interval. See the script's --help for more info on how this works. -- All these options only work on partition sets managed by pg_partman. If none of these options are set, script still works on non-pg_partman inheritance sets. -- Disallow weekly partitioning to be a subpartition of anything else. Alignment of ISO weeks does not always match up with larger intervals and can cause constraint conflicts. -- Also disallow subpartition interval to be equal to or greater than parent. -- Set part_config_sub & custom_time_partition config tables to dump their contents with pg_dump to ensure all config data is saved. 2.3.1 -- Fixed typo in standalone create_parent.sql file that caused a brand new installation of v2.3.0 to fail (Github Issue #94). -- Added --noparent option to new vacuum_maintenance.py script to allow parent table to be excluded from the maintenance run. -- Added vacuum_maintenance.py script to documentation. 2.3.0 -- New migration documentation (doc/migration.md) to provide assistance changing an existing partition set to be used by pg_partman. Note that pg_partman does not support having child table names that do not comply with its naming convention. -- Added new configuration option to configuration table for time-based partitioning: infinite_time_partitions (Github Pull Request 84) -- By default, if no new data is being inserted, no new partitions are created. This keeps pg_partman from creating new, empty tables infinitely when data insertion has stopped. -- Setting this value to true in part_config or part_config_sub will tell pg_partman to continue making partitions forever with time-based partitioning, even if there's no new data -- Note that serial/id partition is not affected by this and will only make new partitions if new data is being inserted. -- Implemented the clustering work done in fork by https://github.com/dturon/pg_partman -- If a parent table is set to cluster on a specific index, that clustering property will be inherited to all children. -- This does NOT actually cluster the table data, it only inherits the cluster property so that if/when CLUSTER is run on the partition set, it will use a default. -- Note that FILLFACTOR is not inherited at this time, so adjust your re-cluster schedule accordingly. -- Optimized catalog lookups throughout many functions to improve performance on databases with many tables (Github Issue 87). -- For custom time partition sets, their trigger function has had this optimization applied (normal time & id did not have this issue). The next time a child partition is created, the new function will be put in place. If you need it immediately, run: create_function_time('parent_table'). -- Changed the "optimize_constraints" config option to calculate the "old" partition based on what the current partition is instead of the data contained in the partition set. For time series with the new infinite option above set, this means the "current" partition is always moving forward despite no new data. This returns it to the behaviour it had before v2.2.0. The reapply_constraints.py script had not been updated properly to account for the new config option and was not calculating the "old" tables properly at all (it was looking for tables much further in the past than intended) (Github Issue #89). -- New python script to provide additional VACUUM maintenance to help avoid needless vacuums and transaction id wraparound issues: vacuum_maintenance.py -- The script checks the age(relfrozenid) value of all tables in a partition set (parent & children) and runs a VACUUM on them if they are higher than vacuum_freeze_min_age. -- Passing the --freeze option will run a VACUUM FREEZE on all tables in a set that match the above criteria. -- Passing the --full option does the same as above, but runs VACUUM FULL instead. Note this will lock all tables it is vacuuming, possibly even the parent, so review the --dryrun list before running it. -- An explanation for the necessity of using this script is far beyond what can be written in a CHANGELOG or the script help itself. Google is your friend. Basically if you have old, static child tables that will never be dropped, it is highly recommend you run this occasionally with the --freeze option to avoid future database maintenance issues. -- Pulled in bugfix for using the intarray contrib extension in conjunction with constraint management (Github Pull Request #88). -- Pulled in bugfix for using bigint instead of int type for variable in apply_constraints() (Github Pull Request #90). -- Fixed bug that may have caused serial trigger function to not contain all optimized child table conditions. No write failures would have occurred, just slower than expected performance. -- Fixed bug in partition_data.py & undo_partition.py that would cause either to throw an error and then not reset autovacuum if script was interrupted (Ctrl+C) (Github Issue #92). -- They also now properly handle SIGTERM to reset autovac as well. -- Note that disabling/enabling autovac now requires an extra available connection to the database. 2.2.3 -- Fixed bug in partition_data_time() that would cause an infinite loop when moving data would require creating a new partition newer than the newest one. This would only occur when using a custom time interval. Infinite loop would also occur when using the partition_data.py python script (Github Issue #83). -- Properly handle the special PUBLIC role when granting/revoking privileges on child tables (Github Issue #66). -- Fixed python exception in reapply_indexes.py if the parent table has no indexes (Github Issue #86). -- Fixed all undo_partition functions to remove the child table in the same batch session when its rowcount reaches zero. Previously an extra batch may have been required to remove all child tables and remove config information after the last row was moved to the parent. -- Fixed undo_partition functions to be more strict on the triggers they drop and ensure they only drop the trigger on the target parent table. -- Fixed undo_partition() to properly remove entries from the custom_time_partitions config table if necessary. Also greatly simplified code in this function. -- Consolidated privilege management into new apply_privileges() internal function. -- Improved performance when applying parent privileges to child tables. Most noticable on larger partition sets with many grants. (Github Issue #78) -- Added a sort to the Makefile when creating the sql extension file. Allows more predictable output between builds (Github Push Request #16 from Mimeo extension). 2.2.2 -- Fixed infinite loop in reapply_indexes.py if the same index name would have been used more than once. Thanks to bougyman for tha bug report. -- Fixed indexes being applied to the parent table instead of the children in reapply_indexes.py (Github Push Request #73). 2.2.1 -- Fixed search path bug with run_maintenance() introduced in v2.2.0. Was causing errors if the pg_partman schema was not in the search path of the role calling it. (Github Issue #71) 2.2.0 -- Added new optimization & constraint configuration options to part_config & part_config_sub (optimize_trigger, optimize_constraint) (Github Issue #62) -- The premake config value now only controls what its name suggests (the number of future partitions to premake) -- For existing partition sets, the values for these columns will be set equal to the current premake value -- Default value for optimize_trigger is same as default for premake (4) -- Default value for optimize_constraint is 30 (30 days, 30 months, etc) -- Not added as parameters to create_parent() or create_sub_parent() --These can be changed later and normal maintenance will take care of updating the current partition set to match. -- Time epoch partitioning is now possible (Github Issue #44) -- Control column can be an integer type but trigger, partition constraints and partition names will be based on a time interval. -- New boolean parameter to the create_parent()/create_sub_parent() functions. Ensure all of your current calls to this function account for it! -- Fixed bug introduced in v2.1.0 that caused run_maintenance() to not catch up if partition creation fell behind. (Github Issue #67) -- The fix for this issue also introduces new (improved) behavior for time-based partition maintenance. -- Previously, the premade tables were determined by the current time at the time maintenance ran. -- It is now instead based off of the maximum value in the partition set. -- This is essentially the same behavior that serial partitioning has always had. -- This means that if no new data is being inserted, once the premake value is met, no new partitions will be made. Previously they would be created no matter what, indefinitely. -- Resumption of data insertion after a large gap in data and maintenance runs may cause data to go into the parent until run_maintenance() is called again. -- New data should then go to new children, but data will then have to be cleaned out of the parent. -- This also means partitioning should work more predictably if data with a future timestamp is inserted. Previously this was not accounted for and could cause maintenance to work inconsistently. -- KNOWN BUGS: If you insert "future" data in a time-based, sub-partitioned set, maintenance may skip child tables in some partition sets depending how that data relates to the subpartition interval. -- Working on a fix for this, but didn't want to hold this release up anymore. To work around this, -- Increase the premake and/or optimize_trigger values appropriately to account for your normal data insertion window. -- Monitor for data going into parent tables and fix that as documented. -- Fixed bug in show_partitions() when used against a quarterly partitioned set. Wouldn't return all tables in expected order. -- Fixed bugs in reapply_constraints.py instroduced in v2.1.0 that prevented it from running. Increased minimum version requirement of pg_partman to 2.2.0. -- Made creation of additional column constraints more efficient (if used) and less spammy in the run_maintenance() jobmon logs. -- Added debug option to run_maintenance(). -- Fixed bug introduced in 2.1.0 that wasn't setting the retention config columns in part_config properly for multi-level subpartitions -- You may encounter an error during maintenance or during the upgrade related to inconsistent data in the part_config_sub table. There was a constraint in place before that tried to prevent this happening, but it was not doing it properly. There is now a new function in place that is part of routine maintenance that checks for this consistency. This checks to ensure that all sub-partition parents in part_config_sub that are themselves part of a sub-partition set have the same configuration values. You can run the function "check_subpart_sameconfig('parent_table')" for all sub-partitioned table sets to see which ones have inconsistent data. The function should only return a single row. If more than one row is returned then just update the config entries that are mismatched and this should clear the error. If you're still having problems, please open an Issue on Github with the error you receive and the full contents of your part_config & part_config_sub tables. -- If you encounter the error during the upgrade, run the query found in the 2.1.0 -> 2.2.0 upgrade file comments at the top using the parent table returned in the error for <<>> and setting <<>> to the schema you installed pg_partman to. Only a single row should be returned. If not then fix the mismatched data. 2.1.0 -- Object names (tables, roles, etc) with special characters & mixed case are now supported. -- Fixed bug in apply_foreign_keys() that was causing it to fail if there were multiple FKs to the same foreign column. Also fixed a bug that allowed the child name parameter to not be passed and didn't handle it if it wasn't. Thanks to Andrew Dunstan for the bug fix and tremendously simplified code. Backpatched to 1.8.8. (Github Issue #64). -- Ensure trigger function is recreated if a partition is dropped as part of retention. This makes sure the tables explicitily listed actually exist. Backpatched to 1.8.8 (Github Issue #62). -- Fixed bug in check_unique_constraint.py to properly inspect all data from an entire inheritance tree. If you were using this before to check for duplicates across children in an inheritance set, it is highly recommended you run it again as it may not have caught all dupes. -- New function 'show_partition_name()' can tell you the child table name that a given value would exist in given a parent table that pg_partman manages. The name will always be returned whether the child table exists or not. Another boolean column is returned that tells you whether the child table actually does exist. It also returns the raw value (timestamp or integer) for the suffix of the returned partition name (used internally, but could be useful elsewhere too). Thanks to Corey Huinker for idea & assistance. -- show_partitions() function now returns the schema and table name as two separate fields. -- New column in part_config to denote when a sub-partition set has had its final child partition made (sub_partition_set_full). Allows run_maintenance() to skip over it and run more efficiently when managing many sub-partition sets. -- Updated all python scripts to handle mixed-case & special characters. -- Bumped all minimum pg_partman version requirements for python scripts to 2.0.0. May still work on the older versions, but not officially supported or guaranteed anymore. -- New --nonpartman option to the reapply_indexes.py and reapply_foreign_keys.py scripts to allow them to work better with partition sets not managed by pg_partman and to allow those that are to work more efficiently. -- Greatly reduced number of individual pg_jobmon jobs generated. Steps such as creating partition functions, applying foreign keys, and applying additional constraints have been combined into the job log entries for creating partitions or general maintenance when applicable. -- Internal function check_name_length() no longer takes a schemaname argument, nor returns the schema as part of the modified object name. -- Simplified internal code for handling time suffix name generation -- Much more extensive pgTAP testing suites to handle more edge cases -- NOTE: If you are running version 1.8.7 and need to update to 1.8.8 do the following steps: -- Download the latest version as normal. -- Copy the file "updates/pg_partman--1.8.7--1.8.8.sql" to the folder where your extension SQL files are kept (depends on your OS or where you manually installed postgres). -- If you're running from less than 1.8.7, copy whichever prior update files you need to get from your version to 1.8.8. They are all kept in the updates folder. -- While logged into postgres run: ALTER EXTENSION pg_partman UPDATE TO '1.8.8'; -- The version number at the end of the above command is important. Otherwise it may try and update you to the latest 2.x version if you copied more update files then necessary or ran "make install". 2.0.0 -- IMPORTANT NOTE: This version, and all future versions of pg_partman >= 2.0.0, are only compatible with PostgreSQL 9.4 and greater. -- I do not have any current plans for development on the v1.x.x branch anymore. Reported bugs will be fixed, but all development will only be on the 2.x.x from here on out. Bug fixes will also only be done while versions of PostgreSQL older than 9.4 are themselves officially supported. If a bug only exists in an unsupported version of PostgreSQL, it WILL NOT be fixed. And once PostgreSQL 9.3 goes EOL, the 1.8 series will be deprecated entirely. -- No new features, API changes, configuration options or major internal structure changes will be accepted for the 1.x.x series. These will be locked to the 1.8.x series. No future 1.x.x versions after 1.8.7 will be able to be installed directly. You will have to install 1.8.7 (tagged version releases are available on github) and then install the updates found in the "updates" folder of later versions to get to the latest 1.x.x version. I unfortunately don't have time to be maintaining two independent branches with the way extension versions work now in PostgreSQL. Even fixing bugs in the 1.8.x series will be troublesome since I then have to redo every 1.8.x -> 2.x.x update file every time to ensure all fixes are applied no matter the upgrade path. This will unfortunately break managing the 1.x.x series via PGXN. I'm sorry but I don't see any other way around this with the way extension versions are managed right now. I highly recommend updating your database to 9.4+ if you wish to easily maintain this extension going forward. There's tons of other great new features and performance improvements, so it's definitely worth the effort! -- A background worker process (BGW) has been added to pg_partman for general partition maintenance. -- A separate scheduler is no longer required in most cases if you compile pg_partman with the background worker option. -- New postgresql.conf configuration options are listed in the documentation. You must at minimum give the database names you want the BGW to run for. The rest have defaults if not set. Only a reload is required to change values once the database is started with the BGW running. -- The BGW can only be started at cluster start. This may change later, but it is done this way for initial simplicity and with future design ideas in mind. -- There are no longer distinct "static" and "dynamic" partitioning modes. The features of each mode have been combined into a single trigger format. -- All triggers now have the static INSERT statements for the premake window first followed by a fallback, dynamic option to insert to the child table if it exists. -- Whenever the next child partition is created for a partition set, its function will automatically be updated to the new version. If you'd like to update all partition sets immediately, see the code following these release notes in the update file. I cannot make this automatically part of the update because doing so would cause all the new trigger functions that are created to be part of the extension. After updating, all old "static" sets will still work as they did before, but will have an additional fallback case to handle inserts to child tables outside the current premake value if the child table exists. All old "dynamic" sets will now have the "static" conditions added before the dynamic portion and you should see a performance improvement for any data inserted within the premake window. As before, any data that is inserted that has no associated child table will go to the parent. -- The "time-custom" partition type still exists and has not changed for those that need an interval other than the predefined ones. It still uses a lookup table and sacrifices performance for flexibility. -- Eliminated nearly half of the existing pgtap tests due to this change (but added some new ones). -- See my blog post for the explanation of what "static" and "dynamic" meant. http://www.keithf4.com/postgresql-partition-manager -- If retention system is turned on, jobmon no longer logs entries if no retention work was actually done. Would previously just log that zero tables were dropped. If anything is dropped/uninherited, it will be logged as expected. -- Changed column "type" in part_config to "partition_type". "type" is a reserved word, but not currently strictly enforced (doesn't require double-quoting). This avoids any possible future issues. Also changed sub_type in part_config_sub to "sub_partition_type" for consistency -- Changed column "part_interval" in part_config & part_config_sub to "partition_interval" to be more consistent with above renamed column. -- Now uses new, more extensive GET STACKED DIAGNOSTIC feature added in 9.2 to provide more detailed errors when an exception is encountered. Previously when functions called other functions and a custom exception block was used, only the latest function called would report the error. Now a more full stack trace is available to see the original function that caused the error. -- Thanks to https://github.com/IMSoP for the extensive documentation formatting improvements. 1.8.7 -- This update has no core code changes. It is being released to give users that ran into a situation where the trigger functions for serial partition sets called a function that was renamed. In v1.8.0 create_id_partition() was renamed to create_partition_id(). All core code was updated to use this new function, but any existing trigger function from previous versions may have the old function name in the code that creates new partitions when the current one reaches 50% of the max. -- The code block contained in "updates/pg_partman-1.8.6--1.8.7.sql" will go through and recreate all the trigger functions on all partition sets managed by pg_partman. This should fix the issue described above. While only serial partition sets should have been affected, the code below does it for both time & id to just ensure everything is fixed. -- This code MUST must be run manually because if the trigger functions are recreated as part of an extension update, then those trigger functions become members of the pg_partman extension itself. Just copy-n-paste it into psql and things should be good to go. -- Thanks to Jan Lentfer for finding this bug during v2.0.0 testing. 1.8.6 -- Fixed bug with run_maintenance() and subpartitioning. If you had any subpartitioned sets that went back a significant amount of time, or had a significant number of subpartitions, calls to run_maintenance() would seem to hang. It would finish eventually, but could take quite a long time and if you have pg_jobmon installed, would also cause many entries mentioning old partition sets. This bug was introduced with the fixed included in v1.8.5. 1.8.5 -- If run_maintenance() had not been called for a while outside of a partition set's interval, running it again was not creating the necessary child partitions to catch up. Recovery from this scenario was possible if the latest partition was manually created. This update fixes the problem and if you have a partition set that was not catching up before, running it with this version installed should fix things with no further manual intervention. This bug was introduced with version 1.8.0. (Github Issue #56). 1.8.4 -- When inheriting foreign keys to children, also account for the following additional options: -- MATCH FULL/PARTIAL/SIMPLE -- ON UPDATE/DELETE NO ACTION/RESTRICT/CASCADE/SET NULL/SET DEFAULT -- DEFERRABLE / NOT DEFERRABLE -- INITIALLY IMMEDIATE / INITIALLY DEFERRED , when it is DEFERRABLE, MATCH, & CASCADE actions. -- Note that none of the above properties were being inherited to child tables before. If you need to reapply foreign keys on children to enforce these options, see the reapply_foreign_keys.py python script or apply_foreign_keys() plpgsql function. The script is the preferred method to avoid contentions. -- reapply_foreign_keys.py claimed it could work on partition sets not managed by pg_partman, but that wasn't true. Removed dependency on show_partitions() function, so now that is true. 1.8.3 -- Fix both the retention system and the undo partitioning functions/scripts not cleaning up the custom_time_partitions table when using a custom time interval (Github Issue #49). -- When using sub-partitioning, the call to create_parent() that is within create_partition_id() & create_partition_time() was passing 2 of the parameters through incorrectly. p_use_run_maintenance was being fed to p_inherit_fk and vice versa, so the inherit_fk and use_run_maintenance columns in part_config & part_config_sub may be reversed. Since these are both boolean parameters, no error was being raised. If you've used sub-partitioning and used anything other than the default values for either of these configuration options, please double-check the part_config & part_config_sub tables to ensure the proper values are there. If you did not set them specifically, the default values were set for both and things should be fine. -- If p_use_run_maintenance was set wrong, you likely noticed that new partitions were not getting created for new sub-partition sets. You'll still have to fix any existing config settings, but future ones shoould be fine now. -- If p_inherit_fk was set wrong, child tables were likely not inheriting FKs or they were inheriting them when you didn't want them to. Again, fix this for existing partition sets by correcting the config table and all future sub-partitions should now be set properly. If you need to generate FKs on child tables that were missing them, you can use the reapply_foreign_keys.py script. 1.8.2 -- Fixed a bug in sub-partitioning that would cause child tables outside of the time boundaries of the parent partitions to be created when using time->time sub-partitioning. A user encountered the error when doing weekly->daily subpartitioning, but it was possible it could have happened in other interval combinations I had not tested as well (Github Issue #47). -- Updated reapply_indexes.py script to, by default, only add new indexes and drop ones that don't exist on the parent. Previously it would drop all indexes on all children and recreate them to match the parent. Now it only does the minimal amount of work to make the children match the parent. An additional option (--recreate_all/-R) was added to allow the old behavior of redoing all indexes from scratch if desired (Github Issue #41) -- Changed the minimal interval that serial partitioning can be done to 10. Ran into issues with an interval of 2 and partitioning anything this low is unrealistic and provides no benefit. 10 seems like a reasonable minimal to have at this point to avoid any future issues. This does not affect any existing partition sets, only newly created ones (Github Issue #39). -- Serial partition maintenance when using run_maintenance() is now much more efficient. Should run significantly faster for very large partition sets. -- Fixed bug in subpartition creation when using "time-custom" partitioning type. May have created subpartition child tables that were outside the time boundaries of the parent partitions. -- Fixed bug in additional constraint management when using "time-custom" partitioning type. May not have always added additional constraints on old child tables. -- Added constraint on part_config_sub to ensure valid partition types. 1.8.1 -- The p_analyze parameter to the apply_constraint() function is now FALSE by default instead of TRUE. This makes it so that by default, an analyze is only run by the create_partition_id/time() functions upon new partition creation. The parameter was left in apply_contraints() in case someone needs to call it directly and ensure an analyze is run so statistics are updated. (Github Issue #45) -- Note: If using reapply_constraints.py, an ANALYZE is always done at the end of the script. It was like that before this update. -- Changed the manner in which new partition creation is logged in pg_jobmon. Previously, each individual child table creation was logged as its own, separate job entry in pg_jobmon. Now, if multiple child partitions are created for a single partition set, all those child tables are logged as steps for a single job log entry. This now allows the analyze step (if it is done) to be logged as well in pg_jobmon and allows for easier diagnosis if this if holding up partition maintenance. 1.8.0 -- PG Partman now supports sub-partitioning. This allows automatic configuration to turn the child tables of an existing partition set into parent tables of their own partition sets. (Github Issue #26) -- New function "create_sub_parent()" works exactly like "create_parent()", even taking similar parameters. Instead, the parent_table you're giving it as a parameter is telling it which parent's child tables to partition and how to partition them. -- This can be chained down as many levels as desired. Just recall the 63 character limit on object names since this will be adding a new partition suffix every level down. The final suffix is always guarenteed to be added on in full, but the parent suffix name may get truncated off. -- Due to logical complexity (and possible contention issues at larger data sizes), when using subpartitioning, all parent tables at all partition levels are set to use run_maintenance() by default. This includes serial partitioning which normally by default can use a trigger based method to create future partitions. You can still set it to false so you can force maintenance to run at specific times (see new run_maintenance() feature below), but you MUST force it to run at some point otherwise new partitions will never be made. -- Note that there will ALWAYS be at least one child partition created, even for subpartition parents that are outside the current trigger range. Data outside the currently covered trigger range will still be inserted the the relevant parent. -- Note that for retention policies, whatever retention period is set on the highest level will be honored and ALL child tables will be dropped, cascading all the way down to the bottom. Use this option even more carefully! -- New parent table name parameter to run_maintenance(). If set, skips all other tables for that maintenance run and only does the one given. (Github Issue #32) -- This is an optional parameter, so should not affect any existing use of the function. When not given, maintenance is run for all partition sets set to use it in the part_config table. -- The already existing configuration option in part_config (use_run_maintenance) can be used to tell run_maintenance() to skip any partition sets for which you do not want it to run when no table name parameter is given. You can then schedule partition maintenance for specific tables to run at specific times using the new argument to run_maintenance(). Note that if a parent table argument is explicitely given to run_maintenance, it will always run the maintenance for it no matter what the configuration table has set. -- Note that when a table argument is given to run_maintenance(), retention settings will only be run for that one specific table given (if configured). -- Be aware that the "use_run_maintenance" configuration option is always set to true for time-based partitioning & subpartition sets and set false for serial based partitioning (when not subpartitioned) when calling create_parent() or create_sub_parent(). Adjust this configuration setting accordingly so run_maintenance() does what you require after you create your partition sets. -- The trigger constraint on the **part_config** table that would not allow "use_run_maintenance" to be set to false for time based partitioning has been removed. -- New analyze parameter to run_maintenance(). -- Defaults to true so that if any partition set has a new child table created, an analyze is run on that whole partition set. This is to ensure constraint exclusion works properly. -- Large partition sets were causing run_maintenance() to take a long time to run since the analyze would hold it up. This could cause some contention. -- Setting p_analyze to false will cause the analyze to not run for ALL partition sets that are eligible for new partition creation or retention management at the time it is called. -- If you set this to false, it is advised that you have some other means to ensure a regular analyze is being run on your partition sets. -- NOTE this parameter is set as the second argument since it's likely to be more commonly used, so make sure to check any current run_maintenence() calls to account for this (previously p_jobmon was the second parameter). -- Analyze is no longer automatically run on the parent table after create_parent() is run. Since create_parent() takes an exclusive lock on the parent table during setup, tables that already had a lot of existing data where being locked for the length of the analyze run, which could be quite long. When data is partitioned out later, analyze is automatically run. Also, whenever new partitions are created in the future, an analyze will be run as well (if the p_analyze argument to run_maintenance() is true which it is by default). Both those cases should take care of updating the planner statistics when it begins to matter. Run an analyze on the parent table after setup if you want to be sure. -- Fixed bug in show_partitions() that caused an error when the values in the control column of a serial partition set were larger than the max int value. This would also cause errors when partitioning existing data with values that high since the partitioning functions use show_partitions() internally. (Reported by S. Kristensen) -- create_parent() and new create_sub_parent() now return a boolean value to determine whether they succeeded. -- For all pythons scripts, changed the --connection default to "host=" instead of "host=localhost". This makes the default connection to the database use the local socket instead of TCP. Makes it act more predictibly like all other postgres executables (psql, pg_dump, etc). Please check any that you many have scheduled to run to ensure they are still working properly. -- Added a --version argument to all python scripts. This tells you the minimum version of pg_partman this script is meant to work with. -- Made sure all scripts in bin folder are added to Makefile for installation. -- Make sure autovacuum is reset if SIGINT (Ctrl+C) is fired when using partition_data.py or undo_partition.py. -- Added howto.md file to doc folder with some more extensive examples. -- last_partition column in part_config table no longer in use. Dropped it. -- Renamed internal functions create_id_partition(), create_id_function(), create_time_partition() & create_time_function() to create_partition_id(), create_function_id(), create_partition_time() & create_function_time() respectively. This gives all functions a consistent naming pattern. 1.7.2 -- Fixed bug in apply_foreign_keys() where new partition creation would fail when the partition set's schema is in the current search_path. Most commonly happened when partition sets with foreign keys were in public, but any schema in current search_path would cause this to manifest. Reported by Isaƭas SƔnchez via my blog. (Github Issue #27) -- Foreign key inheritance is now optional since more complex FK relationships may not work ideally with pg_partman's default method. New configuration option in part_config table and parameter to create_parent(). 1.7.1 -- Foreign keys placed on the parent table are now inherited to child tables. -- Any new partitions created after this update is installed will have the FKs applied to children. -- Existing child partitions will not have FKs applied. See the reapply_foreign_keys.py python script to reapply FKs to all child tables. -- The new apply_foreign_keys() function & reapply_foreign_keys.py script can be applied to any table inheritance set, not just the ones managed by pg_partman. -- See blog post for some more information about partitioning & foreign keys - http://www.keithf4.com/table-partitioning-and-foreign-keys -- Unlogged table property on parent table is now automatically inherited to child tables. Note this only applies to newly created child partitions after this update is installed. -- Added lockwait options to the undo partition functions (plpgsql & python) -- Added the same autovacuum feature & option to undo_partition.py that partition_data.py has. -- Made python scripts compatible with python 3 -- PgTAP tests for unlogged tables and FK inheritance 1.7.0 -- New configuration option to allow serial partitioning to use run_maintenance() instead of creating next partition via trigger. -- Use "p_use_run_maintenance" argument to create_parent() to set this during partition creation. -- part_config table has new boolean column "use_run_maintenance" -- Serial/ID based partitioning defaults to FALSE. This means serial partitioning uses the parent partition trigger function to make new child partitions when the current one reaches 50% of its configured capacity (the same way it used to work). If set to TRUE, then you must schedule run_maintenance() to run often enough to keep up with your insertion rate. Otherwise rows will get inserted to the parent table. -- Time based partitioning defaults to TRUE and config values for using run_maintenance cannot be set to false. All time-based partitioning still requires run_maintenance() for creating new child tables. -- Existing partition sets have their config table values set to the defaults above. -- If you'd like to change an existing serial partition set to use run_maintenance instead of the trigger, update the "use_run_maintenance" column in part_config to set it to TRUE for that parent table. You must then run the "create_id_function()" function giving it a parameter of the schema qualified parent table of the set. This will remove the code in the trigger that automatically makes new child tables. -- Ex: SELECT partman.create_id_function('parent_schema.parent_table'); -- reapply_indexes.py can now handle too long or duplicate index names. Please see docs for how this is handled since it can change index naming patterns (Github Issue #21). -- Fixed partition_data_id() & partition_data_time() to properly return the number of rows moved when the parent table is empty before the batch limit is reached (Github Issue #22). -- Fixed creation of new child partition tables not working when parent tables had OIDs turned on. (Github Issue #20) -- Fixed check_unique_constraint.py to avoid index scans and check underlying table data. Option added to try and allow index scans if desired. -- Fixed reapply_constraints.py & reapply_indexes.py to properly run jobs in parallel. -- Ensure an analyze is run on parent table of a set after any child table is created so that constraint exclusion works properly for all child tables. -- Ensure an analyze is run on a child table whenever additional column constraints are automatically added. Also analyze partition set if reapply_constraints.py is run. -- Added pgtap tests that ensure the partitioning functions are returning the proper number of rows. -- Added pgtap tests for new features in reapply_index.py 1.6.1 -- The python partitioning script now turns off autovacuum on the entire partition set while it is running. This should help reduce load since it will prevent the autovacuum daemon from kicking off while data is being migrated. When the script is done running, the default value for autovacuum is restored to all tables in the partition set. Also, VACUUM ANALYZE is run on the parent table when all data has finished moving as well. There is an option to disable the turning off of autovacuum if the ALTER TABLE statements are causing more contention and issues than the autovacuum. There is no option for turning off autovacuum when using the plpgsql partitioning functions (inability to COMMIT within function loop would cause too much contention). -- The order that data is migrated from the parent to the children can now be determined via an option to the partition_data_id/time() functions or the python script. The default is the way it originally moved data (ascending order). Thanks for bougyman from #postgresql on freenode for this idea. -- Removed plpgsql function "check_unique_column()" and created python script "check_unique_constraint.py". This runs far more efficiently and causes less contention within the database while checking if a unique constraint is consistent across all child tables. Also now supports checking multi-column constraints. See doc file for more info on script options. -- Fixed syntax error in create_parent(), create_id_function() exception blocks. Reported by bougyman. -- Added pgtap tests for additional constraints feature. 1.6.0 -- A new partitioning type has been added to allow setting almost any desired time interval (time-custom). The smallest interval supported is 1 second and the upper limit is bounded by the minimum and maximum timestamp values that PostgreSQL supports (http://www.postgresql.org/docs/current/static/datatype-datetime.html). This feature uses the range data type for internal configuration management, so it is only supported in PostgreSQL 9.2+. -- The custom time interval is less efficient than both time-static and time-dynamic since it must use a lookup table. If your needed partitioning interval can fit in one of the pre-made intervals given in the documentation, it is highly recommended to use one of those for better performance. time-static is still the best method when performance of inserts is important. See the documentation for more details on this new partitioning type. -- New parameter to create_parent() that sets what the first partition in the set will be (p_start_partition). -- Must be a valid timestamp (for time-based) or positive integer (for id-based) value. Be aware, though, the actual paramater data type is text. -- For time-based partitioning, all partitions starting with the given timestamp up to CURRENT_TIMESTAMP (plus premake) will be created. -- For id-based partitioning, only the partition starting at the given value (plus premake) will be made. -- pg_jobmon is now truly optional. Additonal configuration option for each individual partition set to turn it off and on. run_maintenance() now has an optional parameter to turn it off when being run. If you tried to partition pg_jobmon tables before, it would cause a permanent lockwait. Turn pg_jobmon off for those tables to avoid this. -- Fixed partition_data_time() & partition_data_id() functions to recreate the parent trigger function when static partitioning is used. Without this, partitioning more recent data that may have gotten into the parent table could possibly leave the function without conditions for the new partitions. run_maintenance() would eventually fix this for time partitioning, but id partitioning could be left in a broken state forever. (Github issue #16) -- Fixed bug in partition_data_time() & partition_data_id() to reset the lock wait counter properly between loops. Bug reported & fixed by bougyman from #postgresql on Freenode. -- pg_partman only supports id intervals greater than 1. May see if I can get an interval of 1 working later, but changed create_parent() to check for this and not allow it since it won't work properly at this time. New partitions were not automatically created if interval was set to 1. (Github issue #15) -- Clarify in docs that the id interval value passed to create_parent() must actually be in text type format. -- Changed drop & undo partition functions to use transaction based advistory locks. -- Removed need for internally used function create_next_time_partition() and therefore dropped the function. -- Simplified the create_time_partition() & create_id_partition() parameter lists. 1.5.1 -- Fix create_parent() to actually insert the contraint_cols value passed into the function to the config table when using time based partitioning. Thanks to Jeff Amiel for reporting the issue. 1.5.0 -- New functions that can manage additional constraints on child tables older than premake value based on what their min/max values are. This allows constraint exclusion to become usable on columns other than the partitioning column. However, this is only useful if older tables are no longer editing the columns that will have constraints placed on them, otherwise you risk constraint violations. See blog post for a more thorough explanation and examples: http://www.keithf4.com/managing-constraint-exclusion-in-table-partitioning -- New python script for using above functions to drop/apply constraints on an entire partition set without causing excessive lock issues. -- show_partitions() function is now guarenteed to return a list of partitions in the order that makes sense for the partition type/interval for the set. Additional option to specify ascending or descending order (defaults to ascending). -- Check for valid parameter values in partition creation function (github issue #14 from Josh Berkus) -- Added drop index concurrently option (--drop_concurrently) to reapply_indexes.py script. Only works for 9.2+ -- Changed run_maintenance() to use advisory transaction lock instead of session level lock. -- Fixed missing library import in python scripts. -- Organized docmentation of functions. 1.4.5 -- Fixed bug in reapply_indexes.py script that could cause all new indexes to be added to the parent instead of the children. This was happening if the parent table's schema was in the search_path of the role that the script uses to connect to the database. -- Removed any unneeded library imports in all python scripts. -- Moved python scripts from "extras" folder to "bin" folder. Now that they're actually getting installed as part of "make install" they're not really extras anymore. 1.4.4 -- Bug fix: Typos in partition_time_data/id() functions. Only ran into this if a lockwait was hit while trying to partition data. 1.4.3 -- Fix "make install" to work in PostgreSQL 9.3.x without throwing an error. -- "make install" now installs the python script files to /bin. They are now also executable and have the proper #! line at the top. -- Updated the rest of the python scripts to use argparse library for options (thanks to Josh Berkus for the assistance on this). -- Some of the command line options have changed for the scripts. See the --help for each script to ensure you are using the correct parameters. 1.4.2 -- Added lockwait functionality to background data partitioning, including partition_data_id, partition_data_time, and partition_data.py (Thanks to Josh Berkus for this feature). 1.4.1 -- Assign child partitions to the tablespace of the parent. This will only apply to newly created partitions after this update is installed. To fix existing partitions, you will have to manually alter the child tables. Thanks to https://github.com/joelhoffman for the fix. 1.4.0 -- Updated creation of child partition, function & trigger names to take into account the max object length an object can have to guarentee the partition suffix. Involved extensive rewrite of many core functions. -- WARNING: If your table names were already long enough to be causing name truncation (over 63 characters), you may get duplicate child tables, functions & triggers created. Please check your object name lengths on your partition sets before installing this update to see if you may be affected by this edge case and its subsequent fix. -- New python script (reapply-indexes.py) to re-apply indexes to child tables when they have changed on the parent. See docs for more info. -- New function to check the uniqueness of a column in a partition set (check_unique_column()). Helps to overcome the inability of a unique constraint to be applied efficiently across all partitions in a set. Does not prevent a unique violation, but provides a method to monitor for it happening. -- More pgTAP tests to ensure name trunucation process is working. -- Changed pgTAP tests to assume pgTAP is installed in public schema to try and avoid issues when it isn't. 1.3.0 -- New configuration option for retention system that allows child tables that are eligible for removal to instead be moved to another schema. Set the "retention_schema" option in the configuration table to move the table to the designated schema instead of dropping it. This overrides the retention_keep_table & retention_keep_index options. -- New python script, dump_partition.py, that will dump any tables found in a given schema using pg_dump, create a SHA-512 hash of the dumped file and then drop the table from the database. -- The combination of the retention_schema option and the dump_partition.py script give a way to reliably dump out tables for archiving when they are no longer needed in the database. Idea for this feature adapted from conversation at PGDay NYC 2013 (lost the card of the individual I was talking with :( ). -- New function show_partitions() that gives a list of child tables in a partition set. Adapted from fork by https://github.com/ebaptistella -- Previously the functions that created the new partitions were using only the "INCLUDING DEFAULTS INCLUDING INDEXES" options when using the CREATE TABLE ... (LIKE ...) syntax. This caused some contraints on the parent to be missed in child tables. Changed to include all available options as of PostgreSQL 9.1: INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS. Change will apply to all newly created child tables in all partition sets managed by pg_partman. You'll have to go back and manually fix any already existing child tables that may be missing constraints. Issue reported by Nick Ebbitt. -- Added TAP tests for drop partition functions. -- Fixed some tap tests to more accurately test for table (non)existance -- Clarified the drop_partition_id() function's retention parameter meaning. 1.2.0 -- Bug fix: Make child table lookups more intelligent to be able to deal with schemas being in the current search_path. Functions this affects are: drop_time_partition(), drop_id_partition(), reapply_privileges(), undo_partition(), undo_partition_id(), undo_partition_time(). Before table names may not have matched properly when looping through all tables to drop or reset privileges. Thanks to https://github.com/terrorobe for reporting this issue. -- Bug fix: reapply_privileges() had unconditional calls to pg_jobmon functions and would fail if it wasn't installed. -- Added new parameter to drop partition functions to manually set an interval you'd like to drop. Makes it easier to cleanup a bunch of old partitions you don't need anymore without having to go through the whole retention policy setup if that's not needed. -- Renamed drop_time_partition() to drop_partition_time() and drop_id_partition() to drop_partition_id() to be more consistent with the other function names. Please check function ownership & privileges before and after update to ensure they are reset properly. 1.1.0 -- New python scripts in extras folder to allow partition creation and undoing using smaller commit batches, as is suggested in the documentation for the partition_data_* and undo_partition_* functions. This helps avoid transaction locks when there is a large amount of data to move around. There are also options to commit more slowly and ease the load on very busy systems. -- Changed the ordering of batch arguments in partition_data_id() & partition_data_time(). This makes their order the same as the undo functions and is a more sensical order (I think anyway). -- Made partition functions quieter. No more notices and just returns number of rows moved. -- Changed the undo partition functions to remove partitions in the order they were originally created. They were doing it alphabetically before, which could cause an odd order for serial based partitioning (p100 would be before p2). Creation order may not remove them in ascending order of the data at first, which would be ideal, but it makes more sense than alphabetically. -- Bug fix: undo_partition() could return 0 prematurely if some of the partitions were empty. Will now automatically uninherit/drop any empty partitions and continue on if there are still child tables, not counting them against p_batch_count if given. 1.0.0 -- New functions to undo partitioning. These all either move or copy data from the child tables and put it into the parent. All have an option to allow you either uninherit the child tables (default) or drop them when all their data has been put into the parent. -- undo_partition_time() & undo_partition_id are functions that move the data from the child partitions to the parent tables. Data is deleted from the child table and inserted to the parent. These functions allow smaller interval batches to be given as a parameter and are better able to handle larger partitioning sets. -- undo_partition() can work on an any parent/child table set in PostgreSQL, not just partition sets created by pg_partman. Just pass it the name of the parent table. This method only copies the data out of the child tables instead of deleting it, allowing you to keep all the partitioned data if desired. Because of this it can only process an entire partition at a time and cannot handle batches smaller than the partition interval. -- Changed create_prev_id_partition() to partition_data_id() & create_prev_time_partition() to partition_data_time(). This clarifies what these actually do since they don't always create a partition nor is it always necessarily "previous" data. -- Changed how the above functions work to move data from parent into partitions. You can now feed them a smaller interval value for the rows that you'd like moved instead of it always moving exactly one entire partition of data. This allows smaller batch sizes when you've got a lot of data even in just one partition. That interval is now the second parameter. A third parameter can tell it how many of those interval batches you'd like to move in a single run of the function. Both of these parameters are optional. If not given, the interval defaults to the partition interval and the batch count is one (so it works exactly like it used to with no parameters but the parent table given). -- Partition premake system is now able to catch up if it falls behind for some reason. Also makes it so that if the premake value is increased, within the next few runs it will have that many partitions premade automatically. -- Bug fix: create_time_partition() & create_time_function() now handle the "timestamp with time zone" data type much better. Was getting some mismatches in the trigger rules and table constraints when timestamptz was in use on server not running in UTC/GMT time. Would cause constraint violations during data insert at certain time boundaries. If you ran into this issue, there are two ways to fix it: 1) Manually recreate the constraints for the most recent partitions and any future partitions already created. You may have to move some data around as well. 2) Use the new undo functions to move all the data back into the parent table and then repartition again using the partition_data_* functions. This will fix the issue for all partitions. -- Bug fix: Determining how many partitions to premake in run_maintenance() is now more accurate. Previous date math would occasionally premake 1 extra partition depending on the time differences. This can still occur with weekly partitioning due to differing month lengths (especially February) and daylight savings. Doesn't hurt anything and will self-correct. -- Much more complete pgTAP test suite. 0.4.2 -- The static partitioning trigger function can now handle partitions based on the configured premake value. For example, the default premake value is 4 so it can now handle data for the current partition, 4 previous partitions and 4 future partitions. Changing the premake value will cause the trigger function to be changed appropriately the next time a partition is automatically created. Except for initial setup, at no time does the automated partitioning system create old partitions (see the create_prev_* functions if you need to do this). If you change the premake value and there is no previous partition for it to put data in, it will go to the parent table. -- create_parent() now accounts for the new static partitioning rules. For time-static, it will create the current partition as well as previous and future partitions equal to the configured premake number (default premake being 4, you will end up with 9 partitions). For id-static, it will only create previous partitions if the resulting rules handle id values greater than zero. So if you're starting from zero you will only have future partitions created, and no previous. -- Constraint now ensures that premake value is greater than zero. -- create_parent() now ensures interval value for serial partitioning is greater than zero. -- Much more extensive pgTAP tests. 0.4.1 -- Changed the privilege management system to apply the current parent's privileges only to new child tables at the time they're created. No longer re-applies privileges to existing child tables. When partition sets grew large, this was causing serious performance problems and was too expensive an operation to run every time a child was created. -- Dropped apply_grants() function. New child table privileges are now managed by the partition creation functions themselves. -- Created reapply_privileges() function to reset the privileges of all child tables in a given partition set. Uses the given parent's privileges at the time the function is run. All new grants will be set and all that don't exist will be revoked. Ownership will be updated if it has changed. -- First round of pgTAP tests. 0.4.0 -- No separate configuration required for setting privileges on child tables anymore. Grants config table has been dropped. Please apply the grants you need to the parent table and they will be set for all children using that. Note that unlike before, privilges that don't exist on the parent will now be revoked from all child tables. -- create_parent() now enforces that a given parent table be schema qualified. Ensures that a custom search_path doesn't affect the wrong table by accident. -- Removed enum custom type and replace with check function. -- Applying of grants is now logged in pg_jobmon so if there's any issues with that step, it's clear where it failed. 0.3.2 -- Allow multiple grant commands for the same partition set in case different roles need different grants. Removed primary key constraint from part_grants table and updated apply_grants function -- create_parent() function now ensures that the control column has a not null constraint. -- Make select-only functions STABLE 0.3.1 -- Added check to dynamic id & time trigger functions to see if target table exists. If it doesn't, insert to parent instead of throwing error. Better than losing data! check_parent() function can monitor for this happening and create_prev_* functions can easily fix it. Thought of having it auto-create the needed partition, but if something is going wrong, that could end up creating a lot of unwanted partitions and be harder to clean up. 0.3.0 -- Added grants configuration table to propagate permissions to newly created child partitions. Will also apply those permissions to the parent table and all existing child tables whenever a new partition is created. Permissions are only granted, never revoked. See docs for more info. 0.2.0 -- New functions to manage dropping old partitions. Does not actually need to be called directly unless necessary. Use run_maintenance() function. -- Added ability to run_maintenance() function to manage dropping old tables in addition to managing time-based partitioning. -- Removed raise notice in run_maintenance and make sure old search path is reset after function finishes running. -- Lot of documentation updates 0.1.2 -- Added support for quarterly time partitioning (trickier than it first appeared) -- Fixed bug in run_maintenance() that would give an invalid cast to integer error. -- Fixed some calls to pg_jobmon that were outside the checks to see if it's actually installed -- Properly reset search path back to original before partman functions were run if pg_jobmon is being used -- Changed the default premake to 4 instead of 3. This will cause pg_jobmon's default monitoring for 3 consecutive failing jobs to trigger an before the last premade partition is used up. -- Added optional jobmon logging to run_maintenance() so that if it fails, pg_jobmon can notify that maintenance didn't work. 0.1.1 -- Only re-create partition functions if a new partition is made. pg_partman-4.6.0/LICENSE.txt000066400000000000000000000022111412761706100154750ustar00rootroot00000000000000LICENSE AND COPYRIGHT --------------------- PG Partition Manager (pg_partman) is released under the PostgreSQL License, a liberal Open Source license, similar to the BSD or MIT licenses. Copyright (c) 2017 OmniTI, Inc. Copyright (c) 2018-2021 Crunchy Data Solutions, Inc. Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies. IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE AUTHOR HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. pg_partman-4.6.0/META.json000066400000000000000000000025521412761706100153030ustar00rootroot00000000000000{ "name": "pg_partman", "abstract": "Extension to manage partitioned tables by time or ID", "version": "4.6.0", "maintainer": [ "Keith Fiske " ], "license": "postgresql", "generated_by": "Keith Fiske", "release_status": "stable", "prereqs": { "runtime": { "requires": { "PostgreSQL": "9.6.0" }, "recommends": { "pg_jobmon": "1.4.1" } } }, "provides": { "pg_partman": { "file": "sql/pg_partman--4.6.0.sql", "docfile": "doc/pg_partman.md", "version": "4.6.0", "abstract": "Extension to manage partitioned tables by time or ID" } }, "resources": { "bugtracker": { "web": "https://github.com/pgpartman/pg_partman/issues" }, "repository": { "url": "git://github.com/pgpartman/pg_partman.git" , "web": "https://github.com/pgpartman/pg_partman", "type": "git" } }, "meta-spec": { "version": "1.0.0", "url": "http://pgxn.org/meta/spec.txt" }, "tags": [ "partition", "partitions", "partitioning", "table", "tables", "bgw", "background worker", "custom background worker" ] } pg_partman-4.6.0/Makefile000066400000000000000000000030211412761706100153120ustar00rootroot00000000000000EXTENSION = pg_partman EXTVERSION = $(shell grep default_version $(EXTENSION).control | \ sed -e "s/default_version[[:space:]]*=[[:space:]]*'\([^']*\)'/\1/") PG_CONFIG = pg_config PG94 = $(shell $(PG_CONFIG) --version | egrep " 8\.| 9\.0| 9\.1| 9\.2| 9\.3" > /dev/null && echo no || echo yes) PG11 = $(shell $(PG_CONFIG) --version | egrep " 8\.| 9\.| 10\." > /dev/null && echo no || echo yes) ifeq ($(PG94),yes) DOCS = $(wildcard doc/*.md) MODULES = src/pg_partman_bgw # If user does not want the background worker, run: make NO_BGW=1 ifneq ($(NO_BGW),) MODULES= endif all: sql/$(EXTENSION)--$(EXTVERSION).sql # Only include scripts common to all version of PG in PG11+. # Otherwise if less than PG11, include the old python scripts along with common. # Only include procedures in PG11+ ifeq ($(PG11),yes) SCRIPTS = bin/common/*.py sql/$(EXTENSION)--$(EXTVERSION).sql: $(sort $(wildcard sql/types/*.sql)) $(sort $(wildcard sql/tables/*.sql)) $(sort $(wildcard sql/functions/*.sql)) $(sort $(wildcard sql/procedures/*.sql)) cat $^ > $@ else SCRIPTS = bin/common/*.py bin/pg10/*.py sql/$(EXTENSION)--$(EXTVERSION).sql: $(sort $(wildcard sql/types/*.sql)) $(sort $(wildcard sql/tables/*.sql)) $(sort $(wildcard sql/functions/*.sql)) cat $^ > $@ # # end PG11 if endif DATA = $(wildcard updates/*--*.sql) sql/$(EXTENSION)--$(EXTVERSION).sql EXTRA_CLEAN = sql/$(EXTENSION)--$(EXTVERSION).sql else $(error Minimum version of PostgreSQL required is 9.4.0) # end PG94 if endif PGXS := $(shell $(PG_CONFIG) --pgxs) include $(PGXS) pg_partman-4.6.0/README.md000066400000000000000000000167321412761706100151460ustar00rootroot00000000000000[![PGXN version](https://badge.fury.io/pg/pg_partman.svg)](https://badge.fury.io/pg/pg_partman) PG Partition Manager ==================== pg_partman is an extension to create and manage both time-based and serial-based table partition sets. Native partitioning in PostgreSQL 10 is supported as of pg_partman v3.0.1 and much more extensively as of 4.0.0 along with PostgreSQL 11. Note that all the features of trigger-based partitioning are not yet supported in native, but performance in both reads & writes is significantly better. Child table creation is all managed by the extension itself. For non-native, trigger function maintenance is also handled. For non-native partitioning, tables with existing data can have their data partitioned in easily managed smaller batches. For native partitioning, the creation of a new partitioned parent must be done first and the data migrated over after setup is complete. Optional retention policy can automatically drop partitions no longer needed for both native and non-native partitioning. A background worker (BGW) process is included to automatically run partition maintenance without the need of an external scheduler (cron, etc) in most cases. Bug reports & feature requests can be directed to the Issues section on Github - https://github.com/pgpartman/pg_partman/issues For questions, comments, or if you're just not sure where to post, please use the Discussions section on Github. Feel free to post here no matter how minor you may feel your issue or question may be - https://github.com/pgpartman/pg_partman/discussions If you're looking for a partitioning system that handles any range type beyond just time & serial, the new native partitioning features in PostgreSQL 10+ are likely the best method for the foreseeable future. If this is something critical to your environment, start planning your upgrades now! If you're still trying to evaluate whether partitioning is a good choice for your environment, keep an eye on the HypoPG project. Version 2 will have a hypothetical partitioning feature that will let you evaluate different partitioning schemes without requiring you to actually partition your data. I may see about integrating this feature into pg_partman once it is available. - https://hypopg.readthedocs.io INSTALLATION ------------ Requirement: * PostgreSQL >= 9.6 Recommended: * Native partitioning is highly recommended over trigger-based and PG11+ is HIGHLY recommended over PG10. * pg_jobmon (>=v1.4.0). PG Job Monitor will automatically be used if it is installed and setup properly. https://github.com/omniti-labs/pg_jobmon In the directory where you downloaded pg_partman, run make install If you do not want the background worker compiled and just want the plain PL/PGSQL functions, you can run this instead: make NO_BGW=1 install The background worker must be loaded on database start by adding the library to shared_preload_libraries in postgresql.conf shared_preload_libraries = 'pg_partman_bgw' # (change requires restart) You can also set other control variables for the BGW in postgresql.conf. "dbname" is required at a minimum for maintenance to run on the given database(s). These can be added/changed at anytime with a simple reload. See the documentation for more details. An example with some of them: pg_partman_bgw.interval = 3600 pg_partman_bgw.role = 'keith' pg_partman_bgw.dbname = 'keith' Log into PostgreSQL and run the following commands. Schema is optional (but recommended) and can be whatever you wish, but it cannot be changed after installation. If you're using the BGW, the database cluster can be safely started without having the extension first created in the configured database(s). You can create the extension at any time and the BGW will automatically pick up that it exists without restarting the cluster (as long as shared_preload_libraries was set) and begin running maintenance as configured. CREATE SCHEMA partman; CREATE EXTENSION pg_partman SCHEMA partman; As of version 4.1.0, pg_partman no longer requires a superuser to run for native partitioning. Trigger-based partitioning still requires it, so if you want to not require superuser, look to migrating to native partitioning. Superuser is still required to install pg_partman. It is recommended that a dedicated role is created for running pg_partman functions and to be the owner of all partition sets that pg_partman maintains. At a minimum this role will need the following privileges (assuming pg_partman is installed to the "partman" schema and that dedicated role is called "partman"): CREATE ROLE partman WITH LOGIN; GRANT ALL ON SCHEMA partman TO partman; GRANT ALL ON ALL TABLES IN SCHEMA partman TO partman; GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA partman TO partman; GRANT EXECUTE ON ALL PROCEDURES IN SCHEMA partman TO partman; -- PG11+ only GRANT ALL ON SCHEMA my_partition_schema TO partman; GRANT TEMPORARY ON DATABASE mydb to partman; -- allow creation of temp tables to move data out of default If you need the role to also be able to create schemas, you will need to grant create on the database as well. In general this shouldn't be required as long as you give the above role CREATE privileges on any pre-existing schemas that will contain partition sets. GRANT CREATE ON DATABASE mydb TO partman; I've received many requests for being able to install this extension on Amazon RDS. As of PostgreSQL 12.5, RDS has made the pg_partman extension available. Many thanks to the RDS team for including this extension in their environment! https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/PostgreSQL_Partitions.html UPGRADE ------- Run "make install" same as above to put the script files and libraries in place. Then run the following in PostgreSQL itself: ALTER EXTENSION pg_partman UPDATE TO ''; If you are doing a pg_dump/restore and you've upgraded pg_partman in place from previous versions, it is recommended you use the --column-inserts option when dumping and/or restoring pg_partman's configuration tables. This is due to ordering of the configuration columns possibly being different (upgrades just add the columns onto the end, whereas the default of a new install may be different). If upgrading between any major versions of pg_partman (2.x -> 3.x, etc), please carefully read all intervening version notes in the CHANGELOG, especially those notes for the major version. There are often additional instructions (Ex. updating trigger functions) and other important considerations for the updates. EXAMPLES -------- For setting up native partitioning with pg_partman on a brand new table, or to migrate an existing normal table to native partitioning, see [pg_partman_howto_native.md](doc/pg_partman_howto_native.md). For migrating a trigger-based partitioned table to native partitioning using pg_partman, see [migrate_to_native.md](doc/migrate_to_native.md). Other HowTo documents are also available in the documents folder. See the [pg_partman.md file](doc/pg_partman.md) in the doc folder for full details on all commands and options for pg_partman. TESTING ------- This extension can use the pgTAP unit testing suite to evalutate if it is working properly (http://www.pgtap.org). WARNING: You MUST increase max_locks_per_transaction above the default value of 64. For me, 128 has worked well so far. This is due to the sub-partitioning tests that create/destroy several hundred tables in a single transaction. If you don't do this, you risk a cluster crash when running subpartitioning tests. pg_partman-4.6.0/bin/000077500000000000000000000000001412761706100144265ustar00rootroot00000000000000pg_partman-4.6.0/bin/common/000077500000000000000000000000001412761706100157165ustar00rootroot00000000000000pg_partman-4.6.0/bin/common/check_unique_constraint.py000077500000000000000000000104331412761706100232030ustar00rootroot00000000000000#!/usr/bin/env python import argparse, collections, psycopg2, sys, tempfile partman_version = "2.0.0" parser = argparse.ArgumentParser(description="This script is used to check that all rows in a partition set are unique for the given columns. Since unique constraints are not applied across partition sets, this cannot be enforced within the database. This script can be used as a monitor to ensure uniquness. If any unique violations are found, the values, along with a count of each, are output.") parser.add_argument('-p', '--parent', help="Parent table of the partition set to be checked") parser.add_argument('-l', '--column_list', help="Comma separated list of columns that make up the unique constraint to be checked") parser.add_argument('-c','--connection', default="host=", help="""Connection string for use by psycopg. Defaults to "host=" (local socket).""") parser.add_argument('-t', '--temp', help="Path to a writable folder that can be used for temp working files. Defaults system temp folder.") parser.add_argument('--psql', help="Full path to psql binary if not in current PATH") parser.add_argument('--simple', action="store_true", help="Output a single integer value with the total duplicate count. Use this for monitoring software that requires a simple value to be checked for.") parser.add_argument('--index_scan', action="store_true", help="By default index scans are disabled to force the script to check the actual table data with sequential scans. Set this option if you want the script to allow index scans to be used (does not guarentee that they will be used).") parser.add_argument('-q', '--quiet', action="store_true", help="Suppress all output unless there is a constraint violation found.") parser.add_argument('--version', action="store_true", help="Print out the minimum version of pg_partman this script is meant to work with. The version of pg_partman installed may be greater than this.") args = parser.parse_args() if args.version: print(partman_version) sys.exit() if args.parent == None: print("-p/--parent option is required") sys.exit(2) if args.column_list == None: print("-l/--column_list option is required") sys.exit(2) if args.temp == None: tmp_copy_file = tempfile.NamedTemporaryFile(prefix="partman_constraint") else: tmp_copy_file = tempfile.NamedTemporaryFile(prefix="partman_constraint", dir=args.temp) fh = open(tmp_copy_file.name, 'w') conn = psycopg2.connect(args.connection) conn.set_session(isolation_level="REPEATABLE READ", readonly=True) cur = conn.cursor() # Get separate object names so they can be properly quoted sql = "SELECT schemaname, tablename FROM pg_catalog.pg_tables WHERE schemaname||'.'||tablename = %s" cur.execute(sql, [args.parent]) result = cur.fetchone() if result == None: print("Given parent table ("+args.parent+") does not exist") sys.exit(2) quoted_parent_table = "\"" + result[0] + "\".\"" + result[1] + "\"" #print(quoted_parent_table) # Recreated column list with double quotes around each cols_array = args.column_list.split(",") quoted_col_list = "\"" + "\",\"".join(cols_array) + "\"" #print(quoted_col_list) if args.index_scan == False: sql = """set enable_bitmapscan = false; set enable_indexonlyscan = false; set enable_indexscan = false; set enable_seqscan = true;""" else: sql = """set enable_bitmapscan = true; set enable_indexonlyscan = true; set enable_indexscan = true; set enable_seqscan = false;""" cur.execute(sql) cur.close() cur = conn.cursor() if not args.quiet: print("Dumping out column data to temp file...") copy_statement = "COPY (SELECT " + quoted_col_list + " FROM " + quoted_parent_table + ") TO STDOUT WITH DELIMITER ','" #print(copy_statement) cur.copy_expert(copy_statement, fh) conn.rollback() conn.close() fh.close() total_count = 0 if not args.quiet: print("Checking for dupes...") with open(tmp_copy_file.name) as infile: counts = collections.Counter(l.strip() for l in infile) for line, count in counts.most_common(): if count > 1: if not args.simple: print(str(line) + ": " + str(count)) total_count += count if args.simple: if total_count > 0: print(total_count) elif not args.quiet: print(total_count) else: if total_count == 0 and not args.quiet: print("No constraint violations found") pg_partman-4.6.0/bin/common/dump_partition.py000077500000000000000000000132051412761706100213320ustar00rootroot00000000000000#!/usr/bin/env python import argparse, hashlib, os, os.path, psycopg2, subprocess, sys partman_version = "2.0.0" parser = argparse.ArgumentParser(description="This script will dump out and then drop all tables contained in the designated schema using pg_dump. Each table will be in its own separate file along with a SHA-512 hash of the dump file. Tables are not dropped from the database if pg_dump does not return successfully. All dump_* option defaults are the same as they would be for pg_dump if they are not given.", epilog="NOTE: The connection options for psyocpg and pg_dump were separated out due to distinct differences in their requirements depending on your database connection configuration.") parser.add_argument('-n','--schema', help="(Required) The schema that contains the tables that will be dumped.") parser.add_argument('-c','--connection', default="host=", help="""Connection string for use by psycopg. Role used must be able to select pg_catalog.pg_tables in the relevant database and drop all tables in the given schema. Defaults to "host=" (local socket). Note this is distinct from the parameters sent to pg_dump.""") parser.add_argument('-o','--output', default=os.getcwd(), help="Path to dump file output location. Default is where the script is run from.") parser.add_argument('-d','--dump_database', help="Used for pg_dump, same as its --dbname (-d) option or final database name parameter.") parser.add_argument('--dump_host', help="Used for pg_dump, same as its --host (-h) option.") parser.add_argument('--dump_username', help="Used for pg_dump, same as its --username (-U) option.") parser.add_argument('--dump_port', help="Used for pg_dump, same as its --port (-p) option.") parser.add_argument('--pg_dump_path', help="Path to pg_dump binary location. Must set if not in current PATH.") parser.add_argument('--Fp', action="store_true", help="Dump using pg_dump plain text format. Default is binary custom (-Fc).") parser.add_argument('--nohashfile', action="store_true", help="Do NOT create a separate file with the SHA-512 hash of the dump. If dump files are very large, hash generation can possibly take a long time.") parser.add_argument('--nodrop', action="store_true", help="Do NOT drop the tables from the given schema after dumping/hashing.") parser.add_argument('-v','--verbose', action="store_true", help="Provide more verbose output.") parser.add_argument('--version', action="store_true", help="Print out the minimum version of pg_partman this script is meant to work with. The version of pg_partman installed may be greater than this.") args = parser.parse_args() def create_hash(table_name): output_file = os.path.join(args.output, args.schema + "." + table_name + ".pgdump") try: with open(output_file, "rb") as fh: shash = hashlib.sha512() while True: data = fh.read(8192) if not data: break shash.update(data) except IOError as e: print("Cannot access dump file for hash creation: " + e.strerror) sys.exit(2) hash_file = os.path.join(args.output, args.schema + "." + table_name + ".hash") if args.verbose: print("hash_file: " + hash_file) try: with open(hash_file, "w") as fh: fh.write(shash.hexdigest() + " " + os.path.basename(output_file)) except IOError as e: print("Unable to write to hash file: " + e.strerror) sys.exit(2) def drop_table(table_name): conn = psycopg2.connect(args.connection) cur = conn.cursor() sql = "DROP TABLE IF EXISTS \"" + args.schema + "\".\"" + table_name + "\""; print(sql) cur.execute(sql) conn.commit() cur.close() conn.close() def get_tables(): conn = psycopg2.connect(args.connection) cur = conn.cursor() sql = "SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname = %s"; cur.execute(sql, [args.schema]) result = cur.fetchall() cur.close() conn.close() return result def perform_dump(result): table_name = result.pop()[0] processcmd = [] if args.pg_dump_path != None: processcmd.append(args.pg_dump_path) else: processcmd.append("pg_dump") if args.dump_host != None: processcmd.append("--host=" + args.dump_host) if args.dump_port != None: processcmd.append("--port=" + args.dump_port) if args.dump_username != None: processcmd.append("--username=" + args.dump_username) if args.Fp: processcmd.append("--format=plain") else: processcmd.append("--format=custom") processcmd.append("--table=\"" + args.schema + "\".\"" + table_name + "\"") output_file = os.path.join(args.output, args.schema + "." + table_name + ".pgdump") processcmd.append("--file=" + output_file) if args.dump_database != None: processcmd.append(args.dump_database) if args.verbose: print(processcmd) try: subprocess.check_call(processcmd) except subprocess.CalledProcessError as e: print("Error in pg_dump command: " + str(e.cmd)) sys.exit(2) return table_name def print_version(): print(partman_version) sys.exit() if __name__ == "__main__": if args.version: print_version() if args.schema == None: print("-n/--schema option is required") sys.exit(2) if not os.path.exists(args.output): print("Path given by --output (-o) does not exist: " + str(args.output)) sys.exit(2) result = get_tables() while len(result) > 0: table_name = perform_dump(result) if not args.nohashfile: create_hash(table_name) if not args.nodrop: drop_table(table_name) pg_partman-4.6.0/bin/common/reapply_indexes.py000077500000000000000000000403131412761706100214670ustar00rootroot00000000000000#!/usr/bin/env python import argparse, psycopg2, re, sys, time from multiprocessing import Process partman_version = "4.1.0" parser = argparse.ArgumentParser(description="Script for reapplying indexes on child tables in a partition set to match the parent table. Any indexes that currently exist on the children and match the definition on the parent will be left as is. There is an option to recreate matching as well indexes if desired, as well as the primary key. Indexes that do not exist on the parent will be dropped. Commits are done after each index is dropped/created to help prevent long running transactions & locks.", epilog="NOTE: New index names are made based off the child table name & columns used, so their naming may differ from the name given on the parent. This is done to allow the tool to account for long or duplicate index names. If an index name would be duplicated, an incremental counter is added on to the end of the index name to allow it to be created. Use the --dryrun option first to see what it will do and which names may cause dupes to be handled like this.") parser.add_argument('-p', '--parent', help="Parent table of an already created partition set. (Required)") parser.add_argument('-c','--connection', default="host=", help="""Connection string for use by psycopg. Defaults to "host=" (local socket).""") parser.add_argument('--concurrent', action="store_true", help="Create indexes with the CONCURRENTLY option. Note this does not work on primary keys when --primary is given.") parser.add_argument('--drop_concurrent', action="store_true", help="If an index is dropped (because it doesn't exist on the parent or because you set them to be recreated), do it concurrently. Note this does not work on primary keys when --primary is given.") parser.add_argument('-R', '--recreate_all', action="store_true", help="By default, if an index exists on a child and matches the parent, it will not be touched. Setting this option will force all child indexes to be dropped & recreated. Will obey the --concurrent & --drop_concurrent options if given. Will not recreate primary keys unless --primary option is also given.") parser.add_argument('--primary', action="store_true", help="By default the primary key is not recreated. Set this option if that is needed. Note this will cause an exclusive lock on the child table.") parser.add_argument('-j', '--jobs', type=int, default=0, help="Use the python multiprocessing library to recreate indexes in parallel. Value for -j is number of simultaneous jobs to run. Note that this is per table, not per index. Be very careful setting this option if load is a concern on your systems.") parser.add_argument('-w', '--wait', type=float, default=0, help="Wait the given number of seconds after indexes have finished being created on a table before moving on to the next. When used with -j, this will set the pause between the batches of parallel jobs instead.") parser.add_argument('--dryrun', action="store_true", help="Show what the script will do without actually running it against the database. Highly recommend reviewing this before running. Note that if multiple indexes would get the same default name, the duplicated name will show in the dryrun (because the index doesn't exist in the catalog to check for it). When the real thing is run, the duplicated names will be handled as stated in NOTE at the end of --help.") parser.add_argument('-q', '--quiet', action="store_true", help="Turn off all output.") parser.add_argument('--nonpartman', action="store_true", help="If the partition set you are running this on is not managed by pg_partman, set this flag otherwise this script may not work. Note that the pg_partman extension is still required to be installed for this to work since it uses certain internal functions. When this is set the order that the tables are reindexed is alphabetical instead of logical.") parser.add_argument('--version', action="store_true", help="Print out the minimum version of pg_partman this script is meant to work with. The version of pg_partman installed may be greater than this.") args = parser.parse_args() def check_compatibility(conn, partman_schema): cur = conn.cursor() sql = """SELECT current_setting('server_version_num')::int""" cur.execute(sql) pg_version = int(cur.fetchone()[0]) if pg_version < 90400: print("ERROR: This script requires PostgreSQL minimum version of 9.4.0") sys.exit(2) sql = "SELECT partition_type FROM " + partman_schema + ".part_config WHERE parent_table = %s" cur.execute(sql, [args.parent]) partition_type = cur.fetchone()[0] if pg_version >= 110000 and partition_type == "native": print("This script cannot currently work with native partition sets in PG11+. Please use native index inheritance methods if possible.") cur.close() close_conn(conn) sys.exit(2) cur.close() def create_conn(): conn = psycopg2.connect(args.connection) return conn def create_index(conn, partman_schema, child_schemaname, child_tablename, child_index_list, parent, parent_index_list): cur = conn.cursor() sql = """SELECT schemaname, tablename FROM pg_catalog.pg_tables WHERE schemaname||'.'||tablename = %s""" cur.execute(sql, [parent]) result = cur.fetchone() parent_schemaname = result[0] parent_tablename = result[1] cur.close() parent_match_regex = re.compile(r" ON \"?%s\"?\.\"?%s\"? | ON \"?%s\"?" % (parent_schemaname, parent_tablename, parent_tablename)) index_match_regex = re.compile(r'(?PUSING .*)') for i in parent_index_list: # if there is already a child index that matches the parent index don't try to create it unless --recreate_all set if args.recreate_all != True: child_found = False statement = None parinddef = index_match_regex.search(i[0]) for c in child_index_list: chinddef = index_match_regex.search(c[0]) if chinddef.group('index_def') == parinddef.group('index_def'): child_found = True break if child_found: continue if i[1] == True and args.primary: index_name = child_tablename + "_" + "_".join(i[2].split(",")) sql = "SELECT " + partman_schema + ".check_name_length('" + index_name + "', p_suffix := '_pk')" cur = conn.cursor() cur.execute(sql) index_name = cur.fetchone()[0] cur.close() quoted_column_names = "\"" + "\",\"".join(i[2].split(",")) + "\"" statement = "ALTER TABLE \"" + child_schemaname + "\".\"" + child_tablename + "\" ADD CONSTRAINT \"" + index_name + "\" PRIMARY KEY (" + quoted_column_names + ")" elif i[1] == False: index_name = child_tablename if i[2] != None: index_name += "_" index_name += "_".join(i[2].split(",")) sql = "SELECT " + partman_schema + ".check_name_length('" + index_name + "', p_suffix := '_idx')" cur = conn.cursor() cur.execute(sql) index_name = cur.fetchone()[0] name_counter = 1 while True: sql = "SELECT count(*) FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = %s AND c.relname = %s" cur = conn.cursor() cur.execute(sql, [child_schemaname, index_name]) index_exists = cur.fetchone()[0] if index_exists != None and index_exists > 0: index_name = child_tablename if i[2] != None: index_name += "_" index_name += "_".join(i[2].split(",")) suffix = "_idx" + str(name_counter) sql = "SELECT " + partman_schema + ".check_name_length('" + index_name + "', p_suffix := '" + suffix + "')" cur = conn.cursor() cur.execute(sql) index_name = cur.fetchone()[0] name_counter += 1 else: break cur.close() statement = i[0] statement = statement.replace(i[3], index_name) # replace parent index name with child index name if args.concurrent: statement = statement.replace("CREATE INDEX ", "CREATE INDEX CONCURRENTLY ") statement = parent_match_regex.sub(" ON \"" + child_schemaname + "\".\"" + child_tablename + "\" ", statement) cur = conn.cursor() if statement != None: if not args.quiet: print(cur.mogrify(statement)) if not args.dryrun: cur.execute(statement) cur.close() def close_conn(conn): conn.close() def drop_index(conn, child_schemaname, child_tablename, child_index_list, parent_index_list): cur = conn.cursor() for d in child_index_list: if d[1] == True and args.primary: statement = "ALTER TABLE \"" + child_schemaname + "\".\"" + child_tablename + "\" DROP CONSTRAINT \"" + d[4] + "\"" if not args.quiet: print(cur.mogrify(statement)) if not args.dryrun: cur.execute(statement) elif d[1] == False: if args.drop_concurrent: statement = "DROP INDEX CONCURRENTLY \"" + d[2] + "\".\"" + d[3] + "\"" else: statement = "DROP INDEX \"" + d[2] + "\".\"" + d[3] + "\"" if args.recreate_all != True: pat = re.compile(r'(?PUSING .*)') # if there is a parent index that matches the child index # don't try to drop it - we'd just have to recreate it chinddef = pat.search(d[0]) parent_found = False for p in parent_index_list: parent_found = False parinddef = pat.search(p[0]) if chinddef.group('index_def') == parinddef.group('index_def'): parent_found = True break if not parent_found: if not args.quiet: print(cur.mogrify(statement)) if not args.dryrun: cur.execute(statement) else: if not args.quiet: print(cur.mogrify(statement)) if not args.dryrun: cur.execute(statement) def get_children(conn, partman_schema): cur = conn.cursor() if args.nonpartman == False: sql = "SELECT partition_schemaname, partition_tablename FROM " + partman_schema + ".show_partitions(%s)" else: sql = """ WITH parent_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE n1.nspname ||'.'|| c1.relname = %s ) SELECT n.nspname::text, c.relname::text AS partition_name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN parent_info pi ON h.inhparent = pi.oid ORDER BY 1,2""" cur.execute(sql, [args.parent]) child_list = cur.fetchall() cur.close() return child_list def get_child_index_list(conn, child_schemaname, child_tablename): cur = conn.cursor() sql = """ WITH child_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE n1.nspname = %s AND c1.relname = %s ) SELECT pg_get_indexdef(indexrelid) AS statement , i.indisprimary , n.nspname AS index_schemaname , c.relname AS index_name , t.conname FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid LEFT JOIN pg_catalog.pg_constraint t ON c.oid = t.conindid JOIN child_info ci ON i.indrelid = ci.oid """ cur.execute(sql, [child_schemaname, child_tablename]) child_index_list = cur.fetchall() cur.close() return child_index_list def get_parent_index_list(conn, parent): cur = conn.cursor() sql = """ WITH parent_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE n1.nspname||'.'||c1.relname = %s ) SELECT pg_get_indexdef(indexrelid) AS statement , i.indisprimary , ( SELECT array_to_string(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 FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid JOIN parent_info pi ON i.indrelid = pi.oid WHERE i.indisvalid ORDER BY 1""" cur.execute(sql, [parent]) parent_index_list = cur.fetchall() return parent_index_list def get_parent(conn, partman_schema): cur = conn.cursor() sql = "SELECT template_table FROM " + partman_schema + ".part_config WHERE parent_table = %s AND partition_type = 'native'" cur.execute(sql, [args.parent]) template_table = cur.fetchone() if template_table is None: return args.parent else: return template_table[0] def get_partman_schema(conn): cur = conn.cursor() sql = "SELECT nspname FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_partman' AND e.extnamespace = n.oid" cur.execute(sql) partman_schema = "\"" + cur.fetchone()[0] + "\"" cur.close() return partman_schema def print_version(): print(partman_version) sys.exit() def reindex_proc(child_schemaname, child_tablename, parent, parent_index_list, partman_schema): conn = create_conn() conn.autocommit = True # must be turned on to support CONCURRENTLY cur = conn.cursor() child_index_list = get_child_index_list(conn, child_schemaname, child_tablename) drop_index(conn, child_schemaname, child_tablename, child_index_list, parent_index_list) create_index(conn, partman_schema, child_schemaname, child_tablename, child_index_list, parent, parent_index_list) sql = "ANALYZE \"" + child_schemaname + "\".\"" + child_tablename + "\"" if not args.quiet: print(cur.mogrify(sql)) if not args.dryrun: cur.execute(sql) cur.close() close_conn(conn) if __name__ == "__main__": if args.version: print_version() if args.parent == None: print("-p/--parent option is required") sys.exit(2) if args.parent.find(".") < 0: print("ERROR: Parent table must be schema qualified") sys.exit(2) conn = create_conn() partman_schema = get_partman_schema(conn) check_compatibility(conn,partman_schema) parent = get_parent(conn, partman_schema) parent_index_list = get_parent_index_list(conn, parent) child_list = get_children(conn, partman_schema) close_conn(conn) if args.jobs == 0: for c in child_list: reindex_proc(c[0], c[1], parent, parent_index_list, partman_schema) if args.wait > 0: time.sleep(args.wait) else: child_list.reverse() while len(child_list) > 0: if not args.quiet: print("Jobs left in queue: " + str(len(child_list))) if len(child_list) < args.jobs: args.jobs = len(child_list) processlist = [] for num in range(0, args.jobs): c = child_list.pop() p = Process(target=reindex_proc, args=(c[0], c[1], parent, parent_index_list, partman_schema)) p.start() processlist.append(p) for j in processlist: j.join() if args.wait > 0: time.sleep(args.wait) pg_partman-4.6.0/bin/common/vacuum_maintenance.py000077500000000000000000000261661412761706100221500ustar00rootroot00000000000000#!/usr/bin/env python import argparse, psycopg2, sys partman_version = "2.3.2" parser = argparse.ArgumentParser(description="Script for performing additional vacuum maintenance on the child tables of a partition set in order to avoid excess vacuuming and transaction id wraparound issues. Whether a table is vacuumed or not by this script depends on whether its age(relfrozenxid) is greater than vacuum_freeze_min_age. This ensures repeated runs of this script do not re-vacuum tables that have already had all of their page tuples frozen. This script does not require pg_partman to be installed and will work on any inheritance set given a parent table, as long as the options --all, --type or --interval are NOT set.") parser.add_argument('-p', '--parent', help="Parent table of an already created partition/inheritance set. Either this option or --all is required.") parser.add_argument('--all', action="store_true", help="Run against all tables managed by pg_partman. Either this option or -p is required. Overrides -p if both are set.") parser.add_argument('-c', '--connection', default="host=", help="""Connection string for use by psycopg. Defaults to "host=" (local socket).""") parser.add_argument('-z', '--freeze', action="store_true", help="Sets the FREEZE option to the VACUUM command.") parser.add_argument('-f', '--full', action="store_true", help="Sets the FULL option to the VACUUM command. Note that --freeze is not necessary if you set this. Recommend reviewing --dryrun before running this since it will lock all tables it runs against, possibly including the parent.") parser.add_argument('-a', '--vacuum_freeze_min_age', type=int, help="By default the script obtains this value from the system catalogs. By setting this, you can override the value obtained from the database. Note this does not change the value in the database, only the value this script uses.") parser.add_argument('-t', '--type', choices=["time","id",], help="Tells the script whether the value given by --interval is either time or id based. Also, if --all is set, limits the partition sets that the script runs against to just those types. Valid values are: \"id\" and \"time\".") parser.add_argument('-i', '--interval', help="In addition to checking transaction age, only run against tables that are older than the interval given. Takes either a postgresql time interval or integer value. For time-based partitioning, uses the time that script is run as baseline. For id-based partitioning, the integer value will set that any partitions with an id value less than the current maximum id value minus the given value will be vacuumed. For example, if the current max id is 100 and the given value is 30, any partitions with id values less than 70 will be vacuumed. The current maximum id value at the time the script is run is always used.") parser.add_argument('--noparent', action="store_true", help="Normally the parent table is included in the list of tables to vacuum if its age(relfrozenxid) is higher than vacuum_freeze_min_age. Set this to force exclusion of the parent table, even if it meets that criteria.") parser.add_argument('--dryrun', action="store_true", help="Show what the script will do without actually running it against the database. Highly recommend reviewing this before running for the first time.") parser.add_argument('-q', '--quiet', action="store_true", help="Turn off all output.") parser.add_argument('--version', action="store_true", help="Print out the minimum version of pg_partman this script is meant to work with. The version of pg_partman installed may be greater than this.") parser.add_argument('--debug', action="store_true", help="Show additional debugging output") args = parser.parse_args() def close_conn(conn): conn.close() def create_conn(): conn = psycopg2.connect(args.connection) conn.autocommit = True return conn def get_parent_table_list(conn): cur = conn.cursor() partman_schema = get_partman_schema(conn) sql = "SELECT parent_table FROM " + partman_schema + ".part_config" if args.type == "id": sql += " WHERE partition_type = 'id' " elif args.type == "time": sql += " WHERE partition_type = 'time' OR partition_type = 'time-custom' " sql += " ORDER BY parent_table" cur.execute(sql) parent_table_list = cur.fetchall() cur.close() return parent_table_list def get_partition_list(conn, parent_table, vacuum_freeze_min_age): cur = conn.cursor() new_child_list = [] # Put the parent table in the list so it gets frozen as well if necessary # Do not use show_partitions() so this can work on non-pg_partman tables sql = """ WITH parent_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE n1.nspname ||'.'|| c1.relname = %s ) SELECT n.nspname::text, c.relname::text AS partition_name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN parent_info pi ON h.inhparent = pi.oid ORDER BY 1,2""" if args.debug: print(cur.mogrify(sql, [parent_table])) cur.execute(sql, [parent_table]) child_list = cur.fetchall() # only keep tables in list with age older than vacuum_freeze_min_age # this compare is much faster than --interval compare below, so reduce list size earlier for c in child_list: sql = """SELECT age(relfrozenxid) FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = %s AND c.relname = %s""" if args.debug: print(cur.mogrify(sql, [c[0], c[1]])) cur.execute(sql, [c[0], c[1]]) table_age = cur.fetchone()[0] if args.debug: print("table age ("+c[1]+"): " + str(table_age)) if table_age > vacuum_freeze_min_age: new_child_list.append(c) child_list = new_child_list # This may be used below with --interval as well, so just run it no matter what. Use fetchall() so tuple list is returned to match above list. sql = "SELECT schemaname, tablename FROM pg_catalog.pg_tables WHERE schemaname||'.'||tablename = %s" if args.debug: print(cur.mogrify(sql, [parent_table])) cur.execute(sql, [parent_table]) parent_table_split = cur.fetchall() if args.interval != None: partman_schema = get_partman_schema(conn) new_child_list = [] #reset to reuse variable if args.type == "id": sql = "SELECT control FROM " + partman_schema + ".part_config WHERE parent_table = %s" cur.execute(sql, [parent_table]) control_col = str(cur.fetchone()[0]) sql = "SELECT max(\"" + control_col + "\") FROM \"" + str(parent_table_split[0][0]) + "\".\"" + str(parent_table_split[0][1]) + "\"" cur.execute(sql) max_id = cur.fetchone()[0] upper_boundary = max_id - int(args.interval) elif args.type == "time": sql = "SELECT CURRENT_TIMESTAMP - %s::interval" # Use CURRENT_TIMESTAMP since all variables in pg_partman are timestamptz cur.execute(sql, [args.interval]) upper_boundary = cur.fetchone()[0] else: print("Invalid --type value passed into get_partition_list() function.") sys.exit(2) for c in child_list: if args.type == "id": sql = "SELECT child_start_id, child_end_id FROM " + partman_schema + ".show_partition_info(%s)" if args.debug: print(cur.mogrify(sql, [c[0] + "." + c[1]])) cur.execute(sql, [c[0] + "." + c[1]]) result = cur.fetchone() partition_min = result[0] partition_max = result[1] if args.debug: print("upper_boundary: " + str(upper_boundary) + ", partition_min: " + str(partition_min) + " partition_max: " + str(partition_max)) if partition_min < upper_boundary and partition_max < upper_boundary: new_child_list.append(c) elif args.type == "time": sql = "SELECT child_start_time, child_end_time FROM " + partman_schema + ".show_partition_info(%s)" if args.debug: print(cur.mogrify(sql, [c[0] + "." + c[1]])) cur.execute(sql, [c[0] + "." + c[1]]) result = cur.fetchone() partition_min = result[0] partition_max = result[1] if args.debug: print("upper_boundary: " + str(upper_boundary) + ", partition_min: " + str(partition_min) + " partition_max: " + str(partition_max)) if partition_min < upper_boundary and partition_max < upper_boundary: new_child_list.append(c) child_list = new_child_list if args.noparent == False: child_list += parent_table_split # add parent table to existing list cur.close() return child_list def get_partman_schema(conn): cur = conn.cursor() sql = "SELECT nspname FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_partman' AND e.extnamespace = n.oid" cur.execute(sql) partman_schema = "\"" + cur.fetchone()[0] + "\"" cur.close() return partman_schema def get_vacuum_freeze_min_age(conn): cur = conn.cursor() if args.vacuum_freeze_min_age == None: sql = "SELECT setting FROM pg_settings WHERE name = 'vacuum_freeze_min_age'" cur.execute(sql) vacuum_freeze_min_age = cur.fetchone()[0] else: vacuum_freeze_min_age = args.vacuum_freeze_min_age return int(vacuum_freeze_min_age) def print_version(): print(partman_version) sys.exit() def vacuum_table(conn, schemaname, tablename): cur = conn.cursor() sql = "VACUUM " if args.full: sql += " FULL " if args.freeze: sql += " FREEZE " sql += " \"" + schemaname + "\".\"" + tablename + "\" " if not args.quiet: print(sql) if not args.dryrun: cur.execute(sql) cur.close() if __name__ == "__main__": if args.version: print_version() if args.parent == None and args.all == False: print("Either the -p/--parent option or --all must be set.") sys.exit(2) if args.parent != None and args.parent.find(".") < 0: print("Parent table must be schema qualified") sys.exit(2) if args.interval != None and args.type == None: print("--interval argment requires setting --type argument as well") sys.exit(2) main_conn = create_conn() vacuum_freeze_min_age = get_vacuum_freeze_min_age(main_conn) if args.debug: print("vacuum_freeze_min_age: " + str(vacuum_freeze_min_age)) if args.all: parent_table_list = get_parent_table_list(main_conn) else: # put in nested list because above returns as one parent_table_list = [[args.parent]] for l in parent_table_list: partition_list = get_partition_list(main_conn, l[0], vacuum_freeze_min_age) for p in partition_list: vacuum_table(main_conn, p[0], p[1]) close_conn(main_conn) pg_partman-4.6.0/bin/pg10/000077500000000000000000000000001412761706100151755ustar00rootroot00000000000000pg_partman-4.6.0/bin/pg10/partition_data.py000077500000000000000000000223251412761706100205600ustar00rootroot00000000000000#!/usr/bin/env python import argparse, psycopg2, time, signal, sys partman_version = "2.0.0" parser = argparse.ArgumentParser(description="This script calls either partition_data_time() or partition_data_id() depending on the value given for --type. A commit is done at the end of each --interval and/or fully created partition. Returns the total number of rows moved to partitions. Automatically stops when parent is empty. See docs for examples.", epilog="NOTE: To help avoid heavy load and contention during partitioning, autovacuum is turned off for the entire partition set when this script is run. When partitioning is complete, autovacuum is set back to its default value and the parent table is vacuumed.") parser.add_argument('-p','--parent', help="Parent table of an already created partition set. (Required)") parser.add_argument('-t','--type', choices=["time","id",], help="""Type of partitioning. Valid values are "time" and "id". (Required)""") parser.add_argument('-c','--connection', default="host=", help="""Connection string for use by psycopg. Defaults to "host=" (local socket). Note that two connections are required if allowing autovacuum to be turned off.""") parser.add_argument('-i','--interval', help="Value that is passed on to the partitioning function as p_batch_interval argument. Use this to set an interval smaller than the partition interval to commit data in smaller batches. Defaults to the partition interval if not given.") parser.add_argument('-b','--batch', default=0, type=int, help="""How many times to loop through the value given for --interval. If --interval not set, will use default partition interval and make at most -b partition(s). Script commits at the end of each individual batch. (NOT passed as p_batch_count to partitioning function). If not set, all data in the parent table will be partitioned in a single run of the script.""") parser.add_argument('-w','--wait', default=0, type=float, help="Cause the script to pause for a given number of seconds between commits (batches) to reduce write load") parser.add_argument('-o', '--order', choices=["ASC", "DESC"], default="ASC", help="Allows you to specify the order that data is migrated from the parent to the children, either ascending (ASC) or descending (DESC). Default is ASC.") parser.add_argument('-l','--lockwait', default=0, type=float, help="Have a lock timeout of this many seconds on the data move. If a lock is not obtained, that batch will be tried again.") parser.add_argument('--lockwait_tries', default=10, type=int, help="Number of times to allow a lockwait to time out before giving up on the partitioning. Defaults to 10") parser.add_argument('--autovacuum_on', action="store_true", help="Turning autovacuum off requires a brief lock to ALTER the table property. Set this option to leave autovacuum on and avoid the lock attempt.") parser.add_argument('-q','--quiet', action="store_true", help="Switch setting to stop all output during and after partitioning for use in cron jobs") parser.add_argument('--version', action="store_true", help="Print out the minimum version of pg_partman this script is meant to work with. The version of pg_partman installed may be greater than this.") parser.add_argument('--debug', action="store_true", help="Show additional debugging output") args = parser.parse_args() is_autovac_off = False def close_conn(conn): conn.close() def create_conn(): conn = psycopg2.connect(args.connection) conn.autocommit = True return conn def get_partman_schema(conn): cur = conn.cursor() sql = "SELECT nspname FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_partman' AND e.extnamespace = n.oid" cur.execute(sql) partman_schema = "\"" + cur.fetchone()[0] + "\"" cur.close() return partman_schema def get_quoted_parent_table(conn): cur = conn.cursor() sql = "SELECT schemaname, tablename FROM pg_catalog.pg_tables WHERE schemaname||'.'||tablename = %s" cur.execute(sql, [args.parent]) result = cur.fetchone() if result == None: print("Given parent table ("+args.parent+") does not exist") sys.exit(2) quoted_parent_table = "\"" + result[0] + "\".\"" + result[1] + "\"" cur.close() return quoted_parent_table def partition_data(conn, partman_schema): batch_count = 0 total = 0 lockwait_count = 0 cur = conn.cursor() sql = "SELECT " + partman_schema + ".partition_data_" + args.type + "(%s" if args.interval != "": sql += ", p_batch_interval := %s" sql += ", p_lock_wait := %s" sql += ", p_order := %s" sql += ", p_analyze := FALSE)" while True: if args.interval != "": li = [args.parent, args.interval, args.lockwait, args.order] else: li = [args.parent, args.lockwait, args.order] if args.debug: print(cur.mogrify(sql, li)) cur.execute(sql, li) result = cur.fetchone() if not args.quiet: if result[0] > 0: print("Rows moved: " + str(result[0])) elif result[0] == -1: print("Unable to obtain lock, trying again") print(conn.notices[-1]) # if lock wait timeout, do not increment the counter if result[0] != -1: batch_count += 1 total += result[0] lockwait_count = 0 else: lockwait_count += 1 if lockwait_count > args.lockwait_tries: print("Quitting due to inability to get lock on next rows to be moved") break # If no rows left or given batch argument limit is reached if (result[0] == 0) or (args.batch > 0 and batch_count >= int(args.batch)): break time.sleep(args.wait) return total def print_version(): print(partman_version) sys.exit() def reset_autovacuum(partman_schema, quoted_parent_table): global is_autovac_off vacuum_conn = create_conn() cur = vacuum_conn.cursor() sql = "ALTER TABLE " + quoted_parent_table + " RESET (autovacuum_enabled, toast.autovacuum_enabled)" if not args.quiet: print("Attempting to reset autovacuum for old parent table and all child tables...") if args.debug: print(cur.mogrify(sql)) cur.execute(sql) sql = "SELECT partition_schemaname, partition_tablename FROM " + partman_schema + ".show_partitions(%s)" if args.debug: print(cur.mogrify(sql, [args.parent])) cur.execute(sql, [args.parent]) result = cur.fetchall() for r in result: sql = "ALTER TABLE \"" + r[0] + "\".\"" + r[1] + "\" RESET (autovacuum_enabled, toast.autovacuum_enabled)" if args.debug: print(cur.mogrify(sql)) cur.execute(sql) print("\t... Success!") is_autovac_off = False cur.close() close_conn(vacuum_conn) def signal_handler(signum, frame): if is_autovac_off == True: reset_autovacuum(partman_schema, quoted_parent_table) sys.exit(2) def turn_off_autovacuum(partman_schema, quoted_parent_table): global is_autovac_off vacuum_conn = create_conn() cur = vacuum_conn.cursor() sql = "ALTER TABLE " + quoted_parent_table + " SET (autovacuum_enabled = false, toast.autovacuum_enabled = false)" if not args.quiet: print("Attempting to turn off autovacuum for partition set...") if args.debug: print(cur.mogrify(sql)) cur.execute(sql) sql = "SELECT partition_schemaname, partition_tablename FROM " + partman_schema + ".show_partitions(%s)" if args.debug: print(cur.mogrify(sql, [args.parent])) cur.execute(sql, [args.parent]) result = cur.fetchall() for r in result: sql = "ALTER TABLE \"" + r[0] + "\".\"" + r[1] + "\" SET (autovacuum_enabled = false, toast.autovacuum_enabled = false)" if args.debug: print(cur.mogrify(sql)) cur.execute(sql) print("\t... Success!") is_autovac_off = True cur.close() close_conn(vacuum_conn) def vacuum_parent(conn, quoted_parent_table): cur = conn.cursor() sql = "VACUUM ANALYZE " + quoted_parent_table if args.debug: print(cur.mogrify(sql)) if not args.quiet: print("Running vacuum analyze on parent table...") cur.execute(sql) cur.close() if __name__ == "__main__": if args.version: print_version() if args.parent == None: print("-p/--parent option is required") sys.exit(2) if args.type == None: print("-t/--type option is required") sys.exit(2) is_autovac_off = False signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) conn = create_conn() partman_schema = get_partman_schema(conn) quoted_parent_table = get_quoted_parent_table(conn) try: if not args.autovacuum_on: turn_off_autovacuum(partman_schema, quoted_parent_table) total = partition_data(conn, partman_schema) if not args.quiet: print("Total rows moved: %d" % total) vacuum_parent(conn, quoted_parent_table) except: if not args.autovacuum_on and is_autovac_off == True: reset_autovacuum(partman_schema, quoted_parent_table) sys.exit(2) if not args.autovacuum_on: reset_autovacuum(partman_schema, quoted_parent_table) close_conn(conn) pg_partman-4.6.0/bin/pg10/reapply_constraints.py000077500000000000000000000161521412761706100216620ustar00rootroot00000000000000#!/usr/bin/env python import argparse, psycopg2, sys, time from multiprocessing import Process partman_version = "2.2.0" parser = argparse.ArgumentParser(description="Script for reapplying additional constraints managed by pg_partman on child tables. See docs for additional info on this special constraint management. Script runs in two distinct modes: 1) Drop all constraints 2) Apply all constraints. Typical usage would be to run the drop mode, edit the data, then run apply mode to re-create all constraints on a partition set.") parser.add_argument('-p', '--parent', help="Parent table of an already created partition set. (Required)") parser.add_argument('-c','--connection', default="host=", help="""Connection string for use by psycopg. Defaults to "host=" (local socket).""") parser.add_argument('-d', '--drop_constraints', action="store_true", help="Drop all constraints managed by pg_partman. Drops constraints on all child tables including current & future.") parser.add_argument('-a', '--add_constraints', action="store_true", help="Apply configured constraints to all child tables older than the optimize_constraint value.") parser.add_argument('-j', '--jobs', type=int, default=0, help="Use the python multiprocessing library to recreate indexes in parallel. Value for -j is number of simultaneous jobs to run. Note that this is per table, not per index. Be very careful setting this option if load is a concern on your systems.") parser.add_argument('-w', '--wait', type=float, default=0, help="Wait the given number of seconds after a table has had its constraints dropped or applied before moving on to the next. When used with -j, this will set the pause between the batches of parallel jobs instead.") parser.add_argument('--dryrun', action="store_true", help="Show what the script will do without actually running it against the database. Highly recommend reviewing this before running.") parser.add_argument('-q', '--quiet', action="store_true", help="Turn off all output.") parser.add_argument('--version', action="store_true", help="Print out the minimum version of pg_partman this script is meant to work with. The version of pg_partman installed may be greater than this.") args = parser.parse_args() def apply_proc(child_table, partman_schema): conn = create_conn() conn.autocommit = True cur = conn.cursor() sql = "SELECT " + partman_schema + ".apply_constraints(%s, %s, %s, %s, %s)" debug = False; if not args.quiet: debug = True print(cur.mogrify(sql, [args.parent, child_table, False, None, debug])) if not args.dryrun: cur.execute(sql, [args.parent, child_table, False, None, debug]) cur.close() close_conn(conn) def create_conn(): conn = psycopg2.connect(args.connection) return conn def close_conn(conn): conn.close() def drop_proc(child_table, partman_schema): conn = create_conn() conn.autocommit = True cur = conn.cursor() sql = "SELECT " + partman_schema + ".drop_constraints(%s, %s, %s)" debug = False; if not args.quiet: debug = True print(cur.mogrify(sql, [args.parent, child_table, debug])) if not args.dryrun: cur.execute(sql, [args.parent, child_table, debug]) cur.close() close_conn(conn) def get_children(conn, partman_schema): cur = conn.cursor() sql = "SELECT partition_schemaname||'.'||partition_tablename FROM " + partman_schema + ".show_partitions(%s, %s)" cur.execute(sql, [args.parent, 'ASC']) child_list = cur.fetchall() cur.close() return child_list def get_partman_schema(conn): cur = conn.cursor() sql = "SELECT nspname FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_partman' AND e.extnamespace = n.oid" cur.execute(sql) partman_schema = "\"" + cur.fetchone()[0] + "\"" cur.close() return partman_schema def get_config_values(conn, partman_schema): # [0] = premake, [1] = optimize_constraint cur = conn.cursor() sql = "SELECT premake, optimize_constraint FROM " + partman_schema + ".part_config WHERE parent_table = %s" cur.execute(sql, [args.parent]) config_values = cur.fetchone() cur.close() return config_values def get_quoted_parent_table(conn): cur = conn.cursor() sql = "SELECT schemaname, tablename FROM pg_catalog.pg_tables WHERE schemaname||'.'||tablename = %s" cur.execute(sql, [args.parent]) result = cur.fetchone() if result == None: print("Given parent table ("+args.parent+") does not exist") sys.exit(2) quoted_parent_table = "\"" + result[0] + "\".\"" + result[1] + "\"" cur.close() return quoted_parent_table def print_version(): print(partman_version) sys.exit() if __name__ == "__main__": if args.version: print_version() if args.parent == None: print("-p/--parent option is required") sys.exit(2) if args.parent.find(".") < 0: print("Parent table must be schema qualified") sys.exit(2) if args.drop_constraints and args.add_constraints: print("Can only set one or the other of --drop_constraints (-d) and --add_constraints (-a)") sys.exit(2) if (args.drop_constraints == False) and (args.add_constraints == False): print("Must set one of --drop_constraints (-d) or --add_constraints (-a)") sys.exit(2) main_conn = create_conn() partman_schema = get_partman_schema(main_conn) quoted_parent_table = get_quoted_parent_table(main_conn) child_list = get_children(main_conn, partman_schema) config_values = get_config_values(main_conn, partman_schema) premake = int(config_values[0]) optimize_constraint = int(config_values[1]) if args.add_constraints: # Remove tables from the list of child tables that shouldn't have constraints yet for x in range(optimize_constraint + premake + 1): child_list.pop() if args.jobs == 0: for c in child_list: if args.drop_constraints: drop_proc(c[0], partman_schema) if args.add_constraints: apply_proc(c[0], partman_schema) if args.wait > 0: time.sleep(args.wait) else: child_list.reverse() while len(child_list) > 0: if not args.quiet: print("Jobs left in queue: " + str(len(child_list))) if len(child_list) < args.jobs: args.jobs = len(child_list) processlist = [] for num in range(0, args.jobs): c = child_list.pop() if args.drop_constraints: p = Process(target=drop_proc, args=(c[0], partman_schema)) if args.add_constraints: p = Process(target=apply_proc, args=(c[0], partman_schema)) p.start() processlist.append(p) for j in processlist: j.join() if args.wait > 0: time.sleep(args.wait) sql = 'ANALYZE ' + quoted_parent_table main_cur = main_conn.cursor() if not args.quiet: print(main_cur.mogrify(sql)) if not args.dryrun: main_cur.execute(sql) close_conn(main_conn) pg_partman-4.6.0/bin/pg10/reapply_foreign_keys.py000077500000000000000000000135441412761706100220010ustar00rootroot00000000000000#!/usr/bin/env python import argparse, psycopg2, sys partman_version = "3.1.0" parser = argparse.ArgumentParser(description="This script will reapply the foreign keys on a parent table to all child tables in an inheritance set. Any existing foreign keys on child tables will be dropped in order to match the parent. A commit is done after each foreign key application to avoid excessive contention. Note that this script can work on any inheritance set, not just partition sets managed by pg_partman.") parser.add_argument('-p','--parent', help="Parent table of an already created partition set. (Required)") parser.add_argument('-c','--connection', default="host=", help="""Connection string for use by psycopg. Defaults to "host=" (local socket).""") parser.add_argument('-q', '--quiet', action="store_true", help="Switch setting to stop all output during and after partitioning undo.") parser.add_argument('--dryrun', action="store_true", help="Show what the script will do without actually running it against the database. Highly recommend reviewing this before running.") parser.add_argument('--nonpartman', action="store_true", help="If the partition set you are running this on is not managed by pg_partman, set this flag. Otherwise internal pg_partman functions are used and this script may not work. When this is set the order that the tables are rekeyed is alphabetical instead of logical.") parser.add_argument('--debug', action="store_true", help="Show additional debugging output") parser.add_argument('--version', action="store_true", help="Print out the minimum version of pg_partman this script is meant to work with. The version of pg_partman installed may be greater than this.") args = parser.parse_args() def apply_foreign_keys(conn, child_tables, partman_schema): if not args.quiet: print("Applying foreign keys to child tables...") cur = conn.cursor() # Get template table if exists to use its indexes sql = "SELECT template_table FROM " + partman_schema + ".part_config WHERE parent_table = %s AND partition_type = 'native'" cur.execute(sql, [args.parent]) template_table = cur.fetchone() for c in child_tables: sql = """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 JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname ||'.'|| c.relname = %s AND contype = 'f'"""; if template_table == None: if args.debug: print(cur.mogrify(sql, [args.parent])) cur.execute(sql, [args.parent]) else: if args.debug: print(cur.mogrify(sql, [template_table])) cur.execute(sql, [template_table]) parent_fkeys = cur.fetchall() for pfk in parent_fkeys: alter_sql = "ALTER TABLE \"" + c[0] + "\".\"" + c[1] + "\" ADD " + pfk[0] if not args.quiet: print(alter_sql) if not args.dryrun: cur.execute(alter_sql) def create_conn(): conn = psycopg2.connect(args.connection) conn.autocommit = True return conn def close_conn(conn): conn.close() def drop_foreign_keys(conn, child_tables): if not args.quiet: print("Dropping current foreign keys on child tables...") cur = conn.cursor() for c in child_tables: sql = """SELECT constraint_name FROM information_schema.table_constraints WHERE table_schema = %s AND table_name = %s AND constraint_type = 'FOREIGN KEY'""" if args.debug: print(cur.mogrify(sql, [ c[0], c[1] ])) cur.execute(sql, [ c[0], c[1] ]) child_fkeys = cur.fetchall() for cfk in child_fkeys: alter_sql = "ALTER TABLE \"" + c[0] + "\".\"" + c[1] + "\" DROP CONSTRAINT \"" + cfk[0] + "\"" if not args.quiet: print(alter_sql) if not args.dryrun: cur.execute(alter_sql) def get_child_tables(conn, partman_schema): if not args.quiet: print("Getting list of child tables...") cur = conn.cursor() if args.nonpartman == False: sql = "SELECT partition_schemaname, partition_tablename FROM " + partman_schema + ".show_partitions(%s)" else: sql = """ WITH parent_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE n1.nspname ||'.'|| c1.relname = %s ) SELECT n.nspname::text, c.relname::text AS partition_name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN parent_info pi ON h.inhparent = pi.oid ORDER BY 1,2""" if args.debug: print(cur.mogrify(sql, [args.parent])) cur.execute(sql, [args.parent]) result = cur.fetchall() return result def get_partman_schema(conn): cur = conn.cursor() sql = "SELECT nspname FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_partman' AND e.extnamespace = n.oid" cur.execute(sql) partman_schema = "\"" + cur.fetchone()[0] + "\"" cur.close() return partman_schema def print_version(): print(partman_version) sys.exit() if __name__ == "__main__": if args.version: print_version() if args.parent == None: print("-p/--parent option is required") sys.exit(2) conn = create_conn() partman_schema = get_partman_schema(conn) child_tables = get_child_tables(conn, partman_schema) drop_foreign_keys(conn, child_tables) apply_foreign_keys(conn, child_tables, partman_schema) if not args.quiet: print("Done!") close_conn(conn) pg_partman-4.6.0/bin/pg10/undo_partition.py000077500000000000000000000311361412761706100206140ustar00rootroot00000000000000#!/usr/bin/env python import argparse, psycopg2, signal, sys, time partman_version = "4.0.0" parser = argparse.ArgumentParser(description="This script calls either undo_partition(), undo_partition_time(), undo_partition_id(), or undo_partition_native() depending on the value given for --type. A commit is done at the end of each --interval and/or emptied partition. Returns the total number partitions and rows undone. Automatically stops when last child table is empty.", epilog="NOTE: To help avoid heavy load and contention during the undo, autovacuum is turned off for the entire partition set and all child tables when this script is run. When the undo is complete, autovacuum is set back to its default for the parent. Any child tables left behind will still have it turned off.") parser.add_argument('-p','--parent', help="Parent table of the partition set. (Required)") parser.add_argument('-t','--type', choices=["partman","native",], help="""Type of partitioning. Valid values are "partman" for old, trigger-based partition sets or "native" for natively partitioned sets. """) parser.add_argument('-c','--connection', default="host=", help="""Connection string for use by psycopg. Defaults to "host=" (local socket). Note that two connections are required if allowing autovacuum to be turned off.""") parser.add_argument('-i','--interval', help="Value that is passed on to the undo partitioning function as p_batch_interval. Use this to set an interval smaller than the partition interval to commit data in smaller batches. Defaults to the partition interval if not given. If -t value is not set, interval cannot be smaller than the partition interval and an entire partition is copied each batch.") parser.add_argument('-b','--batch', type=int, default=0, help="How many times to loop through the value given for --interval. If --interval not set, will use default partition interval and undo at most -b partition(s). Script commits at the end of each individual batch. (NOT passed as p_batch_count to undo function). If not set, all data will be moved to the parent table in a single run of the script.") parser.add_argument('--native_target', help="""If unpartitioning a natively partitioned table, use this option to set the target table to send the data to. This option is required if -t is set to "native".""") parser.add_argument('-d', '--droptable', action="store_true", help="Switch setting for whether to drop child tables when they are empty. Do not set to just uninherit.") parser.add_argument('-w','--wait', type=float, default=0, help="Cause the script to pause for a given number of seconds between commits (batches).") parser.add_argument('-l','--lockwait', default=0, type=float, help="Have a lock timeout of this many seconds on the data move. If a lock is not obtained, that batch will be tried again.") parser.add_argument('--lockwait_tries', default=10, type=int, help="Number of times to allow a lockwait to time out before giving up on the partitioning. Defaults to 10") parser.add_argument('--autovacuum_on', action="store_true", help="Turning autovacuum off requires a brief lock to ALTER the table property. Set this option to leave autovacuum on and avoid the lock attempt.") parser.add_argument('-q', '--quiet', action="store_true", help="Switch setting to stop all output during and after partitioning undo.") parser.add_argument('--version', action="store_true", help="Print out the minimum version of pg_partman this script is meant to work with. The version of pg_partman installed may be greater than this.") parser.add_argument('--debug', action="store_true", help="Show additional debugging output") args = parser.parse_args() is_autovac_off = False def close_conn(conn): conn.close() def create_conn(): conn = psycopg2.connect(args.connection) conn.autocommit = True return conn def get_partman_schema(conn): cur = conn.cursor() query = "SELECT nspname FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_partman' AND e.extnamespace = n.oid" cur.execute(query) partman_schema = "\"" + cur.fetchone()[0] + "\"" cur.close() return partman_schema def get_quoted_parent_table(conn): cur = conn.cursor() query = "SELECT schemaname, tablename FROM pg_catalog.pg_tables WHERE schemaname||'.'||tablename = %s" cur.execute(query, [args.parent]) result = cur.fetchone() if result == None: print("Given parent table ("+args.parent+") does not exist") sys.exit(2) quoted_parent_table = "\"" + result[0] + "\".\"" + result[1] + "\"" cur.close() return quoted_parent_table def print_version(): print(partman_version) sys.exit() def reset_autovacuum(partman_schema, quoted_parent_table): global is_autovac_off vacuum_conn = create_conn() cur = vacuum_conn.cursor() # Parameter doesn't exist on native parent tables if args.type == "partman": query = "ALTER TABLE " + quoted_parent_table + " RESET (autovacuum_enabled, toast.autovacuum_enabled)" if not args.quiet: print("Attempting to reset autovacuum for old parent table...") if args.debug: print(cur.mogrify(query)) cur.execute(query) # Do not use show_partitions() so that this can work on non-pg_partman partition sets # query = "SELECT partition_schemaname, partition_tablename FROM " + partman_schema + ".show_partitions(%s)" query = """ WITH parent_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE n1.nspname ||'.'|| c1.relname = %s ) SELECT n.nspname::text, c.relname::text AS partition_name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN parent_info pi ON h.inhparent = pi.oid ORDER BY 1,2""" if args.debug: print(cur.mogrify(query, [args.parent])) cur.execute(query, [args.parent]) result = cur.fetchall() for r in result: query = "ALTER TABLE \"" + r[0] + "\".\"" + r[1] + "\" RESET (autovacuum_enabled, toast.autovacuum_enabled)" if args.debug: print(cur.mogrify(query)) cur.execute(query) print("\t... Success!") is_autovac_off = False cur.close() close_conn(vacuum_conn) def signal_handler(signum, frame): if is_autovac_off == True: reset_autovacuum(conn, partman_schema, quoted_parent_table) sys.exit(2) def turn_off_autovacuum(partman_schema, quoted_parent_table): global is_autovac_off vacuum_conn = create_conn() cur = vacuum_conn.cursor() # Parameter doesn't exist on native parent tables if args.type == "partman": query = "ALTER TABLE " + quoted_parent_table + " SET (autovacuum_enabled = false, toast.autovacuum_enabled = false)" if not args.quiet: print("Attempting to turn off autovacuum for partition set...") if args.debug: print(cur.mogrify(query)) cur.execute(query) # Do not use show_partitions() so that this can work on non-pg_partman partition sets # query = "SELECT partition_schemaname, partition_tablename FROM " + partman_schema + ".show_partitions(%s)" query = """ WITH parent_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE n1.nspname ||'.'|| c1.relname = %s ) SELECT n.nspname::text, c.relname::text AS partition_name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN parent_info pi ON h.inhparent = pi.oid ORDER BY 1,2""" if args.debug: print(cur.mogrify(query, [args.parent])) cur.execute(query, [args.parent]) result = cur.fetchall() for r in result: query = "ALTER TABLE \"" + r[0] + "\".\"" + r[1] + "\" SET (autovacuum_enabled = false, toast.autovacuum_enabled = false)" if args.debug: print(cur.mogrify(query)) cur.execute(query) print("\t... Success!") is_autovac_off = True cur.close() close_conn(vacuum_conn) def undo_partition_data(conn, partman_schema): batch_count = 0 total_partitions = 0 total_rows = 0 lockwait_count = 0 cur = conn.cursor() # Actual undo sql functions do not drop by default, so fix argument value to match that default if args.droptable: keep_table = False else: keep_table = True query = "SELECT partitions_undone, rows_undone FROM " + partman_schema + ".undo_partition" query += "(p_parent_table := %s, p_keep_table := %s, p_lock_wait := %s " if args.interval != None: query += ", p_batch_interval := %s " if args.type == "native": if args.native_target == None: print("""--native_target must be set if --type is set to "native" """) sys.exit(2) query += ", p_target_table := %s " query += ")" while True: #if args.interval != None: # li = [args.parent, keep_table, args.interval, args.lockwait] #else: # li = [args.parent, keep_table, args.lockwait] li = [args.parent, keep_table, args.lockwait] if args.interval != None: li.append(args.interval) if args.type == "native": li.append(args.native_target) if args.debug: print(cur.mogrify(query, li)) cur.execute(query, li) result = cur.fetchone() if args.debug: print("Undo result: " + str(result)) conn.commit() if not args.quiet: if result[0] > 0 or result[1] > 0: print("Partitions undone this batch: " + str(result[0]) + ". Rows undone this batch: " + str(result[1])) elif result[0] == -1: print("Unable to obtain lock, trying again (" + str(lockwait_count+1) + ")") print(conn.notices[-1]) # if lock wait timeout, do not increment the counter if result[0] != -1: batch_count += 1 total_partitions += result[0] total_rows += result[1] lockwait_count = 0 else: lockwait_count += 1 if lockwait_count > args.lockwait_tries: print("Quitting due to inability to get lock on table/rows for migration to parent") break print("Total rows moved: " + str(total_rows)) # If given batch argument limit is reached if (args.batch > 0 and batch_count >= int(args.batch)): break # undo_partition functions will remove config entry once last child is dropped config_query = "SELECT parent_table FROM " + partman_schema + ".part_config WHERE parent_table = %s" if args.debug: print(cur.mogrify(config_query, [args.parent])) cur.execute(config_query, [args.parent]) result = cur.fetchone() if result == None: break if args.wait > 0: time.sleep(args.wait) return total_partitions def vacuum_parent(conn, quoted_parent_table): cur = conn.cursor() query = "VACUUM ANALYZE " + quoted_parent_table if args.debug: print(cur.mogrify(query)) if not args.quiet: print("Running vacuum analyze on parent table...") cur.execute(query) cur.close() if __name__ == "__main__": if args.version: print_version() if args.parent == None: print("-p/--parent option is required") sys.exit(2) if args.parent.find(".") < 0: print("ERROR: Parent table must be schema qualified") sys.exit(2) if args.type == "native" and args.native_target == None: print("""--native_target must be set if --type is set to "native" """) sys.exit(2) is_autovac_off = False signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) conn = create_conn() partman_schema = get_partman_schema(conn) quoted_parent_table = get_quoted_parent_table(conn) try: if not args.autovacuum_on: turn_off_autovacuum(partman_schema, quoted_parent_table) total = undo_partition_data(conn, partman_schema) if not args.quiet: print("Total partitions undone: %d" % total) vacuum_parent(conn, quoted_parent_table) except: if not args.autovacuum_on and is_autovac_off == True: reset_autovacuum(partman_schema, quoted_parent_table) raise if not args.autovacuum_on: reset_autovacuum(partman_schema, quoted_parent_table) close_conn(conn) pg_partman-4.6.0/doc/000077500000000000000000000000001412761706100144235ustar00rootroot00000000000000pg_partman-4.6.0/doc/migrate_to_native.md000066400000000000000000000524131412761706100204520ustar00rootroot00000000000000# Migrating From Trigger-based Partitioning To Native This document will cover how to migrate a partition set using the old method of triggers/inheritance/constraints to a partition set using the native features found in PostgreSQL 11+. Note these instructions do not cover migrating to PG10 since some key features that make this migration easier were not yet implemented. It is highly recommended to move to PG11 or higher if you want to migrate existing partition sets. Also note that while this migration document is specific to pg_partman, most of the process can be adapted to any partition sets that you may have made for yourself. If anyone would like to contribute to automating this migration process using a stored procedure, it would be appreciated. It is planned for development in the future, but there is no ETA at this time. For pg_partman users, this requires at least version 4.3.0. The show_partition_info() function was fixed in this version to give the proper boundary values needed for easier migration. We will be using the partition set generated by the trigger-based `test/test-time-daily.sql` pgtap test. This is how our partition set currently looks before migration: ``` keith@keith=# \d+ partman_test.time_taptest_table Table "partman_test.time_taptest_table" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+--------------------------+-----------+----------+---------+----------+--------------+------------- col1 | integer | | not null | | plain | | col2 | text | | | | extended | | col3 | timestamp with time zone | | not null | now() | plain | | Indexes: "time_taptest_table_pkey" PRIMARY KEY, btree (col1) Triggers: time_taptest_table_part_trig BEFORE INSERT ON partman_test.time_taptest_table FOR EACH ROW EXECUTE FUNCTION partman_test.time_taptest_table_part_trig_func() Child tables: partman_test.time_taptest_table_p2020_01_27, partman_test.time_taptest_table_p2020_01_28, partman_test.time_taptest_table_p2020_01_29, partman_test.time_taptest_table_p2020_01_30, partman_test.time_taptest_table_p2020_01_31, partman_test.time_taptest_table_p2020_02_01, partman_test.time_taptest_table_p2020_02_02, partman_test.time_taptest_table_p2020_02_03, partman_test.time_taptest_table_p2020_02_04, partman_test.time_taptest_table_p2020_02_05, partman_test.time_taptest_table_p2020_02_06, partman_test.time_taptest_table_p2020_02_07, partman_test.time_taptest_table_p2020_02_08, partman_test.time_taptest_table_p2020_02_09, partman_test.time_taptest_table_p2020_02_10, partman_test.time_taptest_table_p2020_02_11, partman_test.time_taptest_table_p2020_02_12 Access method: heap ``` If your partition set is managed by pg_partman, it's best to turn off automatic maintenance during this process to avoid any issues. If you are manually calling maintenance directly on this parent set via some other scheduler (cron, etc), ensure that it is turned off there as well. ``` UPDATE partman.part_config SET automatic_maintenance = 'off' WHERE parent_table = 'partman_test.time_taptest_table'; ``` If this table is also sub-partitioned, turn maintenance off there as well for all sub-parents. For sub-partitioning you should be able to follow all the same processes here, but you will have to work from the lowest level upward and perform the migration on each sub-parent all the way to the top-level parent. ``` UPDATE partman.part_config_sub SET sub_automatic_maintenance = 'off' WHERE sub_parent = 'partman_test.time_taptest_table'; UPDATE partman.part_config_sub SET sub_automatic_maintenance = 'off' WHERE sub_parent = 'partman_test.time_taptest_table_p2019_12_08'; [...] ``` Next, we need to create a new parent table using native partitioning since you cannot currently convert an existing table into a native partition parent. Note in this case our original table had a primary key on `col1`. Since `col1` is not part of the partition key, native partitioning does not allow us to declare it as a primary key on the top level table. If you still need this as a primary key, pg_partman provides a template table you can set this on, but it will still not enforce uniqueness across the entire partition set, only on a per-child basis similar to how it worked before native. Please see the `Child Table Property Inheritance` section of `docs/pg_partman.md` for which properties can be set on the native parent and which must be managed via the template table since this varies between PG versions. These include things like indexes, foreign keys and other table properties. ``` CREATE TABLE partman_test.time_taptest_table_native (col1 int, col2 text default 'stuff', col3 timestamptz NOT NULL DEFAULT now()) PARTITION BY RANGE (col3); CREATE INDEX ON partman_test.time_taptest_table_native (col3); ``` Next check what the ownership and privileges on your original table were and ensure they exist on the new parent table. This will ensure all access to the table works the same after the migration. By default with native partitioning, privileges are no longer granted on child tables to provide direct access to them. If you'd like to keep that behavior, set the `inherit_privileges` column in `part_config` (and part_config_sub if needed) to true. ``` \dt partman_test.time_taptest_table List of relations Schema | Name | Type | Owner --------------+--------------------+-------+--------------- partman_test | time_taptest_table | table | partman_owner (1 row) \dp+ partman_test.time_taptest_table Access privileges Schema | Name | Type | Access privileges | Column privileges | Policies --------------+--------------------+-------+-------------------------------------+-------------------+---------- partman_test | time_taptest_table | table | partman_owner=arwdDxt/partman_owner+| | | | | partman_basic=arwd/partman_owner +| | | | | testing=r/partman_owner | | (1 row) ``` ``` ALTER TABLE partman_test.time_taptest_table_native OWNER TO partman_owner; GRANT SELECT, INSERT, UPDATE, DELETE ON partman_test.time_taptest_table_native TO partman_basic; GRANT SELECT ON partman_test.time_taptest_table_native TO testing; ``` It is best to halt all activity on the original table during the migration process to avoid any issues. This can be done by either revoking all permissions to the table temporarily or by taking out an exclusive lock on the parent table and running all of these steps in a single transaction. The transactional method is highly recommended for the simple fact that if you run into any issues before you've completed the migration process, you can simply rollback and return to the state your database was in before the migration started. ``` BEGIN; LOCK TABLE partman_test.time_taptest_table IN ACCESS EXCLUSIVE MODE NOWAIT; ``` If this is a subpartitioned table, the lock on the top-level parent should lock out access on all child tables as long as you don't use the ONLY clause. The first major step in this migration process is now to uninherit all the child tables from the old parent. You can use a query like the one below to generate the ALTER TABLE statements to uninherit all child tables from the given parent table. It's best to use generated SQL like this to avoid typos, especially with very large partition sets: ``` SELECT 'ALTER TABLE '||inhrelid::regclass||' NO INHERIT '||inhparent::regclass||';' FROM pg_inherits WHERE inhparent::regclass = 'partman_test.time_taptest_table'::regclass; ?column? ----------------------------------------------------------------------------------------------------- ALTER TABLE partman_test.time_taptest_table_p2019_12_08 NO INHERIT partman_test.time_taptest_table; ALTER TABLE partman_test.time_taptest_table_p2019_12_09 NO INHERIT partman_test.time_taptest_table; ALTER TABLE partman_test.time_taptest_table_p2019_12_10 NO INHERIT partman_test.time_taptest_table; ALTER TABLE partman_test.time_taptest_table_p2019_12_11 NO INHERIT partman_test.time_taptest_table; ALTER TABLE partman_test.time_taptest_table_p2019_12_12 NO INHERIT partman_test.time_taptest_table; ALTER TABLE partman_test.time_taptest_table_p2019_12_13 NO INHERIT partman_test.time_taptest_table; ALTER TABLE partman_test.time_taptest_table_p2019_12_14 NO INHERIT partman_test.time_taptest_table; ALTER TABLE partman_test.time_taptest_table_p2019_12_15 NO INHERIT partman_test.time_taptest_table; ALTER TABLE partman_test.time_taptest_table_p2019_12_16 NO INHERIT partman_test.time_taptest_table; ALTER TABLE partman_test.time_taptest_table_p2019_12_17 NO INHERIT partman_test.time_taptest_table; ALTER TABLE partman_test.time_taptest_table_p2019_12_18 NO INHERIT partman_test.time_taptest_table; ALTER TABLE partman_test.time_taptest_table_p2019_12_19 NO INHERIT partman_test.time_taptest_table; ALTER TABLE partman_test.time_taptest_table_p2019_12_20 NO INHERIT partman_test.time_taptest_table; ALTER TABLE partman_test.time_taptest_table_p2019_12_21 NO INHERIT partman_test.time_taptest_table; ALTER TABLE partman_test.time_taptest_table_p2019_12_22 NO INHERIT partman_test.time_taptest_table; ALTER TABLE partman_test.time_taptest_table_p2019_12_23 NO INHERIT partman_test.time_taptest_table; ALTER TABLE partman_test.time_taptest_table_p2019_12_24 NO INHERIT partman_test.time_taptest_table; ``` DO NOT RUN THESE STATEMENTS YET. The following query will not work if the child tables are no longer part of the inheritance set. For any partition sets, even those not managed by pg_partman, the next step is that you need to figure out the boundary values of your existing child tables and feed those to the ATTACH PARTITION command used in native partitioning. Since pg_partman uses set naming patterns for all the partition types it manages, there is a built-in function (`show_partition_info()`) that can return the boundary values based on the child table's name. For non-pg_partman partition sets you will have to use some other method to figure out these child boundaries. Again, we can use some sql to generate statements to re-attach the children to the new parent. As mentioned before, show_partition_info() makes this easy for pg_partman managed partition sets: ``` SELECT ( SELECT 'ALTER TABLE partman_test.time_taptest_table_native ATTACH PARTITION '||x.child_tablename||' FOR VALUES FROM ('||quote_literal(y.child_start_time)||') TO ('||quote_literal(y.child_end_time)||');' FROM partman.show_partition_info(x.child_tablename, p_parent_table := 'partman_test.time_taptest_table') y ) FROM (SELECT inhrelid::regclass::text AS child_tablename FROM pg_inherits WHERE inhparent::regclass = 'partman_test.time_taptest_table'::regclass) x; ALTER TABLE partman_test.time_taptest_table_native ATTACH PARTITION partman_test.time_taptest_table_p2020_01_27 FOR VALUES FROM ('2020-01-27 00:00:00-05') TO ('2020-01-28 00:00:00-05'); ALTER TABLE partman_test.time_taptest_table_native ATTACH PARTITION partman_test.time_taptest_table_p2020_01_28 FOR VALUES FROM ('2020-01-28 00:00:00-05') TO ('2020-01-29 00:00:00-05'); ALTER TABLE partman_test.time_taptest_table_native ATTACH PARTITION partman_test.time_taptest_table_p2020_01_29 FOR VALUES FROM ('2020-01-29 00:00:00-05') TO ('2020-01-30 00:00:00-05'); ALTER TABLE partman_test.time_taptest_table_native ATTACH PARTITION partman_test.time_taptest_table_p2020_01_30 FOR VALUES FROM ('2020-01-30 00:00:00-05') TO ('2020-01-31 00:00:00-05'); ALTER TABLE partman_test.time_taptest_table_native ATTACH PARTITION partman_test.time_taptest_table_p2020_01_31 FOR VALUES FROM ('2020-01-31 00:00:00-05') TO ('2020-02-01 00:00:00-05'); ALTER TABLE partman_test.time_taptest_table_native ATTACH PARTITION partman_test.time_taptest_table_p2020_02_01 FOR VALUES FROM ('2020-02-01 00:00:00-05') TO ('2020-02-02 00:00:00-05'); ALTER TABLE partman_test.time_taptest_table_native ATTACH PARTITION partman_test.time_taptest_table_p2020_02_02 FOR VALUES FROM ('2020-02-02 00:00:00-05') TO ('2020-02-03 00:00:00-05'); ALTER TABLE partman_test.time_taptest_table_native ATTACH PARTITION partman_test.time_taptest_table_p2020_02_03 FOR VALUES FROM ('2020-02-03 00:00:00-05') TO ('2020-02-04 00:00:00-05'); ALTER TABLE partman_test.time_taptest_table_native ATTACH PARTITION partman_test.time_taptest_table_p2020_02_04 FOR VALUES FROM ('2020-02-04 00:00:00-05') TO ('2020-02-05 00:00:00-05'); ALTER TABLE partman_test.time_taptest_table_native ATTACH PARTITION partman_test.time_taptest_table_p2020_02_05 FOR VALUES FROM ('2020-02-05 00:00:00-05') TO ('2020-02-06 00:00:00-05'); ALTER TABLE partman_test.time_taptest_table_native ATTACH PARTITION partman_test.time_taptest_table_p2020_02_06 FOR VALUES FROM ('2020-02-06 00:00:00-05') TO ('2020-02-07 00:00:00-05'); ALTER TABLE partman_test.time_taptest_table_native ATTACH PARTITION partman_test.time_taptest_table_p2020_02_07 FOR VALUES FROM ('2020-02-07 00:00:00-05') TO ('2020-02-08 00:00:00-05'); ALTER TABLE partman_test.time_taptest_table_native ATTACH PARTITION partman_test.time_taptest_table_p2020_02_08 FOR VALUES FROM ('2020-02-08 00:00:00-05') TO ('2020-02-09 00:00:00-05'); ALTER TABLE partman_test.time_taptest_table_native ATTACH PARTITION partman_test.time_taptest_table_p2020_02_09 FOR VALUES FROM ('2020-02-09 00:00:00-05') TO ('2020-02-10 00:00:00-05'); ALTER TABLE partman_test.time_taptest_table_native ATTACH PARTITION partman_test.time_taptest_table_p2020_02_10 FOR VALUES FROM ('2020-02-10 00:00:00-05') TO ('2020-02-11 00:00:00-05'); ALTER TABLE partman_test.time_taptest_table_native ATTACH PARTITION partman_test.time_taptest_table_p2020_02_11 FOR VALUES FROM ('2020-02-11 00:00:00-05') TO ('2020-02-12 00:00:00-05'); ALTER TABLE partman_test.time_taptest_table_native ATTACH PARTITION partman_test.time_taptest_table_p2020_02_12 FOR VALUES FROM ('2020-02-12 00:00:00-05') TO ('2020-02-13 00:00:00-05'); ``` We can now run these two sets of ALTER TABLE statements to first uninherit them from the old trigger-based parent and attach them to the new native parent. After doing so, the old trigger-based parent should have no longer have children: ``` keith@keith=# \d+ partman_test.time_taptest_table Table "partman_test.time_taptest_table" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+--------------------------+-----------+----------+---------+----------+--------------+------------- col1 | integer | | not null | | plain | | col2 | text | | | | extended | | col3 | timestamp with time zone | | not null | now() | plain | | Indexes: "time_taptest_table_pkey" PRIMARY KEY, btree (col1) Triggers: time_taptest_table_part_trig BEFORE INSERT ON partman_test.time_taptest_table FOR EACH ROW EXECUTE FUNCTION partman_test.time_taptest_table_part_trig_func() Access method: heap ``` And our new native parent should have now adopted all its new children: ``` keith@keith=# \d+ partman_test.time_taptest_table_native Partitioned table "partman_test.time_taptest_table_native" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+--------------------------+-----------+----------+---------------+----------+--------------+------------- col1 | integer | | | | plain | | col2 | text | | | 'stuff'::text | extended | | col3 | timestamp with time zone | | not null | now() | plain | | Partition key: RANGE (col3) Indexes: "time_taptest_table_native_col3_idx" btree (col3) Partitions: partman_test.time_taptest_table_p2020_01_27 FOR VALUES FROM ('2020-01-27 00:00:00-05') TO ('2020-01-28 00:00:00-05'), partman_test.time_taptest_table_p2020_01_28 FOR VALUES FROM ('2020-01-28 00:00:00-05') TO ('2020-01-29 00:00:00-05'), partman_test.time_taptest_table_p2020_01_29 FOR VALUES FROM ('2020-01-29 00:00:00-05') TO ('2020-01-30 00:00:00-05'), partman_test.time_taptest_table_p2020_01_30 FOR VALUES FROM ('2020-01-30 00:00:00-05') TO ('2020-01-31 00:00:00-05'), partman_test.time_taptest_table_p2020_01_31 FOR VALUES FROM ('2020-01-31 00:00:00-05') TO ('2020-02-01 00:00:00-05'), partman_test.time_taptest_table_p2020_02_01 FOR VALUES FROM ('2020-02-01 00:00:00-05') TO ('2020-02-02 00:00:00-05'), partman_test.time_taptest_table_p2020_02_02 FOR VALUES FROM ('2020-02-02 00:00:00-05') TO ('2020-02-03 00:00:00-05'), partman_test.time_taptest_table_p2020_02_03 FOR VALUES FROM ('2020-02-03 00:00:00-05') TO ('2020-02-04 00:00:00-05'), partman_test.time_taptest_table_p2020_02_04 FOR VALUES FROM ('2020-02-04 00:00:00-05') TO ('2020-02-05 00:00:00-05'), partman_test.time_taptest_table_p2020_02_05 FOR VALUES FROM ('2020-02-05 00:00:00-05') TO ('2020-02-06 00:00:00-05'), partman_test.time_taptest_table_p2020_02_06 FOR VALUES FROM ('2020-02-06 00:00:00-05') TO ('2020-02-07 00:00:00-05'), partman_test.time_taptest_table_p2020_02_07 FOR VALUES FROM ('2020-02-07 00:00:00-05') TO ('2020-02-08 00:00:00-05'), partman_test.time_taptest_table_p2020_02_08 FOR VALUES FROM ('2020-02-08 00:00:00-05') TO ('2020-02-09 00:00:00-05'), partman_test.time_taptest_table_p2020_02_09 FOR VALUES FROM ('2020-02-09 00:00:00-05') TO ('2020-02-10 00:00:00-05'), partman_test.time_taptest_table_p2020_02_10 FOR VALUES FROM ('2020-02-10 00:00:00-05') TO ('2020-02-11 00:00:00-05'), partman_test.time_taptest_table_p2020_02_11 FOR VALUES FROM ('2020-02-11 00:00:00-05') TO ('2020-02-12 00:00:00-05'), partman_test.time_taptest_table_p2020_02_12 FOR VALUES FROM ('2020-02-12 00:00:00-05') TO ('2020-02-13 00:00:00-05') ``` Next is to swap the names of your old trigger-based parent and the new native parent. ``` ALTER TABLE partman_test.time_taptest_table RENAME TO time_taptest_table_old; ALTER TABLE partman_test.time_taptest_table_native RENAME TO time_taptest_table; ``` PG11+ supports the feature of a default partition to catch any data that doesn't have a matching child. pg_partman does create this default partition for you when it sets up new partition sets, but since we're migrating we'll have to create one manually if desired. If your table names are particularly long, ensure that adding the `_default` suffix doesn't get truncated unexpectedly. The suffix isn't required for functionality, but provides good context for what the table is for, so it's better to shorten the table name itself to fit the suffix. ``` CREATE TABLE partman_test.time_taptest_table_default (LIKE partman_test.time_taptest_table INCLUDING ALL); ALTER TABLE partman_test.time_taptest_table ATTACH PARTITION partman_test.time_taptest_table_default DEFAULT; ``` For pg_partman, a template table is used to handle certain inheritance properties, so this table will need to be created. This table can be located in any schema and named whatever you wish, but by default it is created in the same schema that pg_partman was installed to and just prepends `template_` onto the name of the current table including its schema. Also it's good to set the owner to be the same as the parent table owner. Again, if your table names are particularly long, make sure you account for any name truncation if it occurs. As mentioned above, view the `Child Table Property Inheritance` section of the documentation for what properties are managed via this template table depending on your version of PostgreSQL. ``` CREATE TABLE partman.partman_test_time_taptest_table (LIKE partman_test.time_taptest_table); ALTER TABLE partman.partman_test_time_taptest_table OWNER TO partman_owner; ``` Because we had a primary key on our original table, and we can't set that on the parent table, this template table can manage that for us. ``` ALTER TABLE partman.partman_test_time_taptest_table ADD PRIMARY KEY (col1); ``` And lastly, for pg_partman, you will need to update the `part_config` (and `part_config_sub` if subpartitioned) tables to account for now being natively partitioned. The value for `template_table` must match the name of the table that was created above. ``` UPDATE partman.part_config SET partition_type = 'native', template_table = 'partman.partman_test_time_taptest_table' WHERE parent_table = 'partman_test.time_taptest_table'; ``` If you've run this process inside a transaction, be sure to commit your work now: ``` COMMIT; ``` This should complete the migration process. If you'd like general calls to `run_maintenance()` with pg_partman to work with this partition set again, be sure to update the `part_config` table to set `automatic_maintenance` back to `on`. pg_partman-4.6.0/doc/migration_to_partman.md000066400000000000000000000421551412761706100211710ustar00rootroot00000000000000Migrating An Existing Partition Set to PG Partition Manager =========================================================== This document is an aid for migrating an existing partitioned table set to using pg_partman. Please note that at this time, this guide is only for non-native, trigger-baed partitioning. Documentation for native partitioning is in the works, but it will be mostly focused on PostgreSQL 11 since 10 was very limited in its partitioning support. For now, the easiest way to migrate to a natively partitioned table is to create a brand new table and copy/move the data. pg_partman does not support having child table names that do not match its naming convention. I've tried to implement that several times, but it's too difficult to support in a general manner and just ends up hindering development or breaking a feature. Your situation likely isn't exactly like the ones below, but this should at least provide guidance on what is required. As always, if you can first test this migration on a development system, it is highly recommended. The full data set is not needed to test this and just the schema with a smaller set of data in each child should be sufficient enough to make sure it works properly. The following are the example tables I will be using: Table "tracking.hits" Column | Type | Modifiers | Storage | Stats target | Description --------+-----------------------------+-----------+---------+--------------+------------- id | integer | not null | plain | | start | timestamp without time zone | not null | plain | | Child tables: tracking.hits20160103, tracking.hits20160110, tracking.hits20160117 insert into tracking.hits20160103 values (1, generate_series('2016-01-03 01:00:00'::timestamptz, '2016-01-09 23:00:00'::timestamptz, '1 hour'::interval)); insert into tracking.hits20160110 values (1, generate_series('2016-01-10 01:00:00'::timestamptz, '2016-01-16 23:00:00'::timestamptz, '1 hour'::interval)); insert into tracking.hits20160117 values (1, generate_series('2016-01-17 01:00:00'::timestamptz, '2016-01-23 23:00:00'::timestamptz, '1 hour'::interval)); Table "tracking.hits" Column | Type | Modifiers | Storage | Stats target | Description --------+-----------------------------+-----------+---------+--------------+------------- id | integer | not null | plain | | start | timestamp without time zone | not null | plain | | Child tables: tracking.hits1000, tracking.hits2000, tracking.hits3000 insert into tracking.hits1000 values (generate_series(1000,1999), now()); insert into tracking.hits2000 values (generate_series(2000,2999), now()); insert into tracking.hits3000 values (generate_series(3000,3999), now()); Table "tracking.hits" Column | Type | Modifiers | Storage | Stats target | Description --------+-----------------------------+-----------+---------+--------------+------------- id | integer | not null | plain | | start | timestamp without time zone | not null | plain | | Child tables: tracking.hits_aa, tracking.hits_bb, tracking.hits_cc Data depends on partitioning type. See below. Step 1 ------ Disable calls to the run_maintenance() If you have any partitions currently maintained by pg_partman, you may be calling this already for them. They should be fine for the period of time this conversion is being done. This is to avoid any issues with only a partial configuration existing during conversion. If you are using the background worker, commenting out the "pg_partman_bgw.dbname" parameter in postgresql.conf and then reloading (SELECT pg_reload_conf();) should be sufficient to stop it from running. If you're running pg_partman on several databases in the cluster and you don't want to stop them all, you can also just remove the one you're doing the migration on from that same parameter. Step 2 ------ Stop all writes to the partition set being migrated if possible. If you cannot do this for the period of time the conversion will take, all of these following steps must be done in a single transaction to avoid write errors due table names changing and old & new triggers existing at the same time. Step 3 ------ Rename the existing partitions to new naming convention. pg_partman uses a static pattern of suffixes for all partitions, both time & serial. All suffixes start with the string "_p". Even the custom time intervals use the same patterns. All of them are listed here for your reference. _pYYYY - Yearly (and any custom time greater than this) _pYYYY"q"Q - Quarterly (double quotes required to add another string value inside a date/time format string) _pYYYY_MM - Monthly (and all custom time intervals between yearly and monthly) _pIYYY"w"IW - Weekly (ISO Year and ISO Week) _pYYYY_MM_DD - Daily (and all custom time intervals between monthly and daily) _pYYYY_MM_DD_HH24MI - Hourly, Half-Hourly, Quarter-Hourly (and all custom time intervals between daily and hourly) _pYYYY_MM_DD_HH24MISS - Only used with custom time if interval is less than 1 minute (cannot be less than 1 second) _p##### - Serial/ID partition has a suffix that is the value of the lowest possible entry in that table (Ex: _p10, _p20000, etc) Step 3a ------- For converting either time or serial based partition sets, if you have the lower boundary value as part of the partition name already, then it's simply a matter of doing a rename with some substring formatting since that's the pattern that pg_partman itself uses. Say your table was partitioned weekly and your original format just had the first day of the week (sunday) for the partition name (as in the example above). You can see below that we had 3 partitions with the old naming pattern of "YYYYMMDD". Looking at the list above, you can see the new weekly pattern that pg_partman uses. A note about quarterly partitioning... the to_timestamp() function does not recognize the "Q" format string like to_char() does. Why? You can look in the postgres source and see the reasoning, but it makes no sense to me. I handle this inside the show_partition_info() function if you need a way to do this. So a query like the following which first extracts the original name then reformats the suffix would work. It doesn't actually do the renaming, it just generates all the ALTER TABLE statements for you for all the child tables in the set. If all of them don't quite have the same pattern for some reason, you can easily just re-run this, editing things as needed, and filter the resulting list of ALTER TABLE statements accordingly. select 'ALTER TABLE '||n.nspname||'.'||c.relname||' RENAME TO '||substring(c.relname from 1 for 4)||'_p'||to_char(to_timestamp(substring(c.relname from 5), 'YYYYMMDD'), 'IYYY')||'w'||to_char(to_timestamp(substring(c.relname from 5), 'YYYYMMDD'), 'IW')||';' from pg_inherits h join pg_class c on h.inhrelid = c.oid join pg_namespace n on c.relnamespace = n.oid where h.inhparent::regclass = 'tracking.hits'::regclass order by c.relname; Which outputs: ALTER TABLE tracking.hits20160103 RENAME TO hits_p2015w53; ALTER TABLE tracking.hits20160110 RENAME TO hits_p2016w01; ALTER TABLE tracking.hits20160117 RENAME TO hits_p2016w02; Running that should rename your tables to look like this now: tablename | schemaname ---------------+------------ hits | tracking hits_p2015w53 | tracking hits_p2016w01 | tracking hits_p2016w02 | tracking If you're migrating a serial/id based partition set, and also have the naming convention with the lowest possible value, you'd do something very similar. Everything would be the same as the time-series one above except the renaming would be slightly different. Since the number value can vary, if you didn't have that as the final suffix of the partition name, that could make pattern matching for a rename more challanging. Using my second example table above, it would be something like this. select 'ALTER TABLE '||n.nspname||'.'||c.relname||' RENAME TO '||substring(c.relname from 1 for 4)||'_p'||substring(c.relname from 5)||';' from pg_inherits h join pg_class c on h.inhrelid = c.oid join pg_namespace n on c.relnamespace = n.oid where h.inhparent::regclass = 'tracking.hits'::regclass order by c.relname; ALTER TABLE tracking.hits1000 RENAME TO hits_p1000; ALTER TABLE tracking.hits2000 RENAME TO hits_p2000; ALTER TABLE tracking.hits3000 RENAME TO hits_p3000; tablename | schemaname -----------+------------ hits | tracking hits1000 | tracking hits2000 | tracking hits3000 | tracking Step 3b ------- If your partitioned sets are named in a manner that relates differently to the data contained, or just doesn't relate at all, you'll instead have to do the renaming based off the lowest value in the control column instead. I'll be using the example above with the _aa, _bb, & _cc suffixes. If this is partitioned by time, assume the following data exists in the child tables: insert into tracking.hits_aa values (1, generate_series('2016-01-03 01:00:00'::timestamptz, '2016-01-09 23:00:00'::timestamptz, '1 hour'::interval)); insert into tracking.hits_bb values (2, generate_series('2016-01-10 01:00:00'::timestamptz, '2016-01-16 23:00:00'::timestamptz, '1 hour'::interval)); insert into tracking.hits_cc values (3, generate_series('2016-01-17 01:00:00'::timestamptz, '2016-01-23 23:00:00'::timestamptz, '1 hour'::interval)); This next step takes advantage of anonymous code blocks. It's basically writing pl/pgsql function code without creating an actual function. Just run this block of code, adjusting values as needed, right inside a psql session. DO $rename$ DECLARE v_min_val timestamp; v_row record; v_sql text; BEGIN -- Adjust your parent table name in the for loop query FOR v_row IN SELECT n.nspname AS child_schema, c.relname AS child_table FROM pg_inherits h JOIN pg_class c ON h.inhrelid = c.oid JOIN pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent::regclass = 'tracking.hits'::regclass ORDER BY c.relname LOOP -- Substitute your control column's name here in the min() function v_sql := format('SELECT min(start) FROM %I.%I', v_row.child_schema, v_row.child_table); EXECUTE v_sql INTO v_min_val; -- Adjust the date_trunc here to account for whatever your partitioning interval is. v_min_val := date_trunc('week', v_min_val); -- Build the sql statement to rename the child table -- Use the appropriate date/time string from the list above for your interval v_sql := format('ALTER TABLE %I.%I RENAME TO %I' , v_row.child_schema , v_row.child_table , substring(v_row.child_table from 1 for 4)||'_p'||to_char(v_min_val, 'IYYY"w"IW')); -- I just have it outputing the ALTER statement for review. If you'd like this code to actually run it, uncomment the EXECUTE below. RAISE NOTICE '%', v_sql; -- EXECUTE v_sql; END LOOP; END $rename$; This will output something like this: NOTICE: ALTER TABLE tracking.hits_aa RENAME TO hits_p2015w53 NOTICE: ALTER TABLE tracking.hits_bb RENAME TO hits_p2016w01 NOTICE: ALTER TABLE tracking.hits_cc RENAME TO hits_p2016w02 I'd recommend running it at least once with the final EXECUTE commented out to review what it generates. If it looks good, you can uncomment the EXECUTE and rename your tables! If you've got a serial/id partition set, calculating the proper suffix value can be done by taking advantage of modulus arithmetic. Assume the following values in the same example partition set used before: insert into tracking.hits_aa values (generate_series(1100,1294), now()); insert into tracking.hits_bb values (generate_series(2400,2991), now()); insert into tracking.hits_cc values (generate_series(3602,3843), now()); We'll be partitioning by 1000 again and you can see none of the minimum values are that even. DO $rename$ DECLARE v_min_val bigint; v_row record; v_sql text; BEGIN -- Adjust your parent table name in the for loop query FOR v_row IN SELECT n.nspname AS child_schema, c.relname AS child_table FROM pg_inherits h JOIN pg_class c ON h.inhrelid = c.oid JOIN pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent::regclass = 'tracking.hits'::regclass ORDER BY c.relname LOOP -- Substitute your control column's name here in the min() function v_sql := format('SELECT min(id) FROM %I.%I', v_row.child_schema, v_row.child_table); EXECUTE v_sql INTO v_min_val; -- Adjust the numerical value after the % to account for whatever your partitioning interval is. v_min_val := v_min_val - (v_min_val % 1000); -- Build the sql statement to rename the child table v_sql := format('ALTER TABLE %I.%I RENAME TO %I' , v_row.child_schema , v_row.child_table , substring(v_row.child_table from 1 for 4)||'_p'||v_min_val::text); -- I just have it outputing the ALTER statement for review. If you'd like this code to actually run it, uncomment the EXECUTE below. RAISE NOTICE '%', v_sql; -- EXECUTE v_sql; END LOOP; END $rename$; You can see this makes nice even partition names: NOTICE: ALTER TABLE tracking.hits_aa RENAME TO hits_p1000 NOTICE: ALTER TABLE tracking.hits_bb RENAME TO hits_p2000 NOTICE: ALTER TABLE tracking.hits_cc RENAME TO hits_p3000 Step 4 ------ Actual setup and trigger swap I mentioned at the beginning that if you had ongoing writes, pretty much everything from Step 2 and on had to be done in a single transaction. Even if you don't have to worry about writes, I'd highly recommend doing steps 4a and 4b in a single transaction just to avoid weird trigger conflicts. Step 4a ------- Drop your current partitioning trigger after starting a transaction BEGIN; DROP TRIGGER myoldtrigger ON tracking.hits; Step 4b ------- Setup pg_partman to manage your partition set. SELECT partman.create_parent('tracking.hits', 'start', 'partman', 'weekly'); COMMIT; This single function call will add your old partition set into pg_partman's configuration, create a new trigger and possibly create some new child tables as well. pg_partman always keeps a minumum number of future partitions premade (based on the *premake* value in the config table or as a parameter to the create_parent() function), so if you don't have those yet, this step will take care of that as well. Adjust the parameters as needed and see the documentation for addtional options that are available. This call matches the time partition used in the example so far. \d+ tracking.hits Table "tracking.hits" Column | Type | Modifiers | Storage | Stats target | Description --------+-----------------------------+-----------+---------+--------------+------------- id | integer | not null | plain | | start | timestamp without time zone | not null | plain | | Triggers: hits_part_trig BEFORE INSERT ON tracking.hits FOR EACH ROW EXECUTE PROCEDURE tracking.hits_part_trig_func() Child tables: tracking.hits_p2015w53, tracking.hits_p2016w01, tracking.hits_p2016w02, tracking.hits_p2016w03, tracking.hits_p2016w04, tracking.hits_p2016w05, tracking.hits_p2016w06, tracking.hits_p2016w07, tracking.hits_p2016w08, tracking.hits_p2016w09 Note that I ran this create_parent() function on Feb 6th, 2016. This is the 5th week of the year. By default, the premake value is 4, so it created 4 weeks in the future. And since this was the initial creation, it also creates 4 tables in the past. Some of those tables already existed and, since their naming pattern matched pg_partman's, it handled that just fine. This final step is exactly the same no matter the partitioning type or interval, so once you reach here, run COMMIT and you're done! Schedule the run_maintenance() function to run (either via cron or the BGW) and future partition maintenance will be handled for you. Review the pg_partman.md documentation for additional configuration options. If you have any issues with this migration document, please create an issue on Github. pg_partman-4.6.0/doc/pg_partman.md000066400000000000000000003304161412761706100171040ustar00rootroot00000000000000PostgreSQL Partition Manager Extension (`pg_partman`) ===================================================== About ----- PostgreSQL Partition Manager is an extension to help make managing time or serial id based table partitioning easier. It has many options, but usually only a few are needed, so it's much easier to use than it may first appear (and definitely easier than implementing it yourself). Currently the trigger functions only handle inserts to the parent table. Updates that would move a value from one partition to another are only supported in PostgreSQL 11 native partitioning. Some features of this extension have been expanded upon in the author's blog - http://www.keithf4.com/tag/pg_partman As of version 3.0.1, this extension will support the native partitioning methods that were introduced in PostgreSQL 10. A trigger function is no longer necessary in native partitioning, but automatic child table creation is not handled natively, which is where this extension comes into play. Version 4.0.0 adds even more native support for features introduced in PG11 (easier index/fk inheritance, default partition). For non-native partitioning, if you attempt to insert data into a partition set that contains data for a partition that does not exist, that data will be placed into the set's parent table. This is preferred over automatically creating new partitions to match that data since a mistake that is causing non-partitioned data to be inserted could cause a lot of unwanted child tables to be made as well as contention due to transactional DDL. The `check_default()` function provides monitoring for any data getting inserted into the parent/default table and the `partition_data_`* set of functions can easily partition that data for you if it is valid data. That is much easier than having to clean up potentially hundreds or thousands of unwanted partitions. And also better than throwing an error and losing the data! For native partitioning, inserting data with no relevant child causes an error in PostgreSQL 10. A default partition for native is only available in PostgreSQL 11+. Note that future child table creation is based on the data currently in the partition set. This means that if you put "future" data in, newly created tables will be based off that value. This may cause intervening data to go to the parent/default as stated above if no child table exists. It is recommended that you set the `premake` value high enough to encompass your expected data range being inserted. And for non-native partitioning, set the `optimize_trigger` value to efficiently handle your most frequent data range. See below for further explanations on these configuration values. If you have an existing partition set and you'd like to migrate it to pg_partman, please see the migration.md file in the doc folder. This is for non-native partitioning only at this time. I'm working on a migration plan for getting non-native partition sets moved to native partition sets. If it all works out, will be included in a future version of pg_partman. ### Child Table Property Inheritance For this extension, most of the attributes of the child partitions are all obtained from the parent table. For non-native, trigger-based partititioning, all properties are managed via the parent and always will be. However, with native partitioning, certain features are not able to be inherited from the parent depending on the version of PostgreSQL. So pg_partman uses a template table instead. The following table matrix shows how certain propery inheritances are managed with pg_partman for native partitioning. If a property is not listed here, then assume it is managed via the parent. Note that if you will be upgrading your major version, you will have to change how the properties are managed appropriately if something has moved from being managed by the template to being managed by the real parent (Ex. foreign keys going from 10 to 11+). The WITH OIDS property is no longer officially supported by pg_partman at all (native or non-native) as of the release of PostgreSQL 12 since it was dropped there. | Feature | Parent Inheritance | Template Inheritance | | ---------- | --------------------- | --------------------- | | non-partition column primary key | | All | | non-partition column unique index | | All | | non-partition column unique index tablespace | | All | | unlogged table state* | | All | | non-unique indexes | 11, 12 | 10 | | foreign keys | 11, 12 | 10 | | tablespaces | 12 | 10, 11 | | privileges/ownership | All | | | constraints | All | | | defaults | All | | Privileges & ownership are inherited by default for non-native partitioning, but NOT for native partitioning. Also note that this inheritance is only at child table creation and isn't automatically retroactive when changed (see `reapply_privileges()`). Unless you need direct access to the child tables, this should not be needed. You can set the `inherit_privileges` option if this is needed (see config table information below). If a property is managed via the template table, it likely will not be retroactively applied to all existing child tables if that property is changed. It will apply to any newly created children, but will have to be manually applied to any existing children. The new IDENTITY feature introduced in PG10 is only supported in natively partitioned tables and the automatic generation of new sequence values using this feature is only supported when data is inserted through the parent table, not directly into the children. IMPORTANT NOTES: * The template table feature in use for PostgreSQL 10+ to handle certain features is only a temporary solution to help speed up native partitioning adoption. As things are handled better natively (as they were in PG11), the use of the template table will be phased out quickly from pg_partman. So please plan ahead for quick major version upgrades if you use this feature and check release notes carefully for changes. * The UNLOGGED status was moved to the template table as of v4.2.0 of pg_partman. This is due to an inconsistency in the way the property is handled when either enabling or disabling UNLOGGED on the parent table of a native partition set. That property does not actually change when the ALTER command is written so new child tables will continue to use the property that existed before. So if you wanted to change a partition set from UNLOGGED to LOGGED for all future children, it does not work. With the property now being managed on the template table, changing it there will allow the change to propogate to newly created children. Pre-existing child tables will have to be changed manually, but that has always been the case. See reported bug at https://www.postgresql.org/message-id/flat/15954-b61523bed4b110c4%40postgresql.org ### Time Zones It is important to ensure that the time zones for all systems that will be running pg_partman maintenance operations are consistent, especially when running time-based partitioning. The calls to pg_partman functions will use the time zone that is set by the client at the time the functions are called. This is consistent with the way PostgreSQL clients work in general. It is highly recommended to run your database system in UTC time to overcome issues that are currently not possible to solve due to Daylight Saving time changes. Then also ensure the client that will be creating partition sets and running the maintenance calls is also set to UTC. There is currently an open issue for anyone looking to help try and solve some of these DST issues - https://github.com/pgpartman/pg_partman/issues/334 ### Sub-partitioning Sub-partitioning with multiple levels is supported, but it is of very limited use in PostgreSQL and provides next to NO PERFORMANCE BENEFIT outside of extremely large data in a single partition set (100s of terabytes, petabytes). If you're looking for performance benefits, adjust your partition interval before considering sub-partitioning. It's main use is in data organization and retention management. You can do time->time, id->id, time->id and id->time. There is no set limit on the level of subpartitioning you can do, but be sensible and keep in mind performance considerations on managing many tables in a single inheritance set. Also, if the number of tables in a single partition set gets very high, you may have to adjust the `max_locks_per_transaction` postgresql.conf setting above the default of 64. Otherwise you may run into shared memory issues or even crash the cluster. If you have contention issues when `run_maintenance()` is called for general maintenance of all partition sets, you can set the **`automatic_maintenance`** column in the **`part_config`** table to false if you do not want that general call to manage your subpartition set. But you must then call `run_maintenance(parent_table)` directly, and often enough, to have to future partitions made. If you're on PG11+, you can use the new run_maintenance_proc() procedure to cause less contention issues since it automatically commits after each partition set's maintenance. PUBLICATION/SUBSCRIPTION for logical replication is NOT supported with native sub-partitioning. See the `create_parent_sub()` & `run_maintenance()` functions below for more information. ### Retention If you don't need to keep data in older partitions, a retention system is available to automatically drop unneeded child partitions. By default, they are only uninherited/detached not actually dropped, but that can be configured if desired. There is also a method available to dump the tables out if they don't need to be in the database anymore but still need to be kept. To set the retention policy, enter either an interval or integer value into the **retention** column of the **part_config** table. For time-based partitioning, the interval value will set that any partitions containing only data older than that will be dropped. For id-based partitioning, the integer value will set that any partitions with an id value less than the current maximum id value minus the retention value will be dropped. For example, if the current max id is 100 and the retention value is 30, any partitions with id values less than 70 will be dropped. The current maximum id value at the time the drop function is run is always used. Keep in mind that for subpartition sets, when a parent table has a child dropped, if that child table is in turn partitioned, the drop is a CASCADE and ALL child tables down the entire inheritance tree will be dropped. Also note that a partition set managed by pg_partman must always have at least one child, so retention will never drop the last child table in a set. ### Constraint Exclusion One of the big advantages of partitioning is a feature called **constraint exclusion** (see docs for explanation of functionality and examples http://www.postgresql.org/docs/current/static/ddl-partitioning.html#DDL-PARTITIONING-CONSTRAINT-EXCLUSION). The problem with most partitioning setups however, is that this will only be used on the partitioning control column. If you use a WHERE condition on any other column in the partition set, a scan across all child tables will occur unless there are also constraints on those columns. And predicting what a column's values will be to precreate constraints can be very hard or impossible. `pg_partman` has a feature to apply constraints on older tables in a partition set that may no longer have any edits done to them ("old" being defined as older than the `optimize_constraint` config value). It checks the current min/max values in the given columns and then applies a constraint to that child table. This can allow the constraint exclusion feature to potentially eliminate scanning older child tables when other columns are used in WHERE conditions. Be aware that this limits being able to edit those columns, but for the situations where it is applicable it can have a tremendous affect on query performance for very large partition sets. So if you are only inserting new data this can be very useful, but if data is regularly being inserted/updated throughout the entire partition set, this is of limited use. Functions for easily recreating constraints are also available if data does end up having to be edited in those older partitions. Note that constraints managed by PG Partman SHOULD NOT be renamed in order to allow the extension to manage them properly for you. For a better example of how this works, please see this blog post: http://www.keithf4.com/managing-constraint-exclusion-in-table-partitioning Adding these constraints could potentially cause contention with the data contained in those tables and also make pg_partman maintenance take a long time to run. As of version 4.2+ of pg_partman, there is now a "constraint_valid" column in the part_config(_sub) table to set whether these constraints should be set NOT VALID on creation. While this can make the creation of the constraint(s) nearly instantaneous, constraint exclusion cannot be used until it is validated. This is why constraints are added as valid by default. NOTE: This may not work with sub-partitioning. It will work on the first level of partitioning, but is not guarenteed to work properly on further sub-partition sets depending on the interval combinations and the optimize_constraint value. Ex: Weekly -> Daily with a daily optimize_constraint of 7 won't work as expected. Weekly constraints will get created but daily sub-partition ones likely will not. ### Custom Time Interval Considerations The list of time intervals given for create_parent() below are optimized to work as fast as possible with non-native, trigger-based partitioning. Intervals other than those values are possible, but performance will take a non-trivial hit to allow such flexibility. For native partitioning, unlike pg_partman's trigger based method, there's no differing method of partitioning for any given intervals. All possible intervals that use the native method have the same performance characteristics and are better than any trigger-based method. It is HIGHLY recommended to upgrade to PG10 if you need a partitioning interval different than the optimized ones that pg_partman provides. The smallest time interval supported is 1 second and the upper limit is bounded by the minimum and maximum timestamp values that PostgreSQL supports (http://www.postgresql.org/docs/current/static/datatype-datetime.html). The smallest integer interval supported at this time is 10. When first running `create_parent()` to create a partition set, intervals less than a day round down when determining what the first partition to create will be. Intervals less than 24 hours but greater than 1 minute use the nearest hour rounded down. Intervals less than 1 minute use the nearest minute rounded down. However, enough partitions will be made to support up to what the real current time is. This means that when `create_parent()` is run, more previous partitions may be made than expected and all future partitions may not be made. The first run of `run_maintenance()` will fix the missing future partitions. This happens due to the nature of being able to support custom time intervals. Any intervals greater than or equal to 24 hours should set things up as would be expected. Keep in mind that for intervals equal to or greater than 100 years, the extension will use the real start of the century or millennium to determine the partition name & constraint rules. For example, the 21st century and 3rd millennium started January 1, 2001 (not 2000). This also means there is no year "0". It's much too difficult to try to work around this and make nice "even" partition names & rules to handle all possible time periods people may need. Blame the Gregorian creators. ### Naming Length Limits PostgreSQL has an object naming length limit of 63 characters. If you try and create an object with a longer name, it truncates off any characters at the end to fit that limit. This can cause obvious issues with partition names that rely on having a specifically named suffix. PG Partman automatically handles this for all child tables, trigger functions and triggers. It will truncate off the existing parent table name to fit the required suffix. Be aware that if you have tables with very long, similar names, you may run into naming conflicts if they are part of separate partition sets. With serial based partitioning, be aware that over time the table name will be truncated more and more to fit a longer partition suffix. So while the extention will try and handle this edge case for you, it is recommended to keep table names that will be partitioned as short as possible. ### Unique Constraints & Upsert Table inheritance in PostgreSQL does not allow a primary key or unique index/constraint on the parent to apply to all child tables. The constraint is applied to each individual table, but not on the entire partition set as a whole. For example, this means a careless application can cause a primary key value to be duplicated in a partition set. In the mean time, a python script is included with `pg_partman` that can provide monitoring to help ensure the lack of this feature doesn't cause long term harm. See **`check_unique_constraint.py`** in the **Scripts** section. IMPORTANT NOTE: Upsert is no longer supported in pg_partman for native partitioning as of version 4.6.0 and PostgreSQL 11+. Please use the INSERT...ON CONFLICT feature built into PostgreSQL. For non-native partitioning and PG10 native partitioning, INSERT ... ON CONFLICT (upsert) is supported in the partitioning trigger as well as native partitioning, but is very limited. The major limitations are that the constraint violations that would trigger the ON CONFLICT clause only occur on individual child tables that actually contain data due to reasons explained above. Of a larger concern than data duplication is an ON CONFLICT DO UPDATE clause which may not fire and cause wildly inconsistent data if not accounted for. For situations where only new data is being inserted, upsert can provide significant performance improvements. However, if you're relying on data in older partitions to cause a constraint violation that upsert would normally handle, it likely will not work. Also, if the resulting UPDATE would end up violating the partitioning constraint of that chld table, it will fail. Neither pg_partman & PG10 native partitioning currently support UPDATES that would require moving a row from one child table to another. This is only supported in PG11+. pg_partman's upsert feature is optional, turned off by default and was only included since there was no native support on the core PostgreSQL roadmap at the time it was implemented. At this time, if you have not implemented this feature, it is highly recommended you upgrade to PG11. ### Logging/Monitoring The PG Jobmon extension (https://github.com/omniti-labs/pg_jobmon) is optional and allows auditing and monitoring of partition maintenance. If jobmon is installed and configured properly, it will automatically be used by partman with no additional setup needed. Jobmon can also be turned on or off individually for each partition set by using the `jobmon` column in the **`part_config`** table or with the option to `create_parent()` during initial setup. Note that if you try to partition `pg_jobmon`'s tables you **MUST** set the jobmon option in `create_parent()` to false, otherwise it will be put into a permanent lockwait since `pg_jobmon` will be trying to write to the table it's trying to partition. By default, any function that fails to run successfully 3 consecutive times will cause jobmon to raise an alert. This is why the default pre-make value is set to 4 so that an alert will be raised in time for intervention with no additional configuration of jobmon needed. You can of course configure jobmon to alert before (or later) than 3 failures if needed. If you're running partman in a production environment it is HIGHLY recommended to have jobmon installed and some sort of 3rd-party monitoring configured with it to alert when partitioning fails (Nagios, Circonus, etc). Background Worker ----------------- With PostgreSQL 9.4, the ability to create custom background workers and dynamically load them during runtime was introduced. `pg_partman`'s BGW is basically just a scheduler that runs the `run_maintenance()` function for you so that you don't have to use an external scheduler (cron, etc). Right now it doesn't do anything differently than calling `run_maintenance()` directly, but that may change in the future. See the README.md file for installation instructions. If you need to call `run_maintenance()` directly on any specific partition sets, you will still need to do so manually using an outside scheduler. This only maintains partition sets that have `automatic_maintenance` in `**part_config**` set to true. LOG messages are output to the normal PostgreSQL log file to indicate when the BGW runs. Additional logging messages are available if *log_min_messages* is set to "DEBUG1". **REMEMBER:** You must have `pg_partman_bgw` in your `shared_preload_libraries` (requires a restart). The following configuration options are available to add into postgresql.conf to control the BGW process: - `pg_partman_bgw.dbname` - Required. The database(s) that `run_maintenance()` will run on. If more than one, use a comma separated list. If not set, BGW will do nothing. - `pg_partman_bgw.interval` - Number of seconds between calls to `run_maintenance()`. Default is 3600 (1 hour). - See further documenation below on suggested values for this based on partition types & intervals used. - `pg_partman_bgw.role` - The role that `run_maintenance()` will run as. Default is "postgres". Only a single role name is allowed. - `pg_partman_bgw.analyze` - Same purpose as the p_analyze argument to `run_maintenance()`. See below for more detail. Set to 'on' for TRUE (default for PG10 and older). Set to 'off' for FALSE (Default for PG11+). - `pg_partman_bgw.jobmon` - Same purpose as the p_jobmon argument to `run_maintenance()`. See below for more detail. Set to 'on' for TRUE. Set to 'off' for FALSE. Default is 'on'. If for some reason the main background worker process crashes, it is set to try and restart every 10 minutes. Check the postgres logs for any issues if the background worker is not starting. As of version 4.0.0, the background worker still uses the normal run_maintenance() function. An option to use the new procedure is in the works. Extension Objects ----------------- As of 4.4.0, SECURITY DEFINER has been removed from all functions in pg_partman. Requiring a superuser to use pg_partman is now completely optional for native partitioning. To run as nonsuperuser, the role(s) that run pg_partman functions and maintenance must have ownership of all partition sets they manage and permissions to create objects in any schema that will contain partition sets that it manages. For ease of use and privilege management, it is recommended to create a role dedicated to partition management. Please see the main README.md file for role & privileges setup instructions. As a note for people that were not aware, you can name arguments in function calls to make calling them easier and avoid confusion when there are many possible arguments. If a value has a default listed, it is not required to pass a value to that argument. As an example: `SELECT create_parent('schema.table', 'col1', 'partman', 'daily', p_start_partition := '2015-10-20');` ### Creation Functions *`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, p_date_trunc_interval text DEFAULT NULL) RETURNS boolean`* * Main function to create a partition set with one parent table and inherited children. Parent table must already exist. Please apply all defaults, indexes, constraints, privileges & ownership to parent table so they will propagate to children. For native partitioning, the parent table must already be declared as such and config options passed to this function must match that definition. See notes above about handling indexes & foreign keys. * An ACCESS EXCLUSIVE lock is taken on the parent table during the running of this function. No data is moved when running this function, so lock should be brief. * For PG11+, a default partition is automatically created. A "_default" suffix is added onto the current table name. * p_parent_table - the existing parent table. MUST be schema qualified, even if in public schema. * p_control - the column that the partitioning will be based on. Must be a time or integer based column. * p_type - one of the following values to set the partitioning type that will be used: + **native** - Use the native partitioning methods that are built into PostgreSQL 10+. - For PG11+, it is highly recommended that native partitioning be used over trigger-based partitioning. PG10 is still lacking significant features for native partitioning, so please see notes above for more info. - Provides significantly better write & read performance than "partman" partitioning. - Child table creation is kept up to date by running `run_maintenance(_proc)`. There is no trigger maintenance. + **partman** - Create a trigger-based partition set using pg_partman's method of partitioning. - Whether it is time or serial based is determined by the control column's data type and if the p_epoch flag is set. - The number of partitions most efficiently managed behind and ahead of the current one is determined by the **optimize_trigger** config value in the part_config table (default of 4 means data for 4 previous and 4 future partitions are handled best). - *Beware setting the optimize_trigger value too high as that will lessen the efficiency boost*. - Inserts to the parent table outside the optimize_trigger window will go to the proper child table if it exists, but performance will be degraded due to the higher overhead of handling that condition. - If the child table does not exist for the value given, the row will go to the parent. - Child table creation & trigger function is kept up to date by the `run_maintenance()` function. * `p_interval` - the time or integer range interval for each partition. No matter the partitioning type, value must be given as text. The generic intervals of "yearly -> quarter-hour" are for time partitioning and giving one of these explicit values when using pg_partman's trigger-based partitioning will allow significantly better performance than using an arbitrary time interval. For native partitioning, any interval value is valid and will have the same performance which is always better than trigger-based. + *yearly* - One partition per year + *quarterly* - One partition per yearly quarter. Partitions are named as YYYYqQ (ex: 2012q4) + *monthly* - One partition per month + *weekly* - One partition per week. Follows ISO week date format (http://en.wikipedia.org/wiki/ISO_week_date). Partitions are named as IYYYwIW (ex: 2012w36) + *daily* - One partition per day + *hourly* - One partition per hour + *half-hour* - One partition per 30 minute interval on the half-hour (1200, 1230) + *quarter-hour* - One partition per 15 minute interval on the quarter-hour (1200, 1215, 1230, 1245) + *\* - Any other interval besides the values above that is valid for the PostgreSQL interval type. Note this will have a significant performance penalty if not using native partitioning. Do not type cast the parameter value, just leave as text. + *\* - For ID based partitions, the integer value range of the ID that should be set per partition. Enter this as an integer in text format ('100' not 100). Must be greater than or equal to 10. * `p_constraint_cols` - an optional array parameter to set the columns that will have additional constraints set. See the **About** section above for more information on how this works and the **apply_constraints()** function for how this is used. * `p_premake` - is how many additional partitions to always stay ahead of the current partition. Default value is 4. This will keep at minimum 5 partitions made, including the current one. For example, if today was Sept 6th, and `premake` was set to 4 for a daily partition, then partitions would be made for the 6th as well as the 7th, 8th, 9th and 10th. Note some intervals may occasionally cause an extra partition to be premade or one to be missed due to leap years, differing month lengths, daylight savings (on non-UTC systems), etc. This won't hurt anything and will self-correct. If partitioning ever falls behind the `premake` value, normal running of `run_maintenance()` and data insertion should automatically catch things up. * `p_automatic_maintenance` - parameter to set whether maintenance is managed automatically when `run_maintenance()` is called without a table parameter or by the background worker process. Current valid values are "on" and "off". Default is "on". When set to off, `run_maintenance()` can still be called on an individual partition set by passing it as a paramter to the function. See **run_maintenance** in Maintenance Functions section below for more info. * `p_start_partition` - allows the first partition of a set to be specified instead of it being automatically determined. Must be a valid timestamp (for time-based) or positive integer (for id-based) value. Be aware, though, the actual parameter data type is text. For time-based partitioning, all partitions starting with the given timestamp up to CURRENT_TIMESTAMP (plus `premake`) will be created. For id-based partitioning, only the partition starting at the given value (plus `premake`) will be made. Note that for sub-partitioning, this only applies during initial setup and not during ongoing maintenance. * `p_inherit_fk` - allows `pg_partman` to automatically manage inheriting any foreign keys that exist on the parent (or template for native) table to all its children. Defaults to TRUE. Note this option is only relevant for PostgreSQL 10 and older. PG11+ automatically inherits any foreign keys placed on the parent and is not optional. * `p_epoch` - tells `pg_partman` that the control column is an integer type, but actually represents and epoch time value. You can also specify whether the value is seconds or milliseconds. Valid values for this option are: 'seconds', 'milliseconds' & 'none'. The default is 'none'. All triggers, constraints & table names will be time-based. In addition to a normal index on the control column, be sure you create a functional, time-based index on the control column (to_timestamp(controlcolumn)) as well so this works efficiently. * `p_upsert` - adds upsert to insert queries in the partition trigger to allow handeling of conflicts Defaults to '' (empty string) which means it's inactive. + IMPORTANT NOTE: This feature was deprecated in v4.6.0. Please plan on migrating to PG11, native partitioning and INSERT...ON CONFLICT if you are using this feature. + the value entered here is the entire ON CONFLICT clause which will then be appended to the INSERT statement(s) in the trigger + Ex: to ignore conflicting rows on a table with primary key "id" set p_upsert to `'ON CONFLICT (id) DO NOTHING'` + Ex: to update a conflicting row on a table with columns (id(pk), val) set p_upsert to `'ON CONFLICT (id) DO UPDATE SET val=EXCLUDED.val'` + Requires postgresql 9.5 + See *About* section above for more info. * `p_publications` - Option to add child tables to publications for use with logical replication. Value is an array list of publication names, so multiple publications can be added to each child. Note that if you are replicating to a partition set on the subscriber side, you will have to set the `subscription_refresh` option in the `part_config` table on the subscriber side to pick up new tables from the source publication. Currently does not support sub-partitioning for native partition sets since a publication cannot be added to the parent of a natively partitioned table. * `p_trigger_return_null` - Only applies to non-native, trigger-based partitioning. Boolean value that allows controlling the behavior of the partition trigger RETURN. By default this is true and the trigger returns NULL to prevent data going into the parent table as well as the children. However, if you have multiple triggers and are relying on the return to be the NEW column value, this can cause a problem. Setting this config value to false will cause the partition trigger to RETURN NEW. You are then responsible for handling the return value in another trigger appropriately. Otherwise, this will cause new data to go into both the child and parent table of the partition set. * `p_template_table` - For native partitioning in PG10, indexes, foreign keys & tablespaces cannot be set on the parent table. For PG11, only unique indexes that don't include the partition key cannot be created on the parent. Therefore, if you want them to be automatically created on child tables, they must be managed elsewhere. If you do not pass a value here, a template table will automatically be made for you in same schema that pg_partman was installed to. Note that until indexes, foreign keys or tablespaces are made on the template, no child tables will have any. Use the python scripts to reapply the indexes and foreign keys to the partition set when the template table is ready. For tablespaces, you will have to manually move any previously existing child tables. If you pre-create a template table and pass its name here, then the initial child tables will obtain these properties immediately. * `p_jobmon` - allow `pg_partman` to use the `pg_jobmon` extension to monitor that partitioning is working correctly. Defaults to TRUE. * `p_date_trunc_interval` - By default, pg_partman's time-based partitioning will truncate the child table starting values to line up at the beginning of typical boundaries (midnight for daily, day 1 for monthly, Jan 1 for yearly, etc). If a custom time interval that does not fall on those boundaries is desired, this option may be required to ensure the child table has the expected boundaries (especially if you also set p_start_partition). The valid values allowed for this parameter are the interval values accepted by the built-in date_trunc() function (day, week, month, etc). For example, if you set a 9-week interval, by default pg_partman would truncate the tables by month (since the interval is greater than one month but less than 1 year) and unexpectedly start on the first of the month in some cases. Set this value to week, so that the child table start values are properly truncated on a weekly basis to line up with the 9-week interval. If you are using a custom time interval, please experiment with this option to get the expected set of child tables you desire or use a more typical partitioning interval to simplify partition management. *`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, p_date_trunc_interval text DEFAULT NULL) RETURNS boolean`* * Create a subpartition set of an already existing partitioned set. See important notes about Subpartitioning above. * `p_top_parent` - This parameter is the parent table of an already existing partition set. It tells `pg_partman` to turn all child tables of the given partition set into their own parent tables of their own partition sets using the rest of the parameters for this function. * `p_native_check` - Turning an existing native partition set into a sub-partitioned set is a **destructive** process. A table must be declared natively partitioned at creation time and cannot be altered later. Therefore existing child tables must be dropped and recreated as partitioned parent tables. This flag is here to help ensure this function is not run without prior knowledge that all data in the partition set will be destroyed as part of the creation process. It must be set to "yes" to proceed with sub-partitioning a native partition set. This option can be ignored if you are created a trigger-based pg_partman partition set. * All other parameters to this function have the same exact purpose as those of `create_parent()`, but instead are used to tell `pg_partman` how each child table shall itself be partitioned. * For example if you have an existing partition set done by year and you then want to partition each of the year partitions by day, you would use this function. * It is advised that you keep table names short for subpartition sets if you plan on relying on the table names for organization. The suffix added on to the end of a table name is always guarenteed to be there for whatever partition type is active for that set, but if the total length is longer than 63 chars, the original name will get truncated. Longer table names may cause the original parent table names to be truncated and possibly cut off the top level partitioning suffix. I cannot control this and made the requirement that the lowest level partitioning suffix survives. * Note that for the first level of subpartitions, the `p_parent_table` argument you originally gave to `create_parent()` would be the exact same value you give to `create_sub_parent()`. If you need further subpartitioning, you would then start giving `create_sub_parent()` a different value (the child tables of the top level partition set). * For native partitioning, the template table that is already set for the given p_top_parent will automatically be used. *`partition_data_time(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval interval DEFAULT NULL, p_lock_wait numeric DEFAULT 0, p_order text DEFAULT 'ASC', p_analyze boolean DEFAULT true, p_source_table text DEFAULT NULL, p_ignored_columns text[] DEFAULT NULL) RETURNS bigint`* * This function is used to partition data that may have existed prior to setting up the parent table as a time-based partition set. It also fixes data that accidentally gets inserted into the parent table (trigger-based only) or default table (native, PG11+ only). * If the needed partition does not exist, it will automatically be created. If the needed partition already exists, the data will be moved there. * If you are trying to partition a large amount of data automatically, it is recommended to either use the `partition_data.py` script to commit data in smaller batches. Or if you're on PG11+, use the `partition_data_proc()` procedure to do the same thing. This will greatly reduce issues caused by long running transactions and data contention. * For sub-partitioned sets, you must start partitioning data at the highest level and work your way down each level. This means you must first run this function before running create_sub_parent() to create the additional partitioning levels. Then continue running this function again on each new sub-parent once they're created. See the pg_partman_howto.md document for a full example. IMPORTANT NOTE: this may not work as expected for native partitioning since subpartitioning a native set in pg_partman is a destructive operation. See create_sub_parent(). * `p_parent_table` - the existing parent table. For non-native partitioning, this is assumed to be where the unpartitioned data is located. MUST be schema qualified, even if in public schema. * `p_batch_interval` - optional argument, only relevant for non-native partitioning. A time interval of how much of the data to move. This can be smaller than the partition interval, allowing for very large sized partitions to be broken up into smaller commit batches. Defaults to the configured partition interval if not given or if you give an interval larger than the partition interval. NOTE: This option CANNOT be used when moving data out of a default partition in PostgreSQL 11+. * `p_batch_count` - optional argument, how many times to run the `batch_interval` in a single call of this function. Default value is 1. For native partitioning, this sets how many child tables will be processed in a single run. * `p_lock_wait` - optional argument, sets how long in seconds to wait for a row to be unlocked before timing out. Default is to wait forever. * `p_order` - optional argument, by default data is migrated out of the parent in ascending order (ASC). Allows you to change to descending order (DESC). * `p_analyze` - optional argument, by default whenever a new child table is created, an analyze is run on the parent table of the partition set to ensure constraint exclusion works. This analyze can be skipped by setting this to false and help increase the speed of moving large amounts of data. If this is set to false, it is highly recommended that a manual analyze of the partition set be done upon completion to ensure statistics are updated properly. * `p_source_table` - This option can be used when you need to move data into a natively partitioned set. Pass a schema qualified tablename to this parameter and any data in that table will be MOVED to the partition set designated by p_parent_table, creating any child tables as needed. * `p_ignored_columns` - This option allows for filtering out specific columns when moving data from the default/parent to the proper child table(s). This is generally only required when using columns with a GENERATED ALWAYS value since directly inserting a value would fail when moving the data. Value is a text array of column names. * Returns the number of rows that were moved from the parent table to partitions. Returns zero when source/parent table is empty and partitioning is complete. *`partition_data_id(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval bigint DEFAULT NULL, p_lock_wait numeric DEFAULT 0, p_order text DEFAULT 'ASC', p_analyze boolean DEFAULT true, p_source_table text DEFAULT NULL, p_ignored_columns text[] DEFAULT NULL) RETURNS bigint`* * This function is used to partition data that may have existed prior to setting up the parent table as a serial id partition set. It also fixes data that accidentally gets inserted into the parent (trigger-based only). * If the needed partition does not exist, it will automatically be created. If the needed partition already exists, the data will be moved there. * If you are trying to partition a large amount of data automatically, it is recommended to either use the `partition_data.py` script to commit data in smaller batches. Or if you're on PG11+, use the `partition_data_proc()` procedure to do the same thing. This will greatly reduce issues caused by long running transactions and data contention. * For sub-partitioned sets, you must start partitioning data at the highest level and work your way down each level. This means you must first run this function before running create_sub_parent() to create the additional partitioning levels. Then continue running this function again on each new sub-parent once they're created. See the pg_partman_howto.md document for a full example. IMPORTANT NOTE: this may not work as expected for native partitioning since subpartitioning a native set in pg_partman is a destructive operation. See create_sub_parent(). * `p_parent_table` - the existing parent table. For non-native partitioning, this is assumed to be where the unpartitioned data is located. MUST be schema qualified, even if in public schema. * `p_batch_interval` - optional argument, only relevant for non-native partitioning. A time interval of how much of the data to move. This can be smaller than the partition interval, allowing for very large sized partitions to be broken up into smaller commit batches. Defaults to the configured partition interval if not given or if you give an interval larger than the partition interval. NOTE: This option CANNOT be used when moving data out of a default partition in PostgreSQL 11+. * `p_batch_count` - optional argument, how many times to run the `batch_interval` in a single call of this function. Default value is 1. For native partitioning, this sets how many child tables will be processed in a single run. * `p_lock_wait` - optional argument, sets how long in seconds to wait for a row to be unlocked before timing out. Default is to wait forever. * `p_order` - optional argument, by default data is migrated out of the parent in ascending order (ASC). Allows you to change to descending order (DESC). * `p_analyze` - optional argument, by default whenever a new child table is created, an analyze is run on the parent table of the partition set to ensure constraint exclusion works. This analyze can be skipped by setting this to false and help increase the speed of moving large amounts of data. If this is set to false, it is highly recommended that a manual analyze of the partition set be done upon completion to ensure statistics are updated properly. * `p_source_table` - This option can be used when you need to move data into a natively partitioned set. Pass a schema qualified tablename to this parameter and any data in that table will be MOVED to the partition set designated by p_parent_table, creating any child tables as needed. * `p_ignored_columns` - This option allows for filtering out specific columns when moving data from the default/parent to the proper child table(s). This is generally only required when using columns with a GENERATED ALWAYS value since directly inserting a value would fail when moving the data. Value is a text array of column names. * Returns the number of rows that were moved from the parent table to partitions. Returns zero when source/parent table is empty and partitioning is complete. *`partition_data_proc(p_parent_table text, p_interval text DEFAULT NULL, p_batch int DEFAULT NULL, p_wait int DEFAULT 1, p_source_table text DEFAULT NULL, p_order text DEFAULT 'ASC', p_lock_wait int DEFAULT 0, p_lock_wait_tries int DEFAULT 10, p_quiet boolean DEFAULT false, p_ignored_columns text[] DEFAULT NULL)`* * A procedure that can partition data in distinct commit batches to avoid long running transactions and data contention issues. * Only works with PostgreSQL 11+ * Calls either partition_data_time() or partition_data_id() in a loop depending on partitioning type. * `p_parent_table` - Parent table of an already created partition set. * `p_interval` - Value that is passed on to the partitioning function as p_batch_interval argument. Use this to set an interval smaller than the partition interval to commit data in smaller batches. Defaults to the partition interval if not given. NOTE: This option CANNOT be used when moving data out of a default partition in PostgreSQL 11+. * `p_batch` - How many times to loop through the value given for p_interval. If p_interval not set, will use default partition interval and make at most -b partition(s). Procedure commits at the end of each individual batch. (NOT passed as p_batch_count to partitioning function). If not set, all data in the parent/source table will be partitioned in a single run of the procedure. * `p_wait` - Cause the procedure to pause for a given number of seconds between commits (batches) to reduce write load * `p_source_table` - Same as the p_source_table option in the called partitioning function. * `p_order` - Allows you to specify the order that data is migrated from the parent/default to the children, either ascending (ASC) or descending (DESC). Default is ASC. * `p_lock_wait` - Parameter passed directly through to the underlying partition_data_*() function. Number of seconds to wait on rows that may be locked by another transaction. Default is to wait forever (0). * `p_lock_wait_tries` - Parameter to set how many times the procedure will attempt waiting the amount of time set for p_lock_wait. Default is 10 tries. * `p_quiet` - Procedures cannot return values, so by default it emits NOTICE's to show progress. Set this option to silence these notices. * `p_ignored_columns` - This option allows for filtering out specific columns when moving data from the default/parent to the proper child table(s). This is generally only required when using columns with a GENERATED ALWAYS value since directly inserting a value would fail when moving the data. Value is a text array of column names. *`create_partition_time(p_parent_table text, p_partition_times timestamptz[], p_analyze boolean DEFAULT true, p_start_partition text DEFAULT NULL) RETURNS boolean`* * This function is used to create child partitions for the given parent table. * Normally this function is never called manually since partition creation is managed by run_maintenance(). But if you need to force the creation of specific child tables outside of normal maintenance, this function can make it easier. * For non-native partitioning, you may also need to call create_function_time() to update the partitioning trigger if you created partitions in the "current" optimization window. * `p_parent_table` - parent table to create new child table(s) in. * `p_partition_times` - An array of timestamptz values to create children for. If the child table does not exist, it will be created. If it does exist, it will not be created and the function will still exit cleanly. Be aware that the value given will be used as the lower boundary for the child table and also influence the name given to the child table. So ensure the timestamp value given is consistent with other children or you may encounter a gap in value coverage. * `p_analyze` - If a new child table is created, an analyze is normally kicked off so that the statistics are aware of the constraint boundaries for constraint exclusion. For larger partition sets, this analyze can take a long time. Set this to false to skip this automatic analyze. * `p_start_partition` - When using sub-partitioning, allows passing along the start partition value for the sub-partition child tables. * Returns TRUE if any child tables were created for the given timestamptz values. Returns false if no child tables were created. *`create_partition_id(p_parent_table text, p_partition_ids bigint[], p_analyze boolean DEFAULT true, p_start_partition text DEFAULT NULL) RETURNS boolean`* * This function is used to create child partitions for the given parent table. * Normally this function is never called manually since partition creation is managed by run_maintenance(). But if you need to force the creation of specific child tables outside of normal maintenance, this function can make it easier. * For non-native partitioning, you may need to also call create_function_id() to update the partitioning trigger if you created partitions in the "current" optimization window. * `p_parent_table` - parent table to create new child table(s) in. * `p_partition_ids` - An array of integer values to create children for. If the child table does not exist, it will be created. If it does exist, it will not be created and the function will still exit cleanly. Be aware that the value given will be used as the lower boundary for the child table and also influence the name given to the child table. So ensure the integer value given is consistent with other children or you may encounter a gap in value coverage. * `p_analyze` - If a new child table is created, an analyze is normally kicked off so that the statistics are aware of the constraint boundaries for constraint exclusion. For larger partition sets, this analyze can take a long time. Set this to false to skip this automatic analyze. * `p_start_partition` - When using sub-partitioning, allows passing along the start partition value for the sub-partition child tables. * Returns TRUE if any child tables were created for the given integer values. Returns false if no child tables were created. *`create_function_time(p_parent_table text, p_job_id bigint DEFAULT NULL) RETURNS void`* * This function is used to create the trigger function for non-native time-based partitioning. * Normally this function is never called manually since function creation is managed by run_maintenance(). But if you need to force the re-creation of the trigger function, this will let you do that. * `p_parent_table` - parent table to recreate trigger function on. * The p_job_id parameter is optional. It's for internal use and allows job logging to be consolidated into the original job that called this function if applicable. *`create_function_id(p_parent_table text, p_job_id bigint DEFAULT NULL) RETURNS void`* * This function is used to create the trigger function for non-native serial partitioning. * Normally this function is never called manually since function creation is managed by run_maintenance(). But if you need to force the re-creation of the trigger function, this will let you do that. * `p_parent_table` - parent table to recreate trigger function on. * The p_job_id parameter is optional. It's for internal use and allows job logging to be consolidated into the original job that called this function if applicable. ### Maintenance Functions *`run_maintenance(p_parent_table text DEFAULT NULL, p_analyze boolean DEFAULT NULL, p_jobmon boolean DEFAULT true) RETURNS void `* * Run this function as a scheduled job (cron, etc) to automatically create child tables for partition sets configured to use it. * You can also use the included background worker (BGW) to have this automatically run for you by PostgreSQL itself. Note that the `p_parent_table` parameter is not available with this method, so if you need to run it for a specific partition set, you must do that manually or scheduled as noted above. The other parameters have postgresql.conf values that can be set. See BGW section above. * This function also maintains the partition retention system for any partitions sets that have it turned on (see **About** and part_config table below). * Every run checks for all tables listed in the **part_config** table with **automatic_maintenance** set to true and either creates new partitions for them or runs their retention policy. * By default, all partition sets have automatic_maintenance set to true. * New partitions are only created if the number of child tables ahead of the current one is less than the premake value, so you can run this more often than needed without fear of needlessly creating more partitions. * Will automatically update the trigger function for non-native partition sets to keep the parent table pointing at the correct partitions. When using time, run this function more often than the partitioning interval to keep the trigger function running its most efficient. For example, if using quarter-hour, run every 5 minutes; if using daily, run at least twice a day, etc. * `p_parent_table` - an optional parameter that if passed will cause `run_maintenance()` to be run for ONLY that given table, no matter what automatic_maintenance is set to. High transcation rate tables can cause contention when maintenance is being run for many tables at the same time, so this allows finer control of when partition maintenance is run for specific tables. Note that this will also cause the retention system to only be run for the given table as well. * `p_analyze` - For non-native partitioning and native partitioning in PG10, when a new child table is created, an analyze is run on the parent to ensure statistics are updated. For PG11+, this is no longer done, so it is not run by default then. For large partition sets, this analyze can take a while and if `run_maintenance()` is managing several partitions in a single run, this can cause contention while the analyze finishes. Set this to false (or just leave NULL for PG11+) to disable the analyze run and avoid this contention. For PG10 and older, please note that you must then schedule an analyze of the parent table at some point. * `p_jobmon` - an optional paramter to control whether `run_maintenance()` itself uses the `pg_jobmon` extension to log what it does. Whether the maintenance of a particular table uses `pg_jobmon` is controlled by the setting in the **part_config** table and this setting will have no affect on that. Defaults to true if not set. *`run_maintenance_proc(p_wait int DEFAULT 0, p_analyze boolean DEFAULT NULL, p_jobmon boolean DEFAULT true)`* * For PG11+, this is the preferred method to run partition maintenance vs directly calling the run_maintenance() function. * This procedure can be called instead of the `run_maintenance()` function to cause PostgreSQL to commit after each partition set's maintenance has finished. This greatly reduces contention issues with long running transactions when there are many partition sets to maintain. * NOTE: The BGW does not yet use this procedure and still uses the standard run_maintenance() function. * `p_wait` - How many seconds to wait between each partition set's maintenance run. Defaults to 0. * `p_analyze` - See p_analyze option in run_maintenance. *`check_default(p_exact_count boolean DEFAULT true)`* * Run this function to monitor that the parent tables (non-native) or default tables (native PG11+) of the partition sets that `pg_partman` manages do not get rows inserted to them. * Returns a row for each parent/default table along with the number of rows it contains. Returns zero rows if none found. * `partition_data_time()` & `partition_data_id()` can be used to move data from these parent/default tables into the proper children. * p_exact_count will tell the function to give back an exact count of how many rows are in each parent if any is found. This is the default if the parameter is left out. If you don't care about an exact count, you can set this to false and it will return if it finds even just a single row in any parent. This can significantly speed up the check if a lot of data ends up in a parent or there are many partitions being managed. *`show_partitions (p_parent_table text, p_order text DEFAULT 'ASC', p_include_default boolean DEFAULT false) RETURNS TABLE (partition_schemaname text, partition_tablename text)`* * List all child tables of a given partition set managed by pg_partman. Each child table returned as a single row. * Tables are returned in the order that makes sense for the partition interval, not by the locale ordering of their names. * For PG11+, the default partition can be returned in this result set as well if `p_include_default` is set to true. It is false by default since that is far more common with internal code. * `p_order` - optional parameter to set the order the child tables are returned in. Defaults to ASCending. Set to 'DESC' to return in descending order. If the default is included, it is always listed first. *`show_partition_name (p_parent_table text, p_value text, OUT partition_table text, OUT suffix_timestamp timestamp, OUT suffix_id bigint, OUT table_exists boolean)`* * Given a parent table managed by pg_partman (p_parent_table) and an appropriate value (time or id but given in text form for p_value), return the name of the child partition that that value would exist in. * If using epoch time partitioning, give the timestamp value, NOT the integer epoch value (use to_timestamp() to convert an epoch value). * Returns a child table name whether the child table actually exists or not * Also returns a raw value (suffix_timestamp or suffix_id) for the partition suffix for the given child table * Also returns a boolean value (table_exists) to say whether that child table actually exists *`@extschema@.show_partition_info (p_child_table text, p_partition_interval text DEFAULT NULL, p_parent_table text DEFAULT NULL, OUT child_start_time timestamptz, OUT child_end_time timestamptz, OUT child_start_id bigint, OUT child_end_id bigint, OUT suffix text) RETURNS record`* * Given a schema-qualified child table name (p_child_table), return the relevant boundary values of that child as well as the suffix appended to the child table name. * `p_partition_interval` - If given, return boundary results based on this interval. If not given, function looks up the interval stored in the part_config table for this partition set. * `p_parent_table` - Optional argument that can be given when parent_table is known and to avoid a catalog lookup for the parent table associated with p_child_table. * `OUT child_start_times & child_end_time` - Function returns values for these output parameters if the partition set is time-based. Otherwise outputs NULL. Note that start value is INCLUSIVE and end value is EXCLUSIVE of the given child table boundaries, exactly as they are defined in the database. * `OUT child_start_id & child_end_id` - Function returns values for these output parameters if the partition set is integer-based. Otherwise outputs NULL. Note that start value is INCLUSIVE and end value is EXCLUSIVE of the given child table boundaries, exactly as they are defined in the database. * `OUT suffix` - Outputs the text portition appended to the child table that identifies its contents minus the "_p" (Ex "2020_01_30" OR "920000"). Useful for generating your own suffixes for partitioning similar to how pg_partman does it. *`@extschema@.dump_partitioned_table_definition(p_parent_table text, p_ignore_template_table boolean default false) RETURNS text`* * Function to return the necessary commands to recreate a partition set in pg_partman for the given parent table (p_parent_table). * Returns both the `create_parent()` call as well as an UPDATE statement to set additional parameters stored in part_config. * NOTE: This currently only works with single level partition sets. Looking for contributions to add support for sub-partition sets * `p_ignore_template` - For native partitioned tables the template table needs to be created before the SQL generated by this function will work properly. If you haven't modified the template table at all then it's safe to pass TRUE here to have the generated SQL tell partman to generate a new template table. But for safety it's preferred to use pg_dump to dump the template tables and restore them prior to using the generated SQL so that you can maintain any template overrides. *`partition_gap_fill(p_parent_table text) RETURNS integer`* * Function to fill in any gaps that may exist in the series of child tables for a given parent table (p_parent_table). * Starts from current minimum child table and fills in any gaps encountered based on the partition interval, up to the current maximum child table * Returns how many child tables are created. Returns 0 if none are created. *`apply_constraints(p_parent_table text, p_child_table text DEFAULT NULL, p_analyze boolean DEFAULT FALSE, p_job_id bigint DEFAULT NULL) RETURNS void`* * Apply constraints to child tables in a given partition set for the columns that are configured (constraint names are all prefixed with "partmanconstr_"). * Note that this does not need to be called manually to maintain custom constraints. The creation of new partitions automatically manages adding constraints to old child tables. * Columns that are to have constraints are set in the **part_config** table **constraint_cols** array column or during creation with the parameter to `create_parent()`. * If the `pg_partman` constraints already exists on the child table, the function will cleanly skip over the ones that exist and not create duplicates. * If the column(s) given contain all NULL values, no constraint will be made. * If the child table parameter is given, only that child table will have constraints applied. * If the p_child_table parameter is not given, constraints are placed on the last child table older than the `optimize_constraint` value. For example, if the optimize_constraint value is 30, then constraints will be placed on the child table that is 31 back from the current partition (as long as partition pre-creation has been kept up to date). * If you need to apply constraints to all older child tables, use the included python script (reapply_constraint.py). Or if you're on PG11+, use the `reapply_constraints_proc` procedure. Both these methods have options to make constraint application easier with as little impact on performance as possible. * The p_job_id parameter is optional. It's for internal use and allows job logging to be consolidated into the original job that called this function if applicable. *`drop_constraints(p_parent_table text, p_child_table text, p_debug boolean DEFAULT false)`* * Drop constraints that have been created by `pg_partman` for the columns that are configured in *part_config*. This makes it easy to clean up constraints if old data needs to be edited and the constraints aren't allowing it. * Will only drop constraints that begin with `partmanconstr_*` for the given child table and configured columns. * If you need to drop constraints on all child tables, use the included python script (`reapply_constraint.py`). Or if you're on PG11+, use reapply_constraints_proc(). These both have options to make constraint removal easier with as little impact on performance as possible. * The debug parameter will show you the constraint drop statement that was used. *`reapply_constraints_proc(p_parent_table text, p_drop_constraints boolean DEFAULT false, p_apply_constraints boolean DEFAULT false, p_wait int DEFAULT 0, p_dryrun boolean DEFAULT false)`* * Procedures for PG11+ to reapply the extra constraint managed by pg_partman (see Constraint Exclusion section in About section above). * Calls `drop_constraints()` and/or `apply_constraint()` in a loop, committing after each object is either dropped or added. This helps to avoid long running transaction and contention when doing this on large partition sets. * Typical useage would be to drop constraints first, edit the data as needed, then apply constraints again. * `p_parent_table` - Parent table of an already created partition set. * `p_drop_constraints` - Drop all constraints managed by pg_partman. Drops constraints on all child tables including current & future. * `p_apply_constraints` - Apply constraints on configured columns to all child tables older than the premake value. * `p_wait` - Wait the given number of seconds after a table has had its constraints dropped or applied before moving on to the next. *`reapply_privileges(p_parent_table text)`* * This function is used to reapply ownership & grants on all child tables based on what the parent table has set. * Privileges that the parent table has will be granted to all child tables and privilges that the parent does not have will be revoked (with CASCADE). * Privilges that are checked for are SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, & TRIGGER. * Be aware that for large partition sets, this can be a very long running operation and is why it was made into a separate function to run independently. Only privileges that are different between the parent & child are applied, but it still has to do system catalog lookups and comparisons for every single child partition and all individual privileges on each. * `p_parent_table` - parent table of the partition set. Must be schema qualified and match a parent table name already configured in `pg_partman`. *`apply_foreign_keys(p_parent_table text, p_child_table text DEFAULT NULL, p_job_id bigint DEFAULT NULL, p_debug boolean DEFAULT false)`* * IMPORTANT: This function is no longer necessary for PG11+ since FK inheritance is automatically managed. * Applies any foreign keys that exist on a parent table in a partition set to all the child tables for PG10 and older. * This function is automatically called whenever a new child table is created, so there is no need to manually run it unless you need to fix an existing child table. * If you need to apply this to an entire partition set, see the **reapply_foreign_keys.py** python script. This will commit after every FK creation to avoid contention. * This function can be used on any table inheritance set, not just ones managed by `pg_partman`. * The p_job_id parameter is optional. It's for internal use and allows job logging to be consolidated into the original job that called this function if applicable. * The p_debug parameter will show you the constraint creation statement that was used. *`stop_sub_partition(p_parent_table text, p_jobmon boolean DEFAULT true) RETURNS boolean`* * By default, if you undo a child table that is also partitioned, it will not stop additional sibling children of the parent partition set from being subpartitioned unless that parent is also undone. To handle this situation where you may not be removing the parent but don't want any additional subpartitioned children, this function can be used. * This function simply deletes the parent_table entry from the part_config_sub table. But this gives a predictable, programatic way to do so and also provides jobmon logging for the operation. ### Destruction Functions *`undo_partition(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval text DEFAULT NULL, p_keep_table boolean DEFAULT true, p_lock_wait numeric DEFAULT 0, p_target_table text DEFAULT NULL, p_ignored_columns text[] DEFAULT NULL, p_drop_cascade boolean DEFAULT false, OUT partitions_undone int, OUT rows_undone bigint) RETURNS record`* * Undo a partition set created by `pg_partman`. This function MOVES the data from the child tables to either the parent table (non-native) or the given target table (native). * If you are trying to un-partition a large amount of data automatically, it is recommended to either use the `undo_partition.py` script to commit data in smaller batches. Or if you're on PG11+, use the `undo_partition_data()` procedure to do the same thing. This will greatly reduce issues caused by long running transactions and data contention. * When this function is run, the **`undo_in_progress`** column in the configuration table is set to true. This causes all partition creation and retention management to stop. * By default, partitions are not DROPPED, they are UNINHERITED/UNATTACHED. This leave previous child tables as empty, independent tables. * For non-native, when this function is run, the trigger on the parent table & the trigger function are immediately dropped (if they still exist). This means any further writes are done to the parent. * Without setting either batch argument manually, each run of the function will move all the data from a single partition into the parent/target. * Once all child tables have been uninherited/dropped, the configuration data is removed from `pg_partman` automatically. * For subpartitioned tables, you may have to start at the lowest level parent table and undo from there then work your way up. * `p_parent_table` - parent table of the partition set. Must be schema qualified and match a parent table name already configured in `pg_partman`. * `p_batch_count` - an optional argument, this sets how many times to move the amount of data equal to the `p_batch_interval` argument (or default partition interval if not set) in a single run of the function. Defaults to 1. * `p_batch_interval` - optional argument. A time or id interval of how much of the data to move. This can be smaller than the partition interval, allowing for very large sized partitions to be broken up into smaller commit batches. Defaults to the configured partition interval if not given or if you give an interval larger than the partition interval. Note that the value must be given as text to this parameter. * `p_keep_table` - an optional argument, setting this to false will cause the old child table to be dropped instead of uninherited/unattached after all of its data has been moved. Note that it takes at least two batches to actually drop a table from the set. * `p_lock_wait` - optional argument, sets how long in seconds to wait for either the table or a row to be unlocked before timing out. Default is to wait forever. * `p_target_table` - A schema-qualified table to move the old partitioned table's data to. Required for undoing a native partition set since data cannot be moved to the parent. Schema can be different from original table. * `p_ignored_columns` - This option allows for filtering out specific columns when moving data from the child tables to the target table. This is generally only required when using columns with a GENERATED ALWAYS value since directly inserting a value would fail when moving the data. Value is a text array of column names. * `p_drop_cascade` - Allow undoing sub-partition sets from parent tables higher in the inheritance tree. Only applies when `p_keep_tables` is set to false. Note this causes all child tables below a sub-partition parent to be dropped when that parent is dropped. * Returns the number of partitions undone and the number of rows moved to the parent table. The partitions undone value returns -1 if a problem is encountered. *`undo_partition_proc(p_parent_table text, p_interval text DEFAULT NULL, p_batch int DEFAULT NULL, p_wait int DEFAULT 1, p_target_table text DEFAULT NULL, p_keep_table boolean DEFAULT true, p_lock_wait int DEFAULT 0, p_lock_wait_tries int DEFAULT 10, p_quiet boolean DEFAULT false, p_ignored_columns text[] DEFAULT NULL, p_drop_cascade boolean DEFAULT false)`* * A procedure that can un-partition data in distinct commit batches to avoid long running transactions and data contention issues. * Only works with PostgreSQL 11+ * Calls either undo_partition() function in a loop committing as needed. * `p_parent_table` - Parent table of an already created partition set. * `p_interval` - Value that is passed on to the undo_partition function as p_batch_interval argument. Use this to set an interval smaller than the partition interval to commit data in smaller batches. Defaults to the partition interval if not given. * `p_batch` - How many times to loop through the value given for --interval. If --interval not set, will use default partition interval and undo at most -b partition(s). Procedure commits at the end of each individual batch. (NOT passed as p_batch_count to undo_partition function). If not set, all data in the entire partition set will be moved in a single run of the procedure. * `p_wait` - Cause the procedure to pause for a given number of seconds between commits (batches) to reduce write load * `p_target_table` - Same as the p_target_table option in the undo_partition() function. * `p_keep_table` - Same as the p_keep_table option in the undo_partition() function. * `p_lock_wait` - Parameter passed directly through to the underlying partition_data_*() function. Number of seconds to wait on rows that may be locked by another transaction. Default is to wait forever (0). * `p_lock_wait_tries` - Parameter to set how many times the procedure will attempt waiting the amount of time set for p_lock_wait. Default is 10 tries. * `p_quiet` - Procedures cannot return values, so by default it emits NOTICE's to show progress. Set this option to silence these notices. * `p_ignored_columns` - This option allows for filtering out specific columns when moving data from the child tables to the target table. This is generally only required when using columns with a GENERATED ALWAYS value since directly inserting a value would fail when moving the data. Value is a text array of column names. * `p_drop_cascade` - Allow undoing sub-partition sets from parent tables higher in the inheritance tree. Only applies when `p_keep_tables` is set to false. Note this causes all child tables below a sub-partition parent to be dropped when that parent is dropped. *`drop_partition_time(p_parent_table text, p_retention interval DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int`* * This function is used to drop child tables from a time-based partition set. By default, the table is just uninherited and not actually dropped. For automatically dropping old tables, it is recommended to use the `run_maintenance()` function with retention configured instead of calling this directly. * `p_parent_table` - the existing parent table of a time-based partition set. MUST be schema qualified, even if in public schema. * `p_retention` - optional parameter to give a retention time interval and immediately drop tables containing only data older than the given interval. If you have a retention value set in the config table already, the function will use that, otherwise this will override it. If not, this parameter is required. See the **About** section above for more information on retention settings. * `p_keep_table` - optional parameter to tell partman whether to keep or drop the table in addition to uninheriting it. TRUE means the table will not actually be dropped; FALSE means the table will be dropped. This function will just use the value configured in **part_config** if not explicitly set. This option is ignored if retention_schema is set. * `p_keep_index` - optional parameter to tell partman whether to keep or drop the indexes of the child table when it is uninherited. TRUE means the indexes will be kept; FALSE means all indexes will be dropped. This function will just use the value configured in **part_config** if not explicitly set. This option is ignored if p_keep_table is set to FALSE or if retention_schema is set. * `p_retention_schema` - optional parameter to tell partman to move a table to another schema instead of dropping it. Set this to the schema you want the table moved to. This function will just use the value configured in **`part_config`** if not explicitly set. If this option is set, the retention `p_keep_table` & `p_keep_index` parameters are ignored. * `p_reference_timestamp` - optional parameter to tell partman to use a different reference timestamp from which to determine which partitions should be affected, default value is `CURRENT_TIMESTAMP`. * Returns the number of partitions affected. *`drop_partition_id(p_parent_table text, p_retention bigint DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int`* * This function is used to drop child tables from an id-based partition set. By default, the table just uninherited and not actually dropped. For automatically dropping old tables, it is recommended to use the `run_maintenance()` function with retention configured instead of calling this directly. * `p_parent_table` - the existing parent table of a time-based partition set. MUST be schema qualified, even if in public schema. * `p_retention` - optional parameter to give a retention integer interval and immediately drop tables containing only data less than the current maximum id value minus the given retention value. If you have a retention value set in the config table already, the function will use that, otherwise this will override it. If not, this parameter is required. See the **About** section above for more information on retention settings. * `p_keep_table` - optional parameter to tell partman whether to keep or drop the table in addition to uninheriting it. TRUE means the table will not actually be dropped; FALSE means the table will be dropped. This function will just use the value configured in **part_config** if not explicitly set. This option is ignored if retention_schema is set. * `p_keep_index` - optional parameter to tell partman whether to keep or drop the indexes of the child table when it is uninherited. TRUE means the indexes will be kept; FALSE means all indexes will be dropped. This function will just use the value configured in **part_config** if not explicitly set. This option is ignored if p_keep_table is set to FALSE or if retention_schema is set. * `p_retention_schema` - optional parameter to tell partman to move a table to another schema instead of dropping it. Set this to the schema you want the table moved to. This function will just use the value configured in **part_config** if not explicitly set. If this option is set, the retention p_keep_table & p_keep_index parameters are ignored. * Returns the number of partitions affected. *`drop_partition_column(p_parent_table text, p_column text) RETURNS void`* * Depending on when a column was added (before or after partitioning was set up), dropping it on the parent may or may not drop it from all children. This function is used to ensure a column is always dropped from the parent and all children in a partition set. * This should only be relevant for non-native partition sets. * Uses the IF EXISTS clause in all drop statements, so it may spit out notices/warnings that a column was not found. You can safely ignore these warnings. It should not spit out any errors. ### Tables **`part_config`** Stores all configuration data for partition sets mananged by the extension. - `parent_table` - Parent table of the partition set - `control` - Column used as the control for partition constraints. Must be a time or integer based column. - `partition_type` - Type of partitioning. Must be one of the types mentioned above in the `create_parent()` info. - `partition_interval` - Text type value that determines the interval for each partition. - Must be a value that can either be cast to the interval or bigint data types. - `constraint_cols` - Array column that lists columns to have additional constraints applied. See **About** section for more information on how this feature works. - `premake` - How many partitions to keep pre-made ahead of the current partition. Default is 4. - `optimize_trigger` - Manages number of partitions which are handled most efficiently by trigger. See `create_parent()` function for more info. Default 4. - This option is ignored for native partitioning. - `optimize_constraint` - Manages which old tables get additional constraints set if configured to do so. See **About** section for more info. Default 30. - `epoch` - Flag the table to be partitioned by time by an integer epoch value instead of a timestamp. See `create_parent()` function for more info. Default 'none'. - `inherit_fk` - Set whether `pg_partman` manages inheriting foreign keys from the parent table to all children. - Defaults to TRUE. Can be set with the `create_parent()` function at creation time as well. - This option is currently ignored for native partitioning. - `retention` - Text type value that determines how old the data in a child partition can be before it is dropped. - Must be a value that can either be cast to the interval (for time-based partitioning) or bigint (for serial partitioning) data types. - Leave this column NULL (the default) to always keep all child partitions. See **About** section for more info. - `retention_schema` - Schema to move tables to as part of the retentions system instead of dropping them. Overrides retention_keep_* options. - `retention_keep_table` - Boolean value to determine whether dropped child tables are kept or actually dropped. - Default is TRUE to keep the table and only uninherit it. Set to FALSE to have the child tables removed from the database completely. - `retention_keep_index` - NOTE: This setting has no affect on native partitioning in PG11+. You cannot drop natively inherited child indexes. - Boolean value to determine whether indexes are dropped for child tables that are uninherited. - Default is TRUE. Set to FALSE to have the child table's indexes dropped when it is uninherited. - `infinite_time_partitions` - By default, new partitions in a time-based set will not be created if new data is not inserted to keep an infinte amount of empty tables from being created. - If you'd still like new partitions to be made despite there being no new data, set this to TRUE. - Defaults to FALSE. - `datetime_string` - For time-based partitioning, this is the datetime format string used when naming child partitions. - `automatic_maintenance` - Flag to set whether maintenance is managed automatically when `run_maintenance()` is called without a table parameter or by the background worker process. - Current valid values are "on" and "off". Default is "on". - When set to off, `run_maintenance()` can still be called on in individual partition set by passing it as a paramter to the function. - `jobmon` - Boolean value to determine whether the `pg_jobmon` extension is used to log/monitor partition maintenance. Defaults to true. - `sub_partition_set_full` - Boolean value to denote that the final partition for a sub-partition set has been created. Allows run_maintenance() to run more efficiently when there are large numbers of subpartition sets. - `undo_in_progress` - Set by the undo_partition functions whenever they are run. If true, this causes all partition creation and retention management by the `run_maintenance()` function to stop. Default is false. - `trigger_exception_handling` - This option is ignored for native partitioning. - Boolean value that can be set to allow the partitioning trigger function to handle any exceptions encountered while writing to this table. Handling it in this case means putting the data into the parent table to try and ensure no data loss in case of errors. Be aware that catching the exception here will override any other exception handling that may be done when writing to this partitioned set (Ex. handling a unique constraint violation to ignore it). Just the existence of this exception block will also increase xid consumption since every row inserted will increment the global xid value. If this is table has a high insert rate, you can quickly reach xid wraparound, so use this carefully. This option is set to false by default to avoid causing unexpected behavior in other exception handling situations. - `upsert` - Please note this option will be going away in the near future once PG11 has been out for a while. - text value of the ON CONFLICT clause to include in the partition trigger Defaults to '' (empty string) which means it's inactive. See *create_parent()* function definition & *About* section for more info. - This option is currently ignored for native partitioning since there is no trigger, but upsert is still able to work in a limited fashion. - `trigger_return_null` - Boolean value that allows controlling the behavior of the partition trigger RETURN. By default this is true and the trigger returns NULL to prevent data going into the parent table as well as the children. However, if you have multiple triggers and are relying on the return to be the NEW column value, this can cause a problem. Setting this config value to false will cause the partition trigger to RETURN NEW. You are then responsible for handling the return value in another trigger appropriately. Otherwise, this will cause new data to go into both the child and parent table of the partition set. - This option is ignored for native partitioning - `template_table` - The schema-qualified name of the table used as a template for applying any inheritance options not handled by the native partitioning options in PG. - `inherit_privileges` - Sets whether to inherit the ownership/privileges of the parent table to all child tables. Defaults to true for non-native & PG10. Defaults to false for native PG11+ and should only be necessary if you need direct access to child tables, by-passing the parent table. - `constraint_valid` - Boolean value that allows the additional constraints that pg_partman can manage for you to be created as NOT VALID. See "Constraint Exclusion" section at the beginning for more details on these constraints. This can allow maintenance to run much quicker on large partition sets since the existing data is not validated before additing the constraint. Newly inserted data is validated, so this is a perfectly safe option to set for data integrity. Note that constraint exclusion WILL NOT work until the constraints are validated. Defaults to true so that constraints are created as VALID. Set to false to set new constraints as NOT VALID. - `subscription_refresh` - Name of a logical replication subscription to refresh when maintenance runs. If the partition set is subscribed to a publication that will be adding/removing tables and you need your partition set to be aware of these changes, you must name that subscription with this option. Otherwise the subscription will never become aware of the new tables added to the publisher unless you are refreshing the subscription via some other means. See the PG documentation for ALTER SUBSCRIPTION for more info on refreshing subscriptions - https://www.postgresql.org/docs/current/sql-altersubscription.html - `drop_cascade_fk` - Allow dropping of foreign key references to be cascaded when a child table is dropped. This option is only allowed with non-native partitioning and is not supported with subpartitioning. **`part_config_sub`** * Stores all configuration data for sub-partitioned sets managed by `pg_partman`. * The **`sub_parent`** column is the parent table of the subpartition set and all other columns govern how that parent's children are subpartitioned. * All other columns work the same exact way as their counterparts in either the **`part_config`** table or as the parameters passed to `create_parent()`. ### Scripts If the extension was installed using *make*, the below script files should have been installed to the PostgreSQL binary directory. *`partition_data.py`* * NOTE: This script is only installed for PostgreSQL 10 and lower. It has been replaced by `partition_data_proc()`. * A python script to make partitioning in committed batches easier. * Script currently does not work with native partitioning. * Calls either partition_data_time() or partition_data_id() depending on the value given for --type. * A commit is done at the end of each --interval and/or fully created partition. * Returns the total number of rows moved to partitions. Automatically stops when parent is empty. * To help avoid heavy load and contention during partitioning, autovacuum is turned off for the parent table and all child tables when this script is run. When partitioning is complete, autovacuum is set back to its default value and the parent table is vacuumed when it is emptied. * `--parent (-p)`: Parent table of an already created partition set. Required. * `--type (-t)`: Type of partitioning. Valid values are "time" and "id". Required. * `--connection (-c)`: Connection string for use by psycopg. Defaults to "host=" (local socket). * `--interval (-i)`: Value that is passed on to the partitioning function as `p_batch_interval` argument. Use this to set an interval smaller than the partition interval to commit data in smaller batches. Defaults to the partition interval if not given. * `--batch (-b)`: How many times to loop through the value given for --interval. If --interval not set, will use default partition interval and make at most -b partition(s). Script commits at the end of each individual batch. (NOT passed as p_batch_count to partitioning function). If not set, all data in the parent table will be partitioned in a single run of the script. * `--wait (-w)`: Cause the script to pause for a given number of seconds between commits (batches). * `--order (-o)`: Allows you to specify the order that data is migrated from the parent to the children, either ascending (ASC) or descending (DESC). Default is ASC. * `--lockwait (-l)`: Have a lock timeout of this many seconds on the data move. If a lock is not obtained, that batch will be tried again. * `--lockwait_tries`: Number of times to allow a lockwait to time out before giving up on the partitioning. Defaults to 10. * `--autovacuum_on`: Turning autovacuum off requires a brief lock to ALTER the table property. Set this option to leave autovacuum on and avoid the lock attempt. * `--quiet (-q)`: Switch setting to stop all output during and after partitioning. * `--version`: Print out the minimum version of `pg_partman` this script is meant to work with. The version of `pg_partman` installed may be greater than this. * `--debug` Show additional debugging output * Examples: ``` Partition all data in a parent table. Commit after each partition is made. python partition_data.py -c "host=localhost dbname=mydb" -p schema.parent_table -t time Partition by id in smaller intervals and pause between them for 5 seconds (assume >100 partition interval) python partition_data.py -p schema.parent_table -t id -i 100 -w 5 Partition by time in smaller intervals for at most 10 partitions in a single run (assume monthly partition interval) python partition_data.py -p schema.parent_table -t time -i "1 week" -b 10 ``` *`undo_partition.py`* * NOTE: This script is only installed for PostgreSQL 10 and lower. It has been replaced by `undo_partition_proc()`. * A python script to make undoing partitions in committed batches easier. * Can also work on any non-native parent/child partition set not managed by `pg_partman` if --type option is not set. * This script calls either undo_partition(), undo_partition_time() or undo_partition_id depending on the value given for --type. * A commit is done at the end of each --interval and/or emptied partition. * Returns the total number of child tables undone. Automatically stops when last child table is undone. * `--parent (-p)`: Parent table of the partition set. Required. * `--type (-t)`: Type of partitioning. Valid values are "time", "id", & "native". Not setting this argument will use undo_partition() and work on any non-native parent/child table set. * `--connection (-c)`: Connection string for use by psycopg. Defaults to "host=" (local socket). * `--interval (-i)`: Value that is passed on to the partitioning function as `p_batch_interval`. Use this to set an interval smaller than the partition interval to commit data in smaller batches. Defaults to the partition interval if not given. * `--batch (-b)`: How many times to loop through the value given for --interval. If --interval not set, will use default partition interval and undo at most -b partition(s). Script commits at the end of each individual batch. (NOT passed as p_batch_count to undo function). If not set, all data will be moved to the parent table in a single run of the script. * `--wait (-w)`: Cause the script to pause for a given number of seconds between commits (batches). * `--droptable (-d)`: Switch setting for whether to drop child tables when they are empty. Leave off option to just uninherit. * `--quiet (-q)`: Switch setting to stop all output during and after partitioning undo. * `--version`: Print out the minimum version of `pg_partman` this script is meant to work with. The version of `pg_partman` installed may be greater than this. * `--debug`: Show additional debugging output *`dump_partition.py`* * A python script to dump out tables contained in the given schema. Uses pg_dump, creates a SHA-512 hash file of the dump file, and then drops the table. * When combined with the retention_schema configuration option, provides a way to reliably dump out tables that would normally just be dropped by the retention system. * Tables are not dropped if pg_dump does not return successfully. * The connection options for psycopg and pg_dump were separated out due to distinct differences in their requirements depending on your database connection configuration. * All dump_* option defaults are the same as they would be for pg_dump if they are not given. * Will work on any given schema, not just the one used to manage `pg_partman` retention. * `--schema (-n)`: The schema that contains the tables that will be dumped. (Required). * `--connection (-c)`: Connection string for use by psycopg. Role used must be able to select from pg_catalog.pg_tables in the relevant database and drop all tables in the given schema. Defaults to "host=" (local socket). Note this is distinct from the parameters sent to pg_dump. * `--output (-o)`: Path to dump file output location. Default is where the script is run from. * `--dump_database (-d)`: Used for pg_dump, same as its --dbname option or final database name parameter. * `--dump_host`: Used for pg_dump, same as its --host option. * `--dump_username`: Used for pg_dump, same as its --username option. * `--dump_port`: Used for pg_dump, same as its --port option. * `--pg_dump_path`: Path to pg_dump binary location. Must set if not in current PATH. * `--Fp`: Dump using pg_dump plain text format. Default is binary custom (-Fc). * `--nohashfile`: Do NOT create a separate file with the SHA-512 hash of the dump. If dump files are very large, hash generation can possibly take a long time. * `--nodrop`: Do NOT drop the tables from the given schema after dumping/hashing. * `--verbose (-v)`: Provide more verbose output. * `--version`: Print out the minimum version of `pg_partman` this script is meant to work with. The version of `pg_partman` installed may be greater than this. *`vacuum_maintenance.py`* * A python script to perform additional VACUUM maintenance on a given partition set. The main purpose of this is to provide an easier means of freezing tuples in older partitions that are no longer written to. This allows autovacuum to skip over them safely without causing transaction id wraparound issues. See the PostgreSQL documentation for more information on this maintenance isssue: http://www.postgresql.org/docs/current/static/routine-vacuuming.html#VACUUM-FOR-WRAPAROUND. * Vacuums all child tables in a given partition set who's age(relfrozenxid) is greater than vacuum_freeze_min_age, including the parent table. * Highly recommend scheduled runs of this script with the --freeze option if you have child tables that never have writes after a certain period of time. * --parent (-p): Parent table of an already created partition set. (Required) * --connection (-c): Connection string for use by psycopg. Defaults to "host=" (local socket). * --freeze (-z): Sets the FREEZE option to the VACUUM command. * --full (-f): Sets the FULL option to the VACUUM command. Note that --freeze is not necessary if you set this. Recommend reviewing --dryrun before running this since it will lock all tables it runs against, possibly including the parent. * --vacuum_freeze_min_age (-a): By default the script obtains this value from the system catalogs. By setting this, you can override the value obtained from the database. Note this does not change the value in the database, only the value this script uses. * --noparent: Normally the parent table is included in the list of tables to vacuum if its age(relfrozenxid) is higher than vacuum_freeze_min_age. Set this to force exclusion of the parent table, even if it meets that criteria. * --dryrun: Show what the script will do without actually running it against the database. Highly recommend reviewing this before running for the first time. * --quiet (-q): Turn off all output. * --debug: Show additional debugging output. *`reapply_indexes.py`* * A python script for reapplying indexes on child tables in a partition set after they are changed on the parent table. * NOTE: This script only works with non-native partitioning and native partitioning in PG10. It does not worth with PG11+ and likely won't be needed since most indexes are now managed automatically in those versions. The only exception may be primary & unique indexes that don't include the partition column. There's unfortunately no easy way to manage index inheritance in PG11+ via this script and will have to be handled manually. * Any indexes that currently exist on the children and match the definition on the parent will be left as is. There is an option to recreate matching as well indexes if desired, as well as the primary key. * Indexes that do not exist on the parent will be dropped from all children. * Commits are done after each index is dropped/created to help prevent long running transactions & locks. * NOTE: New index names are made based off the child table name & columns used, so their naming may differ from the name given on the parent. This is done to allow the tool to account for long or duplicate index names. If an index name would be duplicated, an incremental counter is added on to the end of the index name to allow it to be created. Use the --dryrun option first to see what it will do and which names may cause dupes to be handled like this. * `--parent (-p)`: Parent table of an already created partition set. Required. * `--connection (-c)`: Connection string for use by psycopg. Defaults to "host=" (local socket). * `--concurrent`: Create indexes with the CONCURRENTLY option. Note this does not work on primary keys when --primary is given. * `--drop_concurrent`: Drop indexes concurrently when recreating them (PostgreSQL >= v9.2). Note this does not work on primary keys when --primary is given. * `--recreate_all (-R)`: By default, if an index exists on a child and matches the parent, it will not be touched. Setting this option will force all child indexes to be dropped & recreated. Will obey the --concurrent & --drop_concurrent options if given. Will not recreate primary keys unless --primary option is also given. * `--primary`: By default the primary key is not recreated. Set this option if that is needed. Note this will cause an exclusive lock on the child table for the duration of the recreation. * `--jobs (-j)`: Use the python multiprocessing library to recreate indexes in parallel. Note that this is per table, not per index. Be very careful setting this option if load is a concern on your systems. * `--wait (-w)`: Wait the given number of seconds after indexes have finished being created on a table before moving on to the next. When used with -j, this will set the pause between the batches of parallel jobs instead. * `--dryrun`: Show what the script will do without actually running it against the database. Highly recommend reviewing this before running. Note that if multiple indexes would get the same default name, the duplicated names will show in the dryrun (because the index doesn't exist in the catalog to check for it). When the real thing is run, the duplicated names will be handled as stated in the NOTE above. * `--quiet`: Turn off all output. * `--nonpartman` If the partition set you are running this on is not managed by pg_partman, set this flag otherwise this script may not work. Note that the pg_partman extension is still required to be installed for this to work since it uses certain internal functions. When this is set the order that the tables are reindexed is alphabetical instead of logical. * `--version`: Print out the minimum version of `pg_partman` this script is meant to work with. The version of `pg_partman` installed may be greater than this. *`reapply_constraints.py`* * NOTE: This script is only installed for PostgreSQL 10 and lower. It has been replaced by `reapply_constraints_proc()`. * A python script for redoing constraints on child tables in a given partition set for the columns that are configured in **part_config** table. * Typical useage would be -d mode to drop constraints, edit the data as needed, then -a mode to reapply constraints. * `--parent (-p)`: Parent table of an already created partition set. (Required) * `--connection (-c)`: Connection string for use by psycopg. Defaults to "host=" (local socket). * `--drop_constraints (-d)`: Drop all constraints managed by `pg_partman`. Drops constraints on ALL child tables in the partition set. * `--add_constraints (-a)`: Apply constraints on configured columns to all child tables older than the premake value. * `--jobs (-j)`: Use the python multiprocessing library to recreate indexes in parallel. Value for -j is number of simultaneous jobs to run. Note that this is per table, not per index. Be very careful setting this option if load is a concern on your systems. * `--wait (-w)`: Wait the given number of seconds after a table has had its constraints dropped or applied before moving on to the next. When used with -j, this will set the pause between the batches of parallel jobs instead. * `--dryrun`: Show what the script will do without actually running it against the database. Highly recommend reviewing this before running. * `--quiet (-q)`: Turn off all output. * `--version`: Print out the minimum version of `pg_partman` this script is meant to work with. The version of `pg_partman` installed may be greater than this. *`reapply_foreign_keys.py`* * NOTE: This script is only installed for PostgreSQL 10 and lower. * A python script for redoing the inherited foreign keys for an entire partition set. * Script currently does not work with native partitioning and FKs are handled natively as of PG11+. * All existing foreign keys on all child tables are dropped and the foreign keys that exist on the parent at the time this is run will be applied to all children. * Commits after each foreign key is created to avoid long periods of contention. * `--parent (-p)`: Parent table of an already created partition set. (Required) * `--connection (-c)`: Connection string for use by psycopg. Defaults to "host=" (local socket). * `--quiet (-q)`: Switch setting to stop all output during and after partitioning undo. * `--dryrun`: Show what the script will do without actually running it against the database. Highly recommend reviewing this before running. * `--nonpartman` If the partition set you are running this on is not managed by pg_partman, set this flag. Otherwise internal pg_partman functions are used and this script may not work. When this is set the order that the tables are rekeyed is alphabetical instead of logical. * `--version`: Print out the minimum version of `pg_partman` this script is meant to work with. The version of `pg_partman` installed may be greater than this. * `--debug`: Show additional debugging output *`check_unique_constraints.py`* * Partitioning using inheritance (native still does this internally) has the shortcoming of not allowing a unique constraint to apply to all tables in the entire partition set without causing large performance issues once the partition set begins to grow very large. This script is used to check that all rows in a partition set are unique for the given columns. * Note that on very large partition sets this can be an expensive operation to run that can consume a large chunk of storage space. The amount of storage space required is enough to dump out the entire index's column data as a plaintext file. * If there is a column value that violates the unique constraint, this script will return those column values along with a count of how many of each value there are. Output can also be simplified to a single, total integer value to make it easier to use with monitoring applications. * `--parent (-p)`: Parent table of the partition set to be checked. (Required) * `--column_list (-l)`: Comma separated list of columns that make up the unique constraint to be checked. (Required) * `--connection (-c)`: Connection string for use by psycopg. Defaults to "host=" (local socket). * `--temp (-t)`: Path to a writable folder that can be used for temp working files. Defaults system temp folder. * `--psql`: Full path to psql binary if not in current PATH. * `--simple`: Output a single integer value with the total duplicate count. Use this for monitoring software that requires a simple value to be checked for. * `--quiet (-q)`: Suppress all output unless there is a constraint violation found. * `--version`: Print out the minimum version of `pg_partman` this script is meant to work with. The version of `pg_partman` installed may be greater than this. pg_partman-4.6.0/doc/pg_partman_howto_native.md000066400000000000000000001436231412761706100216740ustar00rootroot00000000000000Example Guide On Setting Up Native Partitioning ======================================== - [Simple Time Based: 1 Partition Per Day](#simple-time-based-1-partition-per-day) - [Simple Serial ID: 1 Partition Per 10 ID Values](#simple-serial-id-1-partition-Per-10-id-values) - [Partitioning an Existing Table](#partitioning-an-existing-table) * [Offline Partitioning](#offline-partitioning) * [Online Partitioning](#online-partitioning) - [Undoing Native Partitioning](#undoing-native-partitioning) This HowTo guide will show you some examples of how to set up simple, single level partitioning. It will also show you several methods to partition data out of a table that has existing data (see [Partitioning an Existing Table](#partitioning-an-existing-table)) and undo the partitioning of an existing partition set (see [Undoing Native Partitioning](#undoing-native-partitioning)). For more details on what each function does and the additional features in this extension, please see the **pg_partman.md** documentation file. The examples in this document assume you are running at least 4.4.1 of pg_partman with PostgreSQL 11 or higher. Note that all examples here are for native partitioning. If you need to use non-native, trigger-based partitioning, please see the [Trigger-based HowTo](pg_partman_howto_triggerbased.md) file. ### Simple Time Based: 1 Partition Per Day For native partitioning, you must start with a parent table that has already been set up to be partitioned in the desired type. Currently pg_partman only supports the RANGE type of partitioning (both for time & id). You cannot turn a non-partitioned table into the parent table of a partitioned set, which can make migration a challenge. This document will show you some techniques for how to manage this later. For now, we will start with a brand new table for this example. Any non-unique indexes can also be added to the parent table in PG11+ and they will automatically be created on all child tables. ``` CREATE SCHEMA IF NOT EXISTS partman_test; CREATE TABLE partman_test.time_taptest_table (col1 int, col2 text default 'stuff', col3 timestamptz NOT NULL DEFAULT now()) PARTITION BY RANGE (col3); CREATE INDEX ON partman_test.time_taptest_table (col3); ``` ``` \d+ partman_test.time_taptest_table Partitioned table "partman_test.time_taptest_table" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+--------------------------+-----------+----------+---------------+----------+--------------+------------- col1 | integer | | | | plain | | col2 | text | | | 'stuff'::text | extended | | col3 | timestamp with time zone | | not null | now() | plain | | Partition key: RANGE (col3) Indexes: "time_taptest_table_col3_idx" btree (col3) Number of partitions: 0 ``` Unique indexes (including primary keys) cannot be created on a natively partitioned parent unless they include the partition key. For time-based partitioning that generally doesn't work out since that would limit only a single timestamp value in each child table. pg_partman helps to manage this by using a template table to manage properties that currently are not supported by native partitioning. Note that this does *not* solve the issue of the constraint *not* being enforced across the entire partition set. See the [main documentation](pg_partman.md#child-table-property-inheritance) to see which properties are managed by the template, depending on the version of PostgreSQL. For this example, we are going to manually create the template table first so that when we run `create_parent()` the initial child tables that are created will have a primary key. If you do not supply a template table to pg_partman, it will create one for you in the schema that you installed the extension to. However properties you add to that template are only then applied to newly created child tables after that point. You will have to retroactively apply those properties manually to any child tables that already existed. ``` CREATE TABLE partman_test.time_taptest_table_template (LIKE partman_test.time_taptest_table); ALTER TABLE partman_test.time_taptest_table_template ADD PRIMARY KEY (col1); ``` ``` \d partman_test.time_taptest_table_template Table "partman_test.time_taptest_table_template" Column | Type | Collation | Nullable | Default --------+--------------------------+-----------+----------+--------- col1 | integer | | not null | col2 | text | | | col3 | timestamp with time zone | | not null | Indexes: "time_taptest_table_template_pkey" PRIMARY KEY, btree (col1) ``` ``` SELECT partman.create_parent('partman_test.time_taptest_table', 'col3', 'native', 'daily', p_template_table := 'partman_test.time_taptest_table_template'); create_parent --------------- t (1 row) ``` ``` \d+ partman_test.time_taptest_table Partitioned table "partman_test.time_taptest_table" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+--------------------------+-----------+----------+---------------+----------+--------------+------------- col1 | integer | | | | plain | | col2 | text | | | 'stuff'::text | extended | | col3 | timestamp with time zone | | not null | now() | plain | | Partition key: RANGE (col3) Indexes: "time_taptest_table_col3_idx" btree (col3) Partitions: partman_test.time_taptest_table_p2020_10_26 FOR VALUES FROM ('2020-10-26 00:00:00-04') TO ('2020-10-27 00:00:00-04'), partman_test.time_taptest_table_p2020_10_27 FOR VALUES FROM ('2020-10-27 00:00:00-04') TO ('2020-10-28 00:00:00-04'), partman_test.time_taptest_table_p2020_10_28 FOR VALUES FROM ('2020-10-28 00:00:00-04') TO ('2020-10-29 00:00:00-04'), partman_test.time_taptest_table_p2020_10_29 FOR VALUES FROM ('2020-10-29 00:00:00-04') TO ('2020-10-30 00:00:00-04'), partman_test.time_taptest_table_p2020_10_30 FOR VALUES FROM ('2020-10-30 00:00:00-04') TO ('2020-10-31 00:00:00-04'), partman_test.time_taptest_table_p2020_10_31 FOR VALUES FROM ('2020-10-31 00:00:00-04') TO ('2020-11-01 00:00:00-04'), partman_test.time_taptest_table_p2020_11_01 FOR VALUES FROM ('2020-11-01 00:00:00-04') TO ('2020-11-02 00:00:00-05'), partman_test.time_taptest_table_p2020_11_02 FOR VALUES FROM ('2020-11-02 00:00:00-05') TO ('2020-11-03 00:00:00-05'), partman_test.time_taptest_table_p2020_11_03 FOR VALUES FROM ('2020-11-03 00:00:00-05') TO ('2020-11-04 00:00:00-05'), partman_test.time_taptest_table_default DEFAULT ``` ``` \d+ partman_test.time_taptest_table_p2020_10_26 Table "partman_test.time_taptest_table_p2020_10_26" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+--------------------------+-----------+----------+---------------+----------+--------------+------------- col1 | integer | | not null | | plain | | col2 | text | | | 'stuff'::text | extended | | col3 | timestamp with time zone | | not null | now() | plain | | Partition of: partman_test.time_taptest_table FOR VALUES FROM ('2020-10-26 00:00:00-04') TO ('2020-10-27 00:00:00-04') Partition constraint: ((col3 IS NOT NULL) AND (col3 >= '2020-10-26 00:00:00-04'::timestamp with time zone) AND (col3 < '2020-10-27 00:00:00-04'::timestamp with time zone)) Indexes: "time_taptest_table_p2020_10_26_pkey" PRIMARY KEY, btree (col1) "time_taptest_table_p2020_10_26_col3_idx" btree (col3) Access method: heap ``` ### Simple Serial ID: 1 Partition Per 10 ID Values For this use-case, the template table is not created manually before calling `create_parent()`. So it shows that if a primary/unique key is added later, it does not apply to the currently existing child tables. That will have to be done manually. ``` CREATE TABLE partman_test.id_taptest_table ( col1 bigint , col2 text not null , col3 timestamptz DEFAULT now() , col4 text) PARTITION BY RANGE (col1); CREATE INDEX ON partman_test.id_taptest_table (col1); ``` ``` \d+ partman_test.id_taptest_table Partitioned table "partman_test.id_taptest_table" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+--------------------------+-----------+----------+---------+----------+--------------+------------- col1 | bigint | | | | plain | | col2 | text | | not null | | extended | | col3 | timestamp with time zone | | | now() | plain | | col4 | text | | | | extended | | Partition key: RANGE (col1) Indexes: "id_taptest_table_col1_idx" btree (col1) Number of partitions: 0 ``` ``` SELECT partman.create_parent('partman_test.id_taptest_table', 'col1', 'native', '10'); create_parent --------------- t (1 row) ``` ``` \d+ partman_test.id_taptest_table Partitioned table "partman_test.id_taptest_table" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+--------------------------+-----------+----------+---------+----------+--------------+------------- col1 | bigint | | | | plain | | col2 | text | | not null | | extended | | col3 | timestamp with time zone | | | now() | plain | | col4 | text | | | | extended | | Partition key: RANGE (col1) Indexes: "id_taptest_table_col1_idx" btree (col1) Partitions: partman_test.id_taptest_table_p0 FOR VALUES FROM ('0') TO ('10'), partman_test.id_taptest_table_p10 FOR VALUES FROM ('10') TO ('20'), partman_test.id_taptest_table_p20 FOR VALUES FROM ('20') TO ('30'), partman_test.id_taptest_table_p30 FOR VALUES FROM ('30') TO ('40'), partman_test.id_taptest_table_p40 FOR VALUES FROM ('40') TO ('50'), partman_test.id_taptest_table_default DEFAULT ``` You can see the name of the template table by looking in the pg_partman configration for that parent table ``` select template_table from partman.part_config where parent_table = 'partman_test.id_taptest_table'; template_table ------------------------------------------------ partman.template_partman_test_id_taptest_table ``` ``` ALTER TABLE partman.template_partman_test_id_taptest_table ADD PRIMARY KEY (col2); ``` Now if we add some data and run maintenance again to create new child tables... ``` INSERT INTO partman_test.id_taptest_table (col1, col2) VALUES (generate_series(1,20), generate_series(1,20)::text||'stuff'::text); CALL partman.run_maintenance_proc(); \d+ partman_test.id_taptest_table Partitioned table "partman_test.id_taptest_table" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+--------------------------+-----------+----------+---------+----------+--------------+------------- col1 | bigint | | | | plain | | col2 | text | | not null | | extended | | col3 | timestamp with time zone | | | now() | plain | | col4 | text | | | | extended | | Partition key: RANGE (col1) Indexes: "id_taptest_table_col1_idx" btree (col1) Partitions: partman_test.id_taptest_table_p0 FOR VALUES FROM ('0') TO ('10'), partman_test.id_taptest_table_p10 FOR VALUES FROM ('10') TO ('20'), partman_test.id_taptest_table_p20 FOR VALUES FROM ('20') TO ('30'), partman_test.id_taptest_table_p30 FOR VALUES FROM ('30') TO ('40'), partman_test.id_taptest_table_p40 FOR VALUES FROM ('40') TO ('50'), partman_test.id_taptest_table_p50 FOR VALUES FROM ('50') TO ('60'), partman_test.id_taptest_table_p60 FOR VALUES FROM ('60') TO ('70'), partman_test.id_taptest_table_default DEFAULT ``` ... you'll see that only the new child tables (p50 & p60) have that primary key and the original tables do not (p40 and earlier). ``` \d partman_test.id_taptest_table_p40 Table "partman_test.id_taptest_table_p40" Column | Type | Collation | Nullable | Default --------+--------------------------+-----------+----------+--------- col1 | bigint | | | col2 | text | | not null | col3 | timestamp with time zone | | | now() col4 | text | | | Partition of: partman_test.id_taptest_table FOR VALUES FROM ('40') TO ('50') Indexes: "id_taptest_table_p40_col1_idx" btree (col1) \d partman_test.id_taptest_table_p50 Table "partman_test.id_taptest_table_p50" Column | Type | Collation | Nullable | Default --------+--------------------------+-----------+----------+--------- col1 | bigint | | | col2 | text | | not null | col3 | timestamp with time zone | | | now() col4 | text | | | Partition of: partman_test.id_taptest_table FOR VALUES FROM ('50') TO ('60') Indexes: "id_taptest_table_p50_pkey" PRIMARY KEY, btree (col2) "id_taptest_table_p50_col1_idx" btree (col1) \d partman_test.id_taptest_table_p60 Table "partman_test.id_taptest_table_p60" Column | Type | Collation | Nullable | Default --------+--------------------------+-----------+----------+--------- col1 | bigint | | | col2 | text | | not null | col3 | timestamp with time zone | | | now() col4 | text | | | Partition of: partman_test.id_taptest_table FOR VALUES FROM ('60') TO ('70') Indexes: "id_taptest_table_p60_pkey" PRIMARY KEY, btree (col2) "id_taptest_table_p60_col1_idx" btree (col1) ``` Add them manually: ``` ALTER TABLE partman_test.id_taptest_table_p0 ADD PRIMARY KEY (col2); ALTER TABLE partman_test.id_taptest_table_p10 ADD PRIMARY KEY (col2); ALTER TABLE partman_test.id_taptest_table_p20 ADD PRIMARY KEY (col2); ALTER TABLE partman_test.id_taptest_table_p30 ADD PRIMARY KEY (col2); ALTER TABLE partman_test.id_taptest_table_p40 ADD PRIMARY KEY (col2); ``` ### Partitioning an Existing Table Partitioning an existing table with native partitioning is not as straight forward as methods that could be employed with the old trigger-based methods. As stated above, you cannot turn an already existing table into the parent table of a native partition set. The parent of a native partitioned table must be declared partitioned at the time of its creation. However, there are still methods to take an existing table and partition it natively. Two of those are presented below. #### Offline Partitioning This method is being labelled "offline" because, during points in this process, the data is not accessible to both the new and old table from a single object. The data is moved from the original table to a brand new table. The advantage of this method is that you can move your data in much smaller batches than even the target partition size, which can be a huge efficiency advantage for very large partition sets (you can commit in batches of several thousand vs several million). There are also less object renaming steps as we'll see in the online partitioning method next. *IMPORTANT NOTE REGUARDING FOREIGN KEYS* Taking the partitioned table offline is the only method that realistically works when you have foreign keys TO the table being partitioned. Since a brand new table must be created no matter what, the foreign key must also be recreated, so an outage involving all tables that are part of the FK relationship must be taken. A shorter outage may be possible with the online method below, but if you have to take an outage, this offline method is easier. Here is the original table with some generated data: ``` CREATE TABLE public.original_table ( col1 bigint not null , col2 text not null , col3 timestamptz DEFAULT now() , col4 text); CREATE INDEX ON public.original_table (col1); INSERT INTO public.original_table (col1, col2, col3, col4) VALUES (generate_series(1,100000), 'stuff'||generate_series(1,100000), now(), 'stuff'); ``` First, the original table should be renamed so the partitioned table can be made with the original table's name. This makes it so that, when the child tables are created, they have names that are associated with the original table name. ``` ALTER TABLE public.original_table RENAME to old_nonpartitioned_table; ``` We'll use the serial partitioning example from above. The initial setup is exactly the same, creating a brand new table that will be the parent and then running `create_parent()` on it. We'll make the interval slightly larger this time. Also, make sure you've applied all the same original properties to this new table that the old table had: privileges, constraints, defaults, indexes, etc. Privileges are especially important to make sure they match so that all users of the table will continue to work after the conversion. Note that primary keys/unique indexes cannot be applied to a partitioned parent unless the partition key is part of it. In this case that would work, however it's likely not the intention since that would mean only one row per value is allowed and that would mean only 10,000 rows could ever exist in each child table. Partitioning is definitely not needed then. The next example of online partitioning will show how to handle when you need a primary key for a column that is not part of the partition key. ``` CREATE TABLE public.original_table ( col1 bigint not null , col2 text not null , col3 timestamptz DEFAULT now() , col4 text) PARTITION BY RANGE (col1); CREATE INDEX ON public.original_table (col1); SELECT partman.create_parent('public.original_table', 'col1', 'native', '10000'); ``` ``` \d+ original_table; Partitioned table "public.original_table" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+--------------------------+-----------+----------+---------+----------+--------------+------------- col1 | bigint | | not null | | plain | | col2 | text | | not null | | extended | | col3 | timestamp with time zone | | | now() | plain | | col4 | text | | | | extended | | Partition key: RANGE (col1) Indexes: "original_table_col1_idx1" btree (col1) Partitions: original_table_p0 FOR VALUES FROM ('0') TO ('10000'), original_table_p10000 FOR VALUES FROM ('10000') TO ('20000'), original_table_p20000 FOR VALUES FROM ('20000') TO ('30000'), original_table_p30000 FOR VALUES FROM ('30000') TO ('40000'), original_table_p40000 FOR VALUES FROM ('40000') TO ('50000'), original_table_default DEFAULT ``` If you happened to be using IDENTITY columns, or you created a new sequence for the new partitioned table, you'll want to get the value of those old sequences and reset the new sequences to start with those old values. Some tips for doing that are covered in the Online Partitioning section below. If you just re-used the same sequence on the new partitioned table, you should be fine. Now we can use the `partition_data_proc()` procedure to migrate our data from the old table to the new table. And we're going to do it in 1,000 row increments vs the 10,000 interval that the partition set has. The batch value is used to tell it how many times to run through the given interval; the default value of 1 only makes a single child table. Since we want to partition all of the data, just give it a number equal to or greater than the expected child table count. This procedure has an option where you can tell it the source of the data, which is how we're going to migrate the data from the old table. Without setting this option, it attempts to clean the data out of the DEFAULT partition (which we'll see an example of next). ``` CALL partman.partition_data_proc('public.original_table', p_interval := '1000', p_batch := 200, p_source_table := 'public.old_nonpartitioned_table'); NOTICE: Batch: 1, Rows moved: 1000 NOTICE: Batch: 2, Rows moved: 1000 NOTICE: Batch: 3, Rows moved: 1000 NOTICE: Batch: 4, Rows moved: 1000 NOTICE: Batch: 5, Rows moved: 1000 NOTICE: Batch: 6, Rows moved: 1000 NOTICE: Batch: 7, Rows moved: 1000 NOTICE: Batch: 8, Rows moved: 1000 NOTICE: Batch: 9, Rows moved: 1000 NOTICE: Batch: 10, Rows moved: 999 NOTICE: Batch: 11, Rows moved: 1000 NOTICE: Batch: 12, Rows moved: 1000 [...] NOTICE: Batch: 100, Rows moved: 1000 NOTICE: Batch: 101, Rows moved: 1 NOTICE: Total rows moved: 100000 NOTICE: Ensure to VACUUM ANALYZE the parent (and source table if used) after partitioning data CALL Time: 103206.205 ms (01:43.206) VACUUM ANALYZE public.original_table; VACUUM Time: 352.973 ms ``` Again, doing the commits in smaller batches like this can avoid transactions with huge row counts and long runtimes when you're partitioning a table that may have billions of rows. It's always advisable to avoid long running transactions to allow PostgreSQL's autovacuum process to work efficiently for the rest of the database. Using the `partition_data_proc()` PROCEDURE vs the `partition_data_id()` FUNCTION allows those commit batches. Functions in PostgreSQL always run entirely in a single transaction, even if you may tell it to do things in batches inside the function. Now if we check our original table, it is empty ``` SELECT count(*) FROM old_nonpartitioned_table ; count ------- 0 (1 row) ``` And the new, partitioned table with the original name has all the data and child tables created ``` SELECT count(*) FROM original_table; count -------- 100000 (1 row) \d+ public.original_table Partitioned table "public.original_table" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+--------------------------+-----------+----------+---------+----------+--------------+------------- col1 | bigint | | not null | | plain | | col2 | text | | not null | | extended | | col3 | timestamp with time zone | | | now() | plain | | col4 | text | | | | extended | | Partition key: RANGE (col1) Indexes: "original_table_col1_idx1" btree (col1) Partitions: original_table_p0 FOR VALUES FROM ('0') TO ('10000'), original_table_p10000 FOR VALUES FROM ('10000') TO ('20000'), original_table_p100000 FOR VALUES FROM ('100000') TO ('110000'), original_table_p20000 FOR VALUES FROM ('20000') TO ('30000'), original_table_p30000 FOR VALUES FROM ('30000') TO ('40000'), original_table_p40000 FOR VALUES FROM ('40000') TO ('50000'), original_table_p50000 FOR VALUES FROM ('50000') TO ('60000'), original_table_p60000 FOR VALUES FROM ('60000') TO ('70000'), original_table_p70000 FOR VALUES FROM ('70000') TO ('80000'), original_table_p80000 FOR VALUES FROM ('80000') TO ('90000'), original_table_p90000 FOR VALUES FROM ('90000') TO ('100000'), original_table_default DEFAULT SELECT count(*) FROM original_table_p10000; count ------- 10000 (1 row) ``` Now you should be able to start using your table the same as you were before! #### Online Partitioning Sometimes it is not possible to take the table offline for an extended period of time to migrate it to a partitioned table. Below is one method to allow this to be done online. It's not as flexible as the offline method, but should allow a very minimal downtime and be mostly transparent to the end users of the table. As mentioned above, these methods do not account for there being foreign keys TO the original table. You can create foreign keys FROM the original table on the new patitioned table and things should work as expected. However, if you have foreign keys coming in to the table, I'm not aware of any migration method that does not require an outage to drop the original foreign keys and recreate them against the new partitioned table. This will be a daily, time-based partition set with an IDENTITY sequence as the primary key ``` CREATE TABLE public.original_table ( col1 bigint not null PRIMARY KEY GENERATED ALWAYS AS IDENTITY , col2 text not null , col3 timestamptz DEFAULT now() not null , col4 text); CREATE INDEX CONCURRENTLY ON public.original_table (col3); INSERT INTO public.original_table (col2, col3, col4) VALUES ('stuff', generate_series(now() - '1 week'::interval, now(), '5 minutes'::interval), 'stuff'); ``` The process is still initially the same as the offline method since you cannot turn an existing table into the parent table of a partition set. However it is critical that all constraints, privileges, defaults and any other properties be applied to the new parent table before you move on to the next step of swapping the table names around. ``` CREATE TABLE public.new_partitioned_table ( col1 bigint not null GENERATED BY DEFAULT AS IDENTITY , col2 text not null , col3 timestamptz DEFAULT now() not null , col4 text) PARTITION BY RANGE (col3); CREATE INDEX ON public.new_partitioned_table (col3); ``` You'll notice I did not set "col1" as a primary key here. That is because we cannot. ``` CREATE TABLE public.new_partitioned_table ( col1 bigint not null PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY , col2 text not null , col3 timestamptz DEFAULT now() not null , col4 text) PARTITION BY RANGE (col3); ERROR: unique constraint on partitioned table must include all partitioning columns DETAIL: PRIMARY KEY constraint on table "new_partitioned_table" lacks column "col3" which is part of the partition key. ``` pg_partman does have a mechanism to still apply primary/unique keys that are not part of the partition column. Just be aware that they are NOT enforced across the entire partition set; only for the individual partition. This is done with a template table. And to ensure the keys are applied when the initial child tables are created, that template table must be pre-created and its name supplied to the `create_parent()` call. We're going to use the original table as the basis and give a name similar to that so it makes sense after the name swapping later. Another important note is that we changed the IDENTITY column from GENERATED ALWAYS to GENERATED BY DEFAULT. This is because we need to move existing values for that identity column into place. ALWAYS generally prevents manually entered values. ``` CREATE TABLE public.original_table_template (LIKE public.original_table); ALTER TABLE public.original_table_template ADD PRIMARY KEY (col1); ``` If you do not pre-create a template table, pg_partman will always create one for you in the same schema that the extension was installed into. You can see its name by looking at the `template_table` column in the `part_config` table. However, if you add the index onto that template table after the `create_parent()` call, the already existing child tables will not have that index applied and you will have to go back and do that manually. However, any new child tables create after that will have the index. The tricky part here is that we cannot yet have any child tables in the partition set that match data that currently exists in the original table. This is because we're going to be adding the old table as the DEFAULT table to our new partition table. If the DEFAULT table contains any data that matches a current child table's constraints, PostgreSQL will not allow that table to be added. So, with the below `create_parent()` call, we're going to start the partition set well ahead of the data we inserted. In your case you will have to look at your current data set and pick a value well ahead of the current working set of data that may get inserted before you are able to run the table name swap process below. We're also setting the premake value to a low value to avoid having to rename too many child tables later. We'll increase premake back up to the default later (or you can set it to whatever you require). ``` select min(col3), max(col3) from original_table; min | max -------------------------------+------------------------------- 2020-12-02 19:04:08.559646-05 | 2020-12-09 19:04:08.559646-05 (1 row) SELECT partman.create_parent('public.new_partitioned_table', 'col3', 'native', 'daily', p_template_table:= 'public.original_table_template', p_premake := 1, p_start_partition := (CURRENT_TIMESTAMP+'2 days'::interval)::text); ``` The next step is to drop the DEFAULT partition that pg_partman creates for you. ``` DROP TABLE public.new_partitioned_table_default; ``` The state of the new partitioned table should now look something like this. The current date for when this HowTo was written is given for reference: ``` SELECT CURRENT_TIMESTAMP; current_timestamp ------------------------------- 2020-12-09 19:05:15.358796-05 \d+ new_partitioned_table; Partitioned table "public.new_partitioned_table" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+--------------------------+-----------+----------+----------------------------------+----------+--------------+------------- col1 | bigint | | not null | generated by default as identity | plain | | col2 | text | | not null | | extended | | col3 | timestamp with time zone | | not null | now() | plain | | col4 | text | | | | extended | | Partition key: RANGE (col3) Indexes: "new_partitioned_table_col3_idx" btree (col3) Partitions: new_partitioned_table_p2020_12_11 FOR VALUES FROM ('2020-12-11 00:00:00-05') TO ('2020-12-12 00:00:00-05') ``` You also need to update the `part_config` table to have the original table name. You can also update the template table if you didn't manually create one yourself, just be sure to both rename the table and update the `part_config` table as well. We'll reset the premake to the default value here as well. ``` UPDATE partman.part_config SET parent_table = 'public.original_table', premake = 4 WHERE parent_table = 'public.new_partitioned_table'; UPDATE 1 ``` The next step, which is actually multiple steps in a single transaction, is the only outage of any significance that needs to be anticipated. 1. BEGIN transaction 1. Take an exclusive lock on the original table and new table to ensure no gaps exist for data to be misrouted 1. If using an IDENTITY column, get the original last value 1. Rename the original table to the DEFAULT table name for the partition set 1. If using an IDENTITY column, DROP the IDENTITY from the old table 1. Rename the new table to the original table's name and rename child tables & sequence to match. 1. If using an IDENTITY column, reset the new table's identity to the latest value so any new inserts pick up where old table's sequence left off. 1. Add original table as the DEFAULT for the partition set 1. COMMIT transaction If you are using an IDENTITY column, it is important to get its last value while the original table is locked and BEFORE you drop the old identity. Then use that returned value in the statement to RESET the IDENTITY column in the new table. A query to obtain this is provided in the SQL statements below. If at any point there is a problem with one of these mini-steps, just perform a ROLLBACK and you should return to the previous state and allow your original table to work as it was before. ``` BEGIN; LOCK TABLE public.original_table IN ACCESS EXCLUSIVE MODE; LOCK TABLE public.new_partitioned_table IN ACCESS EXCLUSIVE MODE; SELECT max(col1) FROM public.original_table; ALTER TABLE public.original_table RENAME TO original_table_default; -- IF using an IDENTITY column ALTER TABLE public.original_table_default ALTER col1 DROP IDENTITY; ALTER TABLE public.new_partitioned_table RENAME TO original_table; ALTER TABLE public.new_partitioned_table_p2020_12_11 RENAME TO original_table_p2020_12_11; -- IF using an IDENTITY column ALTER SEQUENCE public.new_partitioned_table_col1_seq RENAME TO original_table_col1_seq; -- IF using an IDENTITY column ALTER TABLE public.original_table ALTER col1 RESTART WITH <<>>; ALTER TABLE public.original_table ATTACH PARTITION public.original_table_default DEFAULT; ``` ``` COMMIT; or ROLLBACK; ``` Once COMMIT is run, the new partitioned table should now take over from the original non-partitioned table. And as long as all the properties have been applied to the new table, it should be working without any issues. Any new data coming in should either be going to the relevant child table, or if it doesn't happen to exist yet, it should go to the DEFAULT. The latter is not an issue since... The next step is to partition the data out of the default. You DO NOT want to leave data in the default partition set for any length of time and especially leave any significant amount of data. If you look at the constraint that exists on a partitioned default, it is basically the anti-constraint of all other child tables. And when a new child table is added, PostgreSQL manages updating that default constraint as needed. But it must check if any data that should belong in that new child table already exists in the default. If it finds any, it will fail. But more importantly, it has to check EVERY entry in the default which can take quite a long time even with an index if there are billions of rows. During this check, there is an exclusive lock on the entire partition set. The `partition_data_proc()` can handle moving the data out of the default. However, it cannot move data in any interval smaller than the partition interval when moving data out of the DEFAULT. This is related to what was just mentioned: You cannot add a child table to a partition set if that new child table's constraint covers data that already exists in the default. pg_partman handles this by first moving all the data for a given child table out to a temporary table, then creating the child table, and then moving the data from the temp table into the new child table. Since we're moving data out of the DEFAULT and we cannot use a smaller interval, the only parameter that we need to pass is a batch size. The default batch size of 1 would only make a single child table then stop. If you want to move all the data in a single call, just pass a value large enough to cover the expected number of child tables. However, with a live table and LOTS rows, this could potentially generate A LOT of WAL files, especially since this method doubles the number of writes vs the offline method (default -> temp -> child table). So if keeping control of your disk usage is a concern, just give a smaller batch value and then give PostgreSQL some time to run through a few CHECKPOINTs and clean up its own WAL before moving on to the next batch. ``` CALL partman.partition_data_proc('public.original_table', p_batch := 200); NOTICE: Batch: 1, Rows moved: 60 NOTICE: Batch: 2, Rows moved: 288 NOTICE: Batch: 3, Rows moved: 288 NOTICE: Batch: 4, Rows moved: 288 NOTICE: Batch: 5, Rows moved: 288 NOTICE: Batch: 6, Rows moved: 288 NOTICE: Batch: 7, Rows moved: 288 NOTICE: Batch: 8, Rows moved: 229 NOTICE: Total rows moved: 2017 NOTICE: Ensure to VACUUM ANALYZE the parent (and source table if used) after partitioning data CALL Time: 8432.725 ms (00:08.433) VACUUM ANALYZE original_table; VACUUM Time: 60.690 ms ``` If you were using an IDENTITY column with GENERATED ALWAYS before, you'll want to change the identity on the partitioned table back to that from the current setting of BY DEFAULT ``` ALTER TABLE public.original_table ALTER col1 SET GENERATED ALWAYS; ``` Now, double-check that there are no child table names that don't conform to the pattern that pg_partman uses for the new child tables. pg_partman expects a specific format for the child tables it manages, so if they don't all match, maintenance may not work as expected ``` \d+ original_table; Partitioned table "public.original_table" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+--------------------------+-----------+----------+----------------------------------+----------+--------------+------------- col1 | bigint | | not null | generated by default as identity | plain | | col2 | text | | not null | | extended | | col3 | timestamp with time zone | | not null | now() | plain | | col4 | text | | | | extended | | Partition key: RANGE (col3) Indexes: "new_partitioned_table_col3_idx" btree (col3) Partitions: original_table_p2020_12_02 FOR VALUES FROM ('2020-12-02 00:00:00-05') TO ('2020-12-03 00:00:00-05'), original_table_p2020_12_03 FOR VALUES FROM ('2020-12-03 00:00:00-05') TO ('2020-12-04 00:00:00-05'), original_table_p2020_12_04 FOR VALUES FROM ('2020-12-04 00:00:00-05') TO ('2020-12-05 00:00:00-05'), original_table_p2020_12_05 FOR VALUES FROM ('2020-12-05 00:00:00-05') TO ('2020-12-06 00:00:00-05'), original_table_p2020_12_06 FOR VALUES FROM ('2020-12-06 00:00:00-05') TO ('2020-12-07 00:00:00-05'), original_table_p2020_12_07 FOR VALUES FROM ('2020-12-07 00:00:00-05') TO ('2020-12-08 00:00:00-05'), original_table_p2020_12_08 FOR VALUES FROM ('2020-12-08 00:00:00-05') TO ('2020-12-09 00:00:00-05'), original_table_p2020_12_09 FOR VALUES FROM ('2020-12-09 00:00:00-05') TO ('2020-12-10 00:00:00-05'), original_table_p2020_12_11 FOR VALUES FROM ('2020-12-11 00:00:00-05') TO ('2020-12-12 00:00:00-05'), ``` And now to ensure any new data coming in is going to proper child tables and not the default, run maintenance on the new partitioned table to ensure the current premake partitions are created ``` SELECT partman.run_maintenance('public.original_table'); \d+ original_table; Partitioned table "public.original_table" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+--------------------------+-----------+----------+----------------------------------+----------+--------------+------------- col1 | bigint | | not null | generated by default as identity | plain | | col2 | text | | not null | | extended | | col3 | timestamp with time zone | | not null | now() | plain | | col4 | text | | | | extended | | Partition key: RANGE (col3) Indexes: "new_partitioned_table_col3_idx" btree (col3) Partitions: original_table_p2020_12_02 FOR VALUES FROM ('2020-12-02 00:00:00-05') TO ('2020-12-03 00:00:00-05'), original_table_p2020_12_03 FOR VALUES FROM ('2020-12-03 00:00:00-05') TO ('2020-12-04 00:00:00-05'), original_table_p2020_12_04 FOR VALUES FROM ('2020-12-04 00:00:00-05') TO ('2020-12-05 00:00:00-05'), original_table_p2020_12_05 FOR VALUES FROM ('2020-12-05 00:00:00-05') TO ('2020-12-06 00:00:00-05'), original_table_p2020_12_06 FOR VALUES FROM ('2020-12-06 00:00:00-05') TO ('2020-12-07 00:00:00-05'), original_table_p2020_12_07 FOR VALUES FROM ('2020-12-07 00:00:00-05') TO ('2020-12-08 00:00:00-05'), original_table_p2020_12_08 FOR VALUES FROM ('2020-12-08 00:00:00-05') TO ('2020-12-09 00:00:00-05'), original_table_p2020_12_09 FOR VALUES FROM ('2020-12-09 00:00:00-05') TO ('2020-12-10 00:00:00-05'), original_table_p2020_12_11 FOR VALUES FROM ('2020-12-11 00:00:00-05') TO ('2020-12-12 00:00:00-05'), original_table_p2020_12_12 FOR VALUES FROM ('2020-12-12 00:00:00-05') TO ('2020-12-13 00:00:00-05'), original_table_p2020_12_13 FOR VALUES FROM ('2020-12-13 00:00:00-05') TO ('2020-12-14 00:00:00-05'), original_table_default DEFAULT ``` Before this, depending on the child tables that were generated and the new data coming in, there may have been some data that still went to the default. You can check for that with a function that comes with pg_partman: ``` SELECT * FROM partman.check_default(p_exact_count := true); ``` If you don't pass "true" to the function, it just returns a 1 or 0 to indicate if any data exists in any default. This is convenient for monitoring situations and it can also be quicker since it stops checking as soon as it finds data in any child table. However, in this case we want to see exactly what our situation is, so passing true will give us an exact count of how many rows are left in the default. You'll also notice that there is a child table missing in the set above (Dec 10, 2020). This is because we set the partition table to start 2 days ahead and we didn't have any data for that date in the original table. You can fix this one of two ways: 1. Wait for data for that time period to be inserted and once you're sure that interval is done, partition the data out of the DEFAULT the same way we did before. 1. Run the `partition_gap_fill()` function to fill any gaps immediately: ``` SELECT * FROM partman.partition_gap_fill('public.original_table'); partition_gap_fill -------------------- 1 (1 row) \d+ original_table; Partitioned table "public.original_table" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+--------------------------+-----------+----------+----------------------------------+----------+--------------+------------- col1 | bigint | | not null | generated by default as identity | plain | | col2 | text | | not null | | extended | | col3 | timestamp with time zone | | not null | now() | plain | | col4 | text | | | | extended | | Partition key: RANGE (col3) Indexes: "new_partitioned_table_col3_idx" btree (col3) Partitions: original_table_p2020_12_02 FOR VALUES FROM ('2020-12-02 00:00:00-05') TO ('2020-12-03 00:00:00-05'), original_table_p2020_12_03 FOR VALUES FROM ('2020-12-03 00:00:00-05') TO ('2020-12-04 00:00:00-05'), original_table_p2020_12_04 FOR VALUES FROM ('2020-12-04 00:00:00-05') TO ('2020-12-05 00:00:00-05'), original_table_p2020_12_05 FOR VALUES FROM ('2020-12-05 00:00:00-05') TO ('2020-12-06 00:00:00-05'), original_table_p2020_12_06 FOR VALUES FROM ('2020-12-06 00:00:00-05') TO ('2020-12-07 00:00:00-05'), original_table_p2020_12_07 FOR VALUES FROM ('2020-12-07 00:00:00-05') TO ('2020-12-08 00:00:00-05'), original_table_p2020_12_08 FOR VALUES FROM ('2020-12-08 00:00:00-05') TO ('2020-12-09 00:00:00-05'), original_table_p2020_12_09 FOR VALUES FROM ('2020-12-09 00:00:00-05') TO ('2020-12-10 00:00:00-05'), original_table_p2020_12_10 FOR VALUES FROM ('2020-12-10 00:00:00-05') TO ('2020-12-11 00:00:00-05'), original_table_p2020_12_11 FOR VALUES FROM ('2020-12-11 00:00:00-05') TO ('2020-12-12 00:00:00-05'), original_table_p2020_12_12 FOR VALUES FROM ('2020-12-12 00:00:00-05') TO ('2020-12-13 00:00:00-05'), original_table_p2020_12_13 FOR VALUES FROM ('2020-12-13 00:00:00-05') TO ('2020-12-14 00:00:00-05'), original_table_default DEFAULT ``` You can see that created the missing table for Dec 10th. At this point your new partitioned table should already have been in use and working without any issues! ``` INSERT INTO original_table (col2, col3, col4) VALUES ('newstuff', now(), 'newstuff'); INSERT INTO original_table (col2, col3, col4) VALUES ('newstuff', now(), 'newstuff'); SELECT * FROM original_table ORDER BY col1 DESC limit 5; ``` ### Undoing Native Partitioning Just as a normal table cannot be converted to a natively partitioned table, the opposite is also true. To undo native partitioning, you must move the data to a brand new table. There may be a way to do this online, but I do not currently have such a method planned out. If someone would like to submit a method or request that I look into it further, please feel free to make an issue on Github. The below method shows how to undo the daily partitioned example above, including handling an IDENTITY column if necessary. First, we create a new table to migrate the data to. We can set a primary key, or any unique indexes that were made on the template. If there are any identity columns, they have to set the method to `GENERATED BY DEFAULT` since we will be adding values in manually as part of the migration. If it needs to be `ALWAYS`, this can be changed later. If this table is going to continue to be used as the partitioned table, ensure all privileges, constraints & indexes are created on this table as well. Index & constraint creation can be delayed until after the data has been moved to speed up the migration. ``` CREATE TABLE public.new_regular_table ( col1 bigint not null GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY , col2 text not null , col3 timestamptz DEFAULT now() not null , col4 text); CREATE INDEX ON public.new_regular_table (col3); ``` Now we can use the `undo_partition_proc()` procedure to move the data out of our partitioned table to the regular table. We can even chose a smaller interval size for this as well to reduce the transaction runtime for each batch. The batch size is a default of 1, which would only run the given interval one time. We want to undo the entire thing with one call, so pass a number at high enough to run through all batches. It will stop when all the data has been moved, even if you passed a higher batch number. We also don't need to keep the old child tables once they're empty, so that is set to false. See the documentation for more information on other options for the undo functions/procedure. ``` CALL partman.undo_partition_proc('public.original_table', p_interval := '1 hour'::text, p_batch := 500, p_target_table := 'public.new_regular_table', p_keep_table := false); NOTICE: Moved 13 row(s) to the target table. Removed 1 partitions. NOTICE: Batch: 1, Partitions undone this batch: 1, Rows undone this batch: 13 NOTICE: Moved 13 row(s) to the target table. Removed 0 partitions. NOTICE: Batch: 2, Partitions undone this batch: 0, Rows undone this batch: 13 NOTICE: Moved 13 row(s) to the target table. Removed 0 partitions. NOTICE: Batch: 3, Partitions undone this batch: 0, Rows undone this batch: 13 [...] NOTICE: Batch: 160, Partitions undone this batch: 0, Rows undone this batch: 13 NOTICE: Moved 5 row(s) to the target table. Removed 1 partitions. NOTICE: Batch: 161, Partitions undone this batch: 1, Rows undone this batch: 5 NOTICE: Moved 0 row(s) to the target table. Removed 4 partitions. NOTICE: Total partitions undone: 13, Total rows moved: 2017 NOTICE: Ensure to VACUUM ANALYZE the old parent & target table after undo has finished CALL Time: 163465.195 ms (02:43.465) VACUUM ANALYZE original_table; VACUUM Time: 20.706 ms VACUUM ANALYZE new_regular_table; VACUUM Time: 20.375 ms ``` Now object names can be swapped around and the identity sequence reset and method changed if needed. Be sure to grab the original sequence value and use that when resetting. ``` SELECT max(col1) FROM public.original_table; ALTER TABLE original_table RENAME TO old_partitioned_table; ALTER SEQUENCE original_table_col1_seq RENAME TO old_partitioned_table_col1_seq; ALTER TABLE new_regular_table RENAME TO original_table; ALTER SEQUENCE new_regular_table_col1_seq RENAME TO original_table_col1_seq; ALTER TABLE public.original_table ALTER col1 RESTART WITH <<>>; ALTER TABLE public.original_table ALTER col1 SET GENERATED ALWAYS; ``` ``` INSERT INTO original_table (col2, col3, col4) VALUES ('newstuff', now(), 'newstuff'); INSERT INTO original_table (col2, col3, col4) VALUES ('newstuff', now(), 'newstuff'); SELECT * FROM original_table ORDER BY col1 DESC limit 2; ``` pg_partman-4.6.0/doc/pg_partman_howto_triggerbased.md000066400000000000000000001477651412761706100230630ustar00rootroot00000000000000Example Guide On Setting Up Trigger-based Partitioning ======================================== This HowTo guide will show you some examples of how to set up both simple, single level partitioning as well as multi-level sub-partitioning. It will also show you how to partition data out of a table that has existing data (see **Sub-partition ID->ID->ID**) and undo the partitioning of an existing partition set. For more details on what each function does and the additional features in this extension, please see the **pg_partman.md** documentation file. The examples in this document assume you are running at least v3.0.1 of pg_partman. If you need a howto for a previous version, please see an older release available on github. Note that all examples here are for non-native, trigger-based partitioning. Documentation for native partitioning is in the works, but it will mostly be centered around PostgreSQL 11 since 10 was very limited in its partitioning support. ### Simple Time Based: 1 Partition Per Day ``` keith@keith=# \d partman_test.time_taptest_table Table "partman_test.time_taptest_table" Column | Type | Collation | Nullable | Default --------+--------------------------+-----------+----------+--------- col1 | integer | | not null | col2 | text | | | col3 | timestamp with time zone | | not null | now() Indexes: "time_taptest_table_pkey" PRIMARY KEY, btree (col1) keith@keith=# SELECT partman.create_parent('partman_test.time_taptest_table', 'col3', 'partman', 'daily'); create_parent --------------- t (1 row) keith@keith=# \d+ partman_test.time_taptest_table Table "partman_test.time_taptest_table" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+--------------------------+-----------+----------+---------+----------+--------------+------------- col1 | integer | | not null | | plain | | col2 | text | | | | extended | | col3 | timestamp with time zone | | not null | now() | plain | | Indexes: "time_taptest_table_pkey" PRIMARY KEY, btree (col1) Triggers: time_taptest_table_part_trig BEFORE INSERT ON partman_test.time_taptest_table FOR EACH ROW EXECUTE PROCEDURE partman_test.time_taptest_table_part_trig_func() Child tables: partman_test.time_taptest_table_p2017_03_23, partman_test.time_taptest_table_p2017_03_24, partman_test.time_taptest_table_p2017_03_25, partman_test.time_taptest_table_p2017_03_26, partman_test.time_taptest_table_p2017_03_27, partman_test.time_taptest_table_p2017_03_28, partman_test.time_taptest_table_p2017_03_29, partman_test.time_taptest_table_p2017_03_30, partman_test.time_taptest_table_p2017_03_31 ``` The trigger function most efficiently covers a specific period of time for 4 days before and 4 days after today. This can be adjusted with the `optimize_trigger` config option in the `part_config` table. Outside of that, a dynamic statement tries to find the appropriate child table to put the data into. Note this dynamic statement is far less efficient since a catalog lookup is required and the statement plan cannot be cached as well as looking up the that the child table exists. If the child table does not exist at all for the time value given, the data goes to the parent: ``` keith@keith=# \sf partman_test.time_taptest_table_part_trig_func CREATE OR REPLACE FUNCTION partman_test.time_taptest_table_part_trig_func() RETURNS trigger LANGUAGE plpgsql AS $function$ DECLARE v_count int; v_partition_name text; v_partition_timestamp timestamptz; BEGIN IF TG_OP = 'INSERT' THEN v_partition_timestamp := date_trunc('day', NEW.col3); IF NEW.col3 >= '2017-03-27 00:00:00-04' AND NEW.col3 < '2017-03-28 00:00:00-04' THEN INSERT INTO partman_test.time_taptest_table_p2017_03_27 VALUES (NEW.*) ; ELSIF NEW.col3 >= '2017-03-26 00:00:00-04' AND NEW.col3 < '2017-03-27 00:00:00-04' THEN INSERT INTO partman_test.time_taptest_table_p2017_03_26 VALUES (NEW.*) ; ELSIF NEW.col3 >= '2017-03-28 00:00:00-04' AND NEW.col3 < '2017-03-29 00:00:00-04' THEN INSERT INTO partman_test.time_taptest_table_p2017_03_28 VALUES (NEW.*) ; ELSIF NEW.col3 >= '2017-03-25 00:00:00-04' AND NEW.col3 < '2017-03-26 00:00:00-04' THEN INSERT INTO partman_test.time_taptest_table_p2017_03_25 VALUES (NEW.*) ; ELSIF NEW.col3 >= '2017-03-29 00:00:00-04' AND NEW.col3 < '2017-03-30 00:00:00-04' THEN INSERT INTO partman_test.time_taptest_table_p2017_03_29 VALUES (NEW.*) ; ELSIF NEW.col3 >= '2017-03-24 00:00:00-04' AND NEW.col3 < '2017-03-25 00:00:00-04' THEN INSERT INTO partman_test.time_taptest_table_p2017_03_24 VALUES (NEW.*) ; ELSIF NEW.col3 >= '2017-03-30 00:00:00-04' AND NEW.col3 < '2017-03-31 00:00:00-04' THEN INSERT INTO partman_test.time_taptest_table_p2017_03_30 VALUES (NEW.*) ; ELSIF NEW.col3 >= '2017-03-23 00:00:00-04' AND NEW.col3 < '2017-03-24 00:00:00-04' THEN INSERT INTO partman_test.time_taptest_table_p2017_03_23 VALUES (NEW.*) ; ELSIF NEW.col3 >= '2017-03-31 00:00:00-04' AND NEW.col3 < '2017-04-01 00:00:00-04' THEN INSERT INTO partman_test.time_taptest_table_p2017_03_31 VALUES (NEW.*) ; ELSE v_partition_name := partman.check_name_length('time_taptest_table', to_char(v_partition_timestamp, 'YYYY_MM_DD'), TRUE); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = 'partman_test'::name AND tablename = v_partition_name::name; IF v_count > 0 THEN EXECUTE format('INSERT INTO %I.%I VALUES($1.*) ', 'partman_test', v_partition_name) USING NEW; ELSE RETURN NEW; END IF; END IF; END IF; RETURN NULL; END $function$ ``` ### Simple Serial ID: 1 Partition Per 10 ID Values Starting With Empty Table ``` keith=# \d partman_test.id_taptest_table Table "partman_test.id_taptest_table" Column | Type | Modifiers --------+--------------------------+-------------------------------- col1 | integer | not null col2 | text | not null default 'stuff'::text col3 | timestamp with time zone | default now() Indexes: "id_taptest_table_pkey" PRIMARY KEY, btree (col1) keith=# SELECT create_parent('partman_test.id_taptest_table', 'col1', 'partman', '10'); create_parent --------------- t (1 row) keith=# \d+ partman_test.id_taptest_table Table "partman_test.id_taptest_table" Column | Type | Modifiers | Storage | Stats target | Description --------+--------------------------+--------------------------------+----------+--------------+------------- col1 | integer | not null | plain | | col2 | text | not null default 'stuff'::text | extended | | col3 | timestamp with time zone | default now() | plain | | Indexes: "id_taptest_table_pkey" PRIMARY KEY, btree (col1) Triggers: id_taptest_table_part_trig BEFORE INSERT ON partman_test.id_taptest_table FOR EACH ROW EXECUTE PROCEDURE partman_test.id_taptest_table_part_trig_func() Child tables: partman_test.id_taptest_table_p0, partman_test.id_taptest_table_p10, partman_test.id_taptest_table_p20, partman_test.id_taptest_table_p30, partman_test.id_taptest_table_p40 ``` This trigger function most efficiently covers for 4x10 intervals above the current max (0). Once max id reaches higher values, it will also efficiently cover up to 4x10 intervals behind the current max. Outside of that, a dynamic statement tries to find the appropriate child table to put the data into. And like I said for time above, the dynamic part is less efficient. ``` keith@keith=# \sf partman_test.id_taptest_table_part_trig_func CREATE OR REPLACE FUNCTION partman_test.id_taptest_table_part_trig_func() RETURNS trigger LANGUAGE plpgsql AS $function$ DECLARE v_count int; v_current_partition_id bigint; v_current_partition_name text; v_id_position int; v_last_partition text := 'id_taptest_table_p40'; v_next_partition_id bigint; v_next_partition_name text; v_partition_created boolean; BEGIN IF TG_OP = 'INSERT' THEN IF NEW.col1 >= 0 AND NEW.col1 < 10 THEN INSERT INTO partman_test.id_taptest_table_p0 VALUES (NEW.*) ; ELSIF NEW.col1 >= 10 AND NEW.col1 < 20 THEN INSERT INTO partman_test.id_taptest_table_p10 VALUES (NEW.*) ; ELSIF NEW.col1 >= 20 AND NEW.col1 < 30 THEN INSERT INTO partman_test.id_taptest_table_p20 VALUES (NEW.*) ; ELSIF NEW.col1 >= 30 AND NEW.col1 < 40 THEN INSERT INTO partman_test.id_taptest_table_p30 VALUES (NEW.*) ; ELSIF NEW.col1 >= 40 AND NEW.col1 < 50 THEN INSERT INTO partman_test.id_taptest_table_p40 VALUES (NEW.*) ; ELSE v_current_partition_id := NEW.col1 - (NEW.col1 % 10); v_current_partition_name := partman.check_name_length('id_taptest_table', v_current_partition_id::text, TRUE); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = 'partman_test'::name AND tablename = v_current_partition_name::n ame; IF v_count > 0 THEN EXECUTE format('INSERT INTO %I.%I VALUES($1.*) ', 'partman_test', v_current_partition_name) USING NEW; ELSE RETURN NEW; END IF; END IF; END IF; RETURN NULL; END $function$ ``` ### Simple Serial ID: 1 Partition Per 10 ID Values Starting With Empty Table and using upsert to drop conflicting rows ``` Uses same example table as above keith@keith=# SELECT partman.create_parent('partman_test.id_taptest_table', 'col1', 'partman', '10', p_upsert := 'ON CONFLICT (col1) DO NOTHING'); create_parent --------------- t (1 row) keith@keith=# \d+ partman_test.id_taptest_table Table "partman_test.id_taptest_table" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+--------------------------+-----------+----------+---------------+----------+--------------+------------- col1 | integer | | not null | | plain | | col2 | text | | not null | 'stuff'::text | extended | | col3 | timestamp with time zone | | | now() | plain | | Indexes: "id_taptest_table_pkey" PRIMARY KEY, btree (col1) Triggers: id_taptest_table_part_trig BEFORE INSERT ON partman_test.id_taptest_table FOR EACH ROW EXECUTE PROCEDURE partman_test.id_taptest_table_part_trig_func() Child tables: partman_test.id_taptest_table_p0, partman_test.id_taptest_table_p10, partman_test.id_taptest_table_p20, partman_test.id_taptest_table_p30, partman_test.id_taptest_table_p40 ``` Other than the new ON CONFLICT clause, this trigger function works exactly the same as the previous ID example. ``` keith@keith=# \sf partman_test.id_taptest_table_part_trig_func CREATE OR REPLACE FUNCTION partman_test.id_taptest_table_part_trig_func() RETURNS trigger LANGUAGE plpgsql AS $function$ DECLARE v_count int; v_current_partition_id bigint; v_current_partition_name text; v_id_position int; v_last_partition text := 'id_taptest_table_p40'; v_next_partition_id bigint; v_next_partition_name text; v_partition_created boolean; BEGIN IF TG_OP = 'INSERT' THEN IF NEW.col1 >= 0 AND NEW.col1 < 10 THEN INSERT INTO partman_test.id_taptest_table_p0 VALUES (NEW.*) ON CONFLICT (col1) DO NOTHING; ELSIF NEW.col1 >= 10 AND NEW.col1 < 20 THEN INSERT INTO partman_test.id_taptest_table_p10 VALUES (NEW.*) ON CONFLICT (col1) DO NOTHING; ELSIF NEW.col1 >= 20 AND NEW.col1 < 30 THEN INSERT INTO partman_test.id_taptest_table_p20 VALUES (NEW.*) ON CONFLICT (col1) DO NOTHING; ELSIF NEW.col1 >= 30 AND NEW.col1 < 40 THEN INSERT INTO partman_test.id_taptest_table_p30 VALUES (NEW.*) ON CONFLICT (col1) DO NOTHING; ELSIF NEW.col1 >= 40 AND NEW.col1 < 50 THEN INSERT INTO partman_test.id_taptest_table_p40 VALUES (NEW.*) ON CONFLICT (col1) DO NOTHING; ELSE v_current_partition_id := NEW.col1 - (NEW.col1 % 10); v_current_partition_name := partman.check_name_length('id_taptest_table', v_current_partition_id::text, TRUE); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = 'partman_test'::name AND tablename = v_current_partition_name::n ame; IF v_count > 0 THEN EXECUTE format('INSERT INTO %I.%I VALUES($1.*) ON CONFLICT (col1) DO NOTHING', 'partman_test', v_current_partition_name) USING NEW; ELSE RETURN NEW; END IF; END IF; END IF; RETURN NULL; END $function$ ``` Running the following query will insert a row in the table ``` keith@keith=# INSERT INTO partman_test.id_taptest_table(col1,col2) VALUES(1,'insert1'); INSERT 0 0 Time: 4.876 ms keith@keith=# SELECT * FROM partman_test.id_taptest_table; col1 | col2 | col3 ------+---------+------------------------------- 1 | insert1 | 2017-03-27 14:23:02.769999-04 (1 row) ``` Running the following query will not fail but the row in the table will not change and col2 will still be 'insert1' ``` keith@keith=# INSERT INTO partman_test.id_taptest_table(col1,col2) VALUES(1,'insert2'); INSERT 0 0 Time: 1.583 ms keith@keith=# SELECT * FROM partman_test.id_taptest_table; col1 | col2 | col3 ------+---------+------------------------------- 1 | insert1 | 2017-03-27 14:23:02.769999-04 (1 row) ``` ### Simple Serial ID: 1 Partition Per 10 ID Values Starting With Empty Table and using upsert to update conflicting rows ``` Uses same example table as above keith@keith=# SELECT partman.create_parent('partman_test.id_taptest_table', 'col1', 'partman', '10', p_upsert := 'ON CONFLICT (col1) DO UPDATE SET col2=EXCLUDED.col2, col3=EXCLUDED.col3'); create_parent --------------- t (1 row) keith@keith=# \d+ partman_test.id_taptest_table Table "partman_test.id_taptest_table" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+--------------------------+-----------+----------+---------------+----------+--------------+------------- col1 | integer | | not null | | plain | | col2 | text | | not null | 'stuff'::text | extended | | col3 | timestamp with time zone | | | now() | plain | | Indexes: "id_taptest_table_pkey" PRIMARY KEY, btree (col1) Triggers: id_taptest_table_part_trig BEFORE INSERT ON partman_test.id_taptest_table FOR EACH ROW EXECUTE PROCEDURE partman_test.id_taptest_table_part_trig_func() Child tables: partman_test.id_taptest_table_p0, partman_test.id_taptest_table_p10, partman_test.id_taptest_table_p20, partman_test.id_taptest_table_p30, partman_test.id_taptest_table_p40 ``` Other than the new ON CONFLICT clause, this trigger function works exactly the same as the previous ID example. ``` keith@keith=# \sf partman_test.id_taptest_table_part_trig_func CREATE OR REPLACE FUNCTION partman_test.id_taptest_table_part_trig_func() RETURNS trigger LANGUAGE plpgsql AS $function$ DECLARE v_count int; v_current_partition_id bigint; v_current_partition_name text; v_id_position int; v_last_partition text := 'id_taptest_table_p40'; v_next_partition_id bigint; v_next_partition_name text; v_partition_created boolean; BEGIN IF TG_OP = 'INSERT' THEN IF NEW.col1 >= 0 AND NEW.col1 < 10 THEN INSERT INTO partman_test.id_taptest_table_p0 VALUES (NEW.*) ON CONFLICT (col1) DO UPDATE SET col2=EXCLUDED.col2, col3=EXCLUDED.col3; ELSIF NEW.col1 >= 10 AND NEW.col1 < 20 THEN INSERT INTO partman_test.id_taptest_table_p10 VALUES (NEW.*) ON CONFLICT (col1) DO UPDATE SET col2=EXCLUDED.col2, col3=EXCLUDED.col3; ELSIF NEW.col1 >= 20 AND NEW.col1 < 30 THEN INSERT INTO partman_test.id_taptest_table_p20 VALUES (NEW.*) ON CONFLICT (col1) DO UPDATE SET col2=EXCLUDED.col2, col3=EXCLUDED.col3; ELSIF NEW.col1 >= 30 AND NEW.col1 < 40 THEN INSERT INTO partman_test.id_taptest_table_p30 VALUES (NEW.*) ON CONFLICT (col1) DO UPDATE SET col2=EXCLUDED.col2, col3=EXCLUDED.col3; ELSIF NEW.col1 >= 40 AND NEW.col1 < 50 THEN INSERT INTO partman_test.id_taptest_table_p40 VALUES (NEW.*) ON CONFLICT (col1) DO UPDATE SET col2=EXCLUDED.col2, col3=EXCLUDED.col3; ELSE v_current_partition_id := NEW.col1 - (NEW.col1 % 10); v_current_partition_name := partman.check_name_length('id_taptest_table', v_current_partition_id::text, TRUE); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = 'partman_test'::name AND tablename = v_current_partition_name::n ame; IF v_count > 0 THEN EXECUTE format('INSERT INTO %I.%I VALUES($1.*) ON CONFLICT (col1) DO UPDATE SET col2=EXCLUDED.col2, col3=EXCLUDED.col3', 'partman_test ', v_current_partition_name) USING NEW; ELSE RETURN NEW; END IF; END IF; END IF; RETURN NULL; END $function$ ``` Running the following query will insert a row in the table ``` keith@keith=# INSERT INTO partman_test.id_taptest_table(col1,col2) VALUES(1,'insert1'); INSERT 0 0 Time: 6.012 ms keith@keith=# SELECT * FROM partman_test.id_taptest_table; col1 | col2 | col3 ------+---------+------------------------------ 1 | insert1 | 2017-03-27 14:32:26.59552-04 (1 row) ``` Running the following query will not fail and the row in the table will change and col2 will now be 'insert2' and the timestamp in col3 will update to the default value now() ``` keith@keith=# INSERT INTO partman_test.id_taptest_table(col1,col2) VALUES(1,'insert2'); INSERT 0 0 Time: 4.235 ms keith@keith=# SELECT * FROM partman_test.id_taptest_table; col1 | col2 | col3 ------+---------+------------------------------- 1 | insert2 | 2017-03-27 14:33:00.949928-04 (1 row) ``` ### Sub-partition Time->Time->Time: Yearly -> Monthly -> Daily ``` keith@keith=# \d partman_test.time_taptest_table Table "partman_test.time_taptest_table" Column | Type | Collation | Nullable | Default --------+--------------------------+-----------+----------+--------- col1 | integer | | not null | col2 | text | | | col3 | timestamp with time zone | | not null | now() Indexes: "time_taptest_table_pkey" PRIMARY KEY, btree (col1) ``` Create top yearly partition set that only covers 2 years forward/back ``` keith@keith=# SELECT partman.create_parent('partman_test.time_taptest_table', 'col3', 'partman', 'yearly', p_premake := 2); create_parent --------------- t (1 row) ``` Now tell pg_partman to partition all yearly child tables by month. Do this by giving it the parent table of the yearly partition set (happens to be the same as above) ``` keith@keith=# SELECT partman.create_sub_parent('partman_test.time_taptest_table', 'col3', 'partman', 'monthly', p_premake := 2); create_sub_parent ------------------- t (1 row) keith@keith=# SELECT tablename FROM pg_tables WHERE schemaname = 'partman_test' ORDER BY tablename; tablename ----------------------------------- time_taptest_table time_taptest_table_p2015 time_taptest_table_p2015_p2015_01 time_taptest_table_p2016 time_taptest_table_p2016_p2016_01 time_taptest_table_p2017 time_taptest_table_p2017_p2017_01 time_taptest_table_p2017_p2017_02 time_taptest_table_p2017_p2017_03 time_taptest_table_p2017_p2017_04 time_taptest_table_p2017_p2017_05 time_taptest_table_p2018 time_taptest_table_p2018_p2018_01 time_taptest_table_p2019 time_taptest_table_p2019_p2019_01 (15 rows) ``` The day this tutorial was updated is 2017-03-27. You now see that this causes only 2 new future partitions to be created. And for the monthly partitions, they have been created to cover 2 months ahead as well. Note that the trigger will still cover 4 ahead and 4 behind for both partition levels unless you change the `optimize_trigger` option in the config table. A parent table ALWAYS has at least one child, so for the time period that is outside of what the premake covers, just a single table has been made for the lowest possible month in that yearly time period (January). Now tell pg_partman to partition every monthly table that currently exists by day. Do this by giving it the parent table of each monthly partition set (the parent with the just the year suffix since its children are the monthly partitions). ``` SELECT partman.create_sub_parent('partman_test.time_taptest_table_p2015', 'col3', 'partman', 'daily', p_premake := 2); SELECT partman.create_sub_parent('partman_test.time_taptest_table_p2016', 'col3', 'partman', 'daily', p_premake := 2); SELECT partman.create_sub_parent('partman_test.time_taptest_table_p2017', 'col3', 'partman', 'daily', p_premake := 2); SELECT partman.create_sub_parent('partman_test.time_taptest_table_p2018', 'col3', 'partman', 'daily', p_premake := 2); SELECT partman.create_sub_parent('partman_test.time_taptest_table_p2019', 'col3', 'partman', 'daily', p_premake := 2); keith@keith=# SELECT tablename FROM pg_tables WHERE schemaname = 'partman_test' ORDER BY tablename; tablename ----------------------------------------------- time_taptest_table time_taptest_table_p2015 time_taptest_table_p2015_p2015_01 time_taptest_table_p2015_p2015_01_p2015_01_01 time_taptest_table_p2016 time_taptest_table_p2016_p2016_01 time_taptest_table_p2016_p2016_01_p2016_01_01 time_taptest_table_p2017 time_taptest_table_p2017_p2017_01 time_taptest_table_p2017_p2017_01_p2017_01_01 time_taptest_table_p2017_p2017_02 time_taptest_table_p2017_p2017_02_p2017_02_01 time_taptest_table_p2017_p2017_03 time_taptest_table_p2017_p2017_03_p2017_03_25 time_taptest_table_p2017_p2017_03_p2017_03_26 time_taptest_table_p2017_p2017_03_p2017_03_27 time_taptest_table_p2017_p2017_03_p2017_03_28 time_taptest_table_p2017_p2017_03_p2017_03_29 time_taptest_table_p2017_p2017_04 time_taptest_table_p2017_p2017_04_p2017_04_01 time_taptest_table_p2017_p2017_05 time_taptest_table_p2017_p2017_05_p2017_05_01 time_taptest_table_p2018 time_taptest_table_p2018_p2018_01 time_taptest_table_p2018_p2018_01_p2018_01_01 time_taptest_table_p2019 time_taptest_table_p2019_p2019_01 time_taptest_table_p2019_p2019_01_p2019_01_01 (28 rows) ``` Again, assuming today's date is 2017-03-27, it has created the sub-partitions to cover 2 days in the future. All other parent tables outside of the current time period have the lowest possible day created for them. ### Sub-partition ID->ID->ID: 10,000 -> 1,000 -> 100 This partition set has existing data already in it. We will partition it out using the python script found in the "bin" directory of the repo. It is possible to use the partition_data_id() function in postgres as well, but that would partition all the data out in a single transaction which, for a live table, could cause serious contention & I/O issues. The python script allows commits to be done in batches and avoid that contention and you can add a pause in between batches to limit I/O activity. The p_jobmon flag being set in the creation functions is done just to keep the spamming of the jobmon logs to a minimum for these test examples. ``` keith@keith=# \d partman_test.id_taptest_table Table "partman_test.id_taptest_table" Column | Type | Collation | Nullable | Default --------+--------------------------+-----------+----------+--------------- col1 | integer | | not null | col2 | text | | not null | 'stuff'::text col3 | timestamp with time zone | | | now() Indexes: "id_taptest_table_pkey" PRIMARY KEY, btree (col1) keith@keith=# SELECT count(*) FROM partman_test.id_taptest_table ; count -------- 100000 (1 row) keith@keith=# SELECT min(col1), max(col1) FROM partman_test.id_taptest_table ; min | max -----+-------- 1 | 100000 (1 row) ``` Since there is already data in the table, the child tables initially created will be based around the max value, two before it and two after it. As stated above for time, the trigger still covers for 4 partitions before & after most efficiently, so if you need to adjust that as well, see the `part_config` table. ``` keith@keith=# SELECT partman.create_parent('partman_test.id_taptest_table', 'col1', 'partman', '10000', p_jobmon := false, p_premake := 2); --------------- t (1 row) keith@keith=# SELECT tablename FROM pg_tables WHERE schemaname = 'partman_test' ORDER BY tablename; tablename -------------------------- id_taptest_table id_taptest_table_p100000 id_taptest_table_p110000 id_taptest_table_p120000 id_taptest_table_p80000 id_taptest_table_p90000 (6 rows) ``` However, the data still resides in the parent table at this time. To partition it out, use the python script as mentioned above. The options below will cause it to commit every 100 rows. If the interval option was not given, it would commit them at the configured interval of 10,000. Allowing a lower interval decreases the possible contention and allows the data to be more readily available in the newly created partitions: ``` $ python partition_data.py -c host=localhost -p partman_test.id_taptest_table -t id -i 100 Attempting to turn off autovacuum for partition set... ... Success! Rows moved: 100 Rows moved: 100 ... Rows moved: 99 ... Rows moved: 100 Rows moved: 1 Total rows moved: 100000 Running vacuum analyze on parent table... Attempting to reset autovacuum for old parent table and all child tables... ... Success! ``` Partitioning the data like this has also made the partitions that were needed to store the data ``` keith@keith=# SELECT tablename FROM pg_tables WHERE schemaname = 'partman_test' ORDER BY tablename; tablename -------------------------- id_taptest_table id_taptest_table_p0 id_taptest_table_p10000 id_taptest_table_p100000 id_taptest_table_p110000 id_taptest_table_p120000 id_taptest_table_p20000 id_taptest_table_p30000 id_taptest_table_p40000 id_taptest_table_p50000 id_taptest_table_p60000 id_taptest_table_p70000 id_taptest_table_p80000 id_taptest_table_p90000 (14 rows) ``` Now create the sub-partitions for 1000. As was noted above for time, we give the parent table who's children we want partitioned along with the properties to give those children: ``` keith@keith=# SELECT partman.create_sub_parent('partman_test.id_taptest_table', 'col1', 'partman', '1000', p_jobmon := false, p_premake := 2); create_sub_parent ------------------- t (1 row) ``` All children tables get at least their minimum sub-partition made and the sub-partitions based around the current max value are also created. ``` keith@keith=# SELECT tablename FROM pg_tables WHERE schemaname = 'partman_test' ORDER BY tablename; tablename ---------------------------------- id_taptest_table id_taptest_table_p0 id_taptest_table_p0_p0 id_taptest_table_p10000 id_taptest_table_p100000 id_taptest_table_p100000_p100000 id_taptest_table_p100000_p101000 id_taptest_table_p100000_p102000 id_taptest_table_p10000_p10000 id_taptest_table_p110000 id_taptest_table_p110000_p110000 id_taptest_table_p120000 id_taptest_table_p120000_p120000 id_taptest_table_p20000 id_taptest_table_p20000_p20000 id_taptest_table_p30000 id_taptest_table_p30000_p30000 id_taptest_table_p40000 id_taptest_table_p40000_p40000 id_taptest_table_p50000 id_taptest_table_p50000_p50000 id_taptest_table_p60000 id_taptest_table_p60000_p60000 id_taptest_table_p70000 id_taptest_table_p70000_p70000 id_taptest_table_p80000 id_taptest_table_p80000_p80000 id_taptest_table_p90000 id_taptest_table_p90000_p98000 id_taptest_table_p90000_p99000 (30 rows) ``` If you're wondering why, even with data in them, the children didn't get all their sub-partitions created, it's for the same reason that the top partition only initially had the 2 previous and 2 after created: the data still exists in the sub-partition parents. You can see this by running the monitoring function built into pg_partman here: ``` keith@keith=# SELECT * FROM partman.check_parent() ORDER BY 1; parent_table | count ---------------------------------------+------- partman_test.id_taptest_table_p0 | 9999 partman_test.id_taptest_table_p10000 | 10000 partman_test.id_taptest_table_p100000 | 1 partman_test.id_taptest_table_p20000 | 10000 partman_test.id_taptest_table_p30000 | 10000 partman_test.id_taptest_table_p40000 | 10000 partman_test.id_taptest_table_p50000 | 10000 partman_test.id_taptest_table_p60000 | 10000 partman_test.id_taptest_table_p70000 | 10000 partman_test.id_taptest_table_p80000 | 10000 partman_test.id_taptest_table_p90000 | 10000 (11 rows) ``` So, lets fix that: ``` python partition_data.py -c host=localhost -p partman_test.id_taptest_table_p0 -t id -i 100 python partition_data.py -c host=localhost -p partman_test.id_taptest_table_p10000 -t id -i 100 python partition_data.py -c host=localhost -p partman_test.id_taptest_table_p20000 -t id -i 100 python partition_data.py -c host=localhost -p partman_test.id_taptest_table_p30000 -t id -i 100 python partition_data.py -c host=localhost -p partman_test.id_taptest_table_p40000 -t id -i 100 python partition_data.py -c host=localhost -p partman_test.id_taptest_table_p50000 -t id -i 100 python partition_data.py -c host=localhost -p partman_test.id_taptest_table_p60000 -t id -i 100 python partition_data.py -c host=localhost -p partman_test.id_taptest_table_p70000 -t id -i 100 python partition_data.py -c host=localhost -p partman_test.id_taptest_table_p80000 -t id -i 100 python partition_data.py -c host=localhost -p partman_test.id_taptest_table_p90000 -t id -i 100 python partition_data.py -c host=localhost -p partman_test.id_taptest_table_p100000 -t id -i 100 ``` Now the monitoring function returns nothing (as should be the norm): ``` keith@keith=# SELECT * FROM partman.check_parent() ORDER BY 1; parent_table | count --------------+------- (0 rows) ``` Now we also see all child partitions were created for the data that exists: ``` keith@keith=# SELECT tablename FROM pg_tables WHERE schemaname = 'partman_test' ORDER BY tablename; tablename ---------------------------------- id_taptest_table id_taptest_table_p0 id_taptest_table_p0_p0 id_taptest_table_p0_p1000 id_taptest_table_p0_p2000 id_taptest_table_p0_p3000 id_taptest_table_p0_p4000 id_taptest_table_p0_p5000 id_taptest_table_p0_p6000 id_taptest_table_p0_p7000 id_taptest_table_p0_p8000 id_taptest_table_p0_p9000 id_taptest_table_p10000 id_taptest_table_p100000 id_taptest_table_p100000_p100000 id_taptest_table_p100000_p101000 id_taptest_table_p100000_p102000 id_taptest_table_p10000_p10000 id_taptest_table_p10000_p11000 id_taptest_table_p10000_p12000 id_taptest_table_p10000_p13000 id_taptest_table_p10000_p14000 id_taptest_table_p10000_p15000 id_taptest_table_p10000_p16000 id_taptest_table_p10000_p17000 id_taptest_table_p10000_p18000 id_taptest_table_p10000_p19000 id_taptest_table_p110000 id_taptest_table_p110000_p110000 id_taptest_table_p120000 id_taptest_table_p120000_p120000 id_taptest_table_p20000 id_taptest_table_p20000_p20000 id_taptest_table_p20000_p21000 id_taptest_table_p20000_p22000 id_taptest_table_p20000_p23000 id_taptest_table_p20000_p24000 id_taptest_table_p20000_p25000 id_taptest_table_p20000_p26000 id_taptest_table_p20000_p27000 id_taptest_table_p20000_p28000 id_taptest_table_p20000_p29000 id_taptest_table_p30000 id_taptest_table_p30000_p30000 id_taptest_table_p30000_p31000 id_taptest_table_p30000_p32000 id_taptest_table_p30000_p33000 id_taptest_table_p30000_p34000 id_taptest_table_p30000_p35000 id_taptest_table_p30000_p36000 id_taptest_table_p30000_p37000 id_taptest_table_p30000_p38000 id_taptest_table_p30000_p39000 id_taptest_table_p40000 id_taptest_table_p40000_p40000 id_taptest_table_p40000_p41000 id_taptest_table_p40000_p42000 id_taptest_table_p40000_p43000 id_taptest_table_p40000_p44000 id_taptest_table_p40000_p45000 id_taptest_table_p40000_p46000 id_taptest_table_p40000_p47000 id_taptest_table_p40000_p48000 id_taptest_table_p40000_p49000 id_taptest_table_p50000 id_taptest_table_p50000_p50000 id_taptest_table_p50000_p51000 id_taptest_table_p50000_p52000 id_taptest_table_p50000_p53000 id_taptest_table_p50000_p54000 id_taptest_table_p50000_p55000 id_taptest_table_p50000_p56000 id_taptest_table_p50000_p57000 id_taptest_table_p50000_p58000 id_taptest_table_p50000_p59000 id_taptest_table_p60000 id_taptest_table_p60000_p60000 id_taptest_table_p60000_p61000 id_taptest_table_p60000_p62000 id_taptest_table_p60000_p63000 id_taptest_table_p60000_p64000 id_taptest_table_p60000_p65000 id_taptest_table_p60000_p66000 id_taptest_table_p60000_p67000 id_taptest_table_p60000_p68000 id_taptest_table_p60000_p69000 id_taptest_table_p70000 id_taptest_table_p70000_p70000 id_taptest_table_p70000_p71000 id_taptest_table_p70000_p72000 id_taptest_table_p70000_p73000 id_taptest_table_p70000_p74000 id_taptest_table_p70000_p75000 id_taptest_table_p70000_p76000 id_taptest_table_p70000_p77000 id_taptest_table_p70000_p78000 id_taptest_table_p70000_p79000 id_taptest_table_p80000 id_taptest_table_p80000_p80000 id_taptest_table_p80000_p81000 id_taptest_table_p80000_p82000 id_taptest_table_p80000_p83000 id_taptest_table_p80000_p84000 id_taptest_table_p80000_p85000 id_taptest_table_p80000_p86000 id_taptest_table_p80000_p87000 id_taptest_table_p80000_p88000 id_taptest_table_p80000_p89000 id_taptest_table_p90000 id_taptest_table_p90000_p90000 id_taptest_table_p90000_p91000 id_taptest_table_p90000_p92000 id_taptest_table_p90000_p93000 id_taptest_table_p90000_p94000 id_taptest_table_p90000_p95000 id_taptest_table_p90000_p96000 id_taptest_table_p90000_p97000 id_taptest_table_p90000_p98000 id_taptest_table_p90000_p99000 (119 rows) ``` We can still take this another level deeper as well. Normally with a large amount of data, it's not recommended to partition down to an interval this low since the benefit gained is minimal compared the management of such a large number of tables. But it's being done here as an example. Just as with the time example above, we now have to sub-partition each one of the sub-parent tables to say how we want their children sub-partitioned: ``` SELECT partman.create_sub_parent('partman_test.id_taptest_table_p0', 'col1', 'partman', '100', p_jobmon := false, p_premake := 2); SELECT partman.create_sub_parent('partman_test.id_taptest_table_p10000', 'col1', 'partman', '100', p_jobmon := false, p_premake := 2); SELECT partman.create_sub_parent('partman_test.id_taptest_table_p20000', 'col1', 'partman', '100', p_jobmon := false, p_premake := 2); SELECT partman.create_sub_parent('partman_test.id_taptest_table_p30000', 'col1', 'partman', '100', p_jobmon := false, p_premake := 2); SELECT partman.create_sub_parent('partman_test.id_taptest_table_p40000', 'col1', 'partman', '100', p_jobmon := false, p_premake := 2); SELECT partman.create_sub_parent('partman_test.id_taptest_table_p50000', 'col1', 'partman', '100', p_jobmon := false, p_premake := 2); SELECT partman.create_sub_parent('partman_test.id_taptest_table_p60000', 'col1', 'partman', '100', p_jobmon := false, p_premake := 2); SELECT partman.create_sub_parent('partman_test.id_taptest_table_p70000', 'col1', 'partman', '100', p_jobmon := false, p_premake := 2); SELECT partman.create_sub_parent('partman_test.id_taptest_table_p80000', 'col1', 'partman', '100', p_jobmon := false, p_premake := 2); SELECT partman.create_sub_parent('partman_test.id_taptest_table_p90000', 'col1', 'partman', '100', p_jobmon := false, p_premake := 2); SELECT partman.create_sub_parent('partman_test.id_taptest_table_p100000', 'col1', 'partman', '100', p_jobmon := false, p_premake := 2); ``` I won't show the full list here, but you can see how every child table of the above parents is now a parent table itself with the appropriate minimal child table created where needed as well as the child tables around the current max: ``` keith@keith=# SELECT tablename FROM pg_tables WHERE schemaname = 'partman_test' order by tablename; tablename ------------------------------------------ id_taptest_table id_taptest_table_p0 id_taptest_table_p0_p0 id_taptest_table_p0_p0_p0 id_taptest_table_p0_p1000 id_taptest_table_p0_p1000_p1000 id_taptest_table_p0_p2000 id_taptest_table_p0_p2000_p2000 ... id_taptest_table_p10000 id_taptest_table_p100000 id_taptest_table_p100000_p100000 id_taptest_table_p100000_p100000_p100000 id_taptest_table_p100000_p100000_p100100 id_taptest_table_p100000_p100000_p100200 id_taptest_table_p100000_p101000 id_taptest_table_p100000_p101000_p101000 id_taptest_table_p100000_p102000 id_taptest_table_p100000_p102000_p102000 id_taptest_table_p10000_p10000 id_taptest_table_p10000_p10000_p10000 id_taptest_table_p10000_p11000 id_taptest_table_p10000_p11000_p11000 ... id_taptest_table_p90000_p98000 id_taptest_table_p90000_p98000_p98000 id_taptest_table_p90000_p99000 id_taptest_table_p90000_p99000_p99800 id_taptest_table_p90000_p99000_p99900 (225 rows) ``` If you ran the check_parent() function, you'd see that now each one of these new parent tables now needs to have its data moved. Now's a good time show a trick for generating many individual statements based on values returned from a query: ``` SELECT 'python partition_data.py -c host=localhost -p '||parent_table||' -t id -i 100' FROM partman.part_config ORDER BY parent_table; ?column? --------------------------------------------------------------------------------------------------------- python partition_data.py -c host=localhost -p partman_test.id_taptest_table -t id -i 100 python partition_data.py -c host=localhost -p partman_test.id_taptest_table_p0 -t id -i 100 python partition_data.py -c host=localhost -p partman_test.id_taptest_table_p0_p0 -t id -i 100 python partition_data.py -c host=localhost -p partman_test.id_taptest_table_p0_p1000 -t id -i 100 ... ``` This will generate the commands to partition out the data found in any parent table managed by pg_partman. Yes some are already empty, but that won't matter since they'll just do nothing and it makes the query to generate these commands easier. Recommend putting the output from this into an executable shell file vs just pasting it all into the shell directly. Now if you get a list of all the tables, you can see there's quite a lot now (the row count returned is the number of tables). ``` keith@keith=# SELECT tablename FROM pg_tables WHERE schemaname = 'partman_test' order by tablename; tablename ----------------------------------------- id_taptest_table id_taptest_table_p0 id_taptest_table_p0_p0 id_taptest_table_p0_p0_p0 id_taptest_table_p0_p0_p100 id_taptest_table_p0_p0_p200 id_taptest_table_p0_p0_p300 id_taptest_table_p0_p0_p400 id_taptest_table_p0_p0_p500 id_taptest_table_p0_p0_p600 id_taptest_table_p0_p0_p700 id_taptest_table_p0_p0_p800 id_taptest_table_p0_p0_p900 id_taptest_table_p0_p1000 id_taptest_table_p0_p1000_p1000 id_taptest_table_p0_p1000_p1100 id_taptest_table_p0_p1000_p1200 id_taptest_table_p0_p1000_p1300 id_taptest_table_p0_p1000_p1400 id_taptest_table_p0_p1000_p1500 id_taptest_table_p0_p1000_p1600 id_taptest_table_p0_p1000_p1700 id_taptest_table_p0_p1000_p1800 id_taptest_table_p0_p1000_p1900 id_taptest_table_p0_p2000 id_taptest_table_p0_p2000_p2000 id_taptest_table_p0_p2000_p2100 ... id_taptest_table_p90000_p98000_p98800 id_taptest_table_p90000_p98000_p98900 id_taptest_table_p90000_p99000 id_taptest_table_p90000_p99000_p99000 id_taptest_table_p90000_p99000_p99100 id_taptest_table_p90000_p99000_p99200 id_taptest_table_p90000_p99000_p99300 id_taptest_table_p90000_p99000_p99400 id_taptest_table_p90000_p99000_p99500 id_taptest_table_p90000_p99000_p99600 id_taptest_table_p90000_p99000_p99700 id_taptest_table_p90000_p99000_p99800 id_taptest_table_p90000_p99000_p99900 (1124 rows) ``` Now all 100,000 rows are properly partitioned where they should be and any new rows should go where they're supposed to. ### Set run_maintenance() to run often enough Using the above time-based partitions, run_maintenance() should be called at least twice a day to ensure it keeps up with the requirements of the smallest time partition interval (daily). For serial based partitioning, you must know your data ingestion rate and call it often enough to keep new partitions created ahead of that rate. If you're using the Background Worker (BGW), set the pg_partman_bgw.interval value in postgresql.conf. This example sets it to run every 12 hrs (43200 seconds). See the doc/pg_partman.md file for more information on the BGW settings. pg_partman_bgw.interval = 43200 pg_partman_bgw.role = 'keith' pg_partman_bgw.dbname = 'keith' If you're not using the BGW, you must use a third-party scheduling tool like cron to schedule the calls to run_maintenance() 03 01,13 * * * psql -c "SELECT run_maintenance()" ### Use Retention Policy To drop partitions on the first example above that are older than 30 days, set the following: ``` UPDATE part_config SET retention = '30 days', retention_keep_table = false WHERE parent_table = 'partman_test.time_taptest_table'; ``` To drop partitions on the second example above that contain a value 100 less than the current max (max(col1) - 100), set the following: ``` UPDATE part_config SET retention = '100', retention_keep_table = false WHERE parent_table = 'partman_test.id_taptest_table'; ``` For example, once the current id value of col1 reaches 1000, all partitions with values less than 900 will be dropped. If you'd like to keep the old data stored offline in dump files, set the retention_schema column as well (the keep* config options will be overridden if this is set): ``` UPDATE part_config SET retention = '30 days', retention_schema = 'archive' WHERE parent_table = 'partman_test.time_taptest_table'; ``` Then use the included python script **dump_partition.py** to dump out all tables contained in the archive schema: ``` $ python dump_partition.py -c "host=localhost username=postgres" -d mydatabase -n archive -o /path/to/dump/location ``` To implement any retention policy, just ensure run_maintenance() is called often enough for your needs. That function handles both partition creation and the retention policies. ### Undo Partitioning: Simple Time Based As with partitioning data out, it's best to use the python script to undo partitioning as well to avoid contention and moving large amounts of data in a single transaction. Except for the final example, there's no data in these partition sets, but the example would work either way. This also shows how you can give time-based partition sets a lower interval than what they are partitioned at. This set was daily above, but the batches are committed at the hourly marks (if there was data). ``` $ python undo_partition.py -p partman_test.time_taptest_table -c host=localhost -t time -i "1 hour" Attempting to turn off autovacuum for partition set... ... Success! Total rows moved: 0 Running vacuum analyze on parent table... Attempting to reset autovacuum for old parent table... ... Success! ``` ### Undo Partitioning: Simple Serial ID This just undoes the id partitions committing at the default partition interval of 10 given above. ``` $ python undo_partition.py -p partman_test.id_taptest_table -c host=localhost -t id Attempting to turn off autovacuum for partition set... ... Success! Total rows moved: 0 Running vacuum analyze on parent table... Attempting to reset autovacuum for old parent table... ... Success! ``` ### Undo Partitioning: Sub-partition ID->ID->ID Undoing sub-partitioning involves a little more work (or possibly a lot if it's a large set). You have to start from the bottom up. Just as I did above for generating statements for partitioning the data out, I can do the same for the undo_partition.py script. Keep in mind this gets the undo statement for ALL the parents at once. You do have to go through and parse out the top level calls as well as the mid-level partition, but this at least saves you a lot of potential typing (and typos). The bottom partitions must all be done first and the top last. Also, in this case I have no intention of keeping the old, empty tables anymore, so the --droptable option is given. pg_partman tries to be as safe as possible, so it only uninherits tables by default when undoing partitioning. If you want something dropped, you have to be sure and tell it. ``` SELECT 'python undo_partition.py -c host=localhost -p '||parent_table||' -t id -i 100 --droptable' FROM partman.part_config ORDER BY parent_table; ``` First do the lowest level sub-partitions: ``` python undo_partition.py -c host=localhost -p partman_test.id_taptest_table_p0_p0 -t id -i 100 --droptable python undo_partition.py -c host=localhost -p partman_test.id_taptest_table_p0_p1000 -t id -i 100 --droptable python undo_partition.py -c host=localhost -p partman_test.id_taptest_table_p0_p2000 -t id -i 100 --droptable ... python undo_partition.py -c host=localhost -p partman_test.id_taptest_table_p100000_p100000 -t id -i 100 --droptable python undo_partition.py -c host=localhost -p partman_test.id_taptest_table_p100000_p101000 -t id -i 100 --droptable python undo_partition.py -c host=localhost -p partman_test.id_taptest_table_p100000_p102000 -t id -i 100 --droptable ``` Next do what were the mid level sub-partitions: ``` python undo_partition.py -c host=localhost -p partman_test.id_taptest_table_p0 -t id -i 100 --droptable python undo_partition.py -c host=localhost -p partman_test.id_taptest_table_p10000 -t id -i 100 --droptable python undo_partition.py -c host=localhost -p partman_test.id_taptest_table_p100000 -t id -i 100 --droptable python undo_partition.py -c host=localhost -p partman_test.id_taptest_table_p110000 -t id -i 100 --droptable python undo_partition.py -c host=localhost -p partman_test.id_taptest_table_p120000 -t id -i 100 --droptable python undo_partition.py -c host=localhost -p partman_test.id_taptest_table_p20000 -t id -i 100 --droptable python undo_partition.py -c host=localhost -p partman_test.id_taptest_table_p30000 -t id -i 100 --droptable python undo_partition.py -c host=localhost -p partman_test.id_taptest_table_p40000 -t id -i 100 --droptable python undo_partition.py -c host=localhost -p partman_test.id_taptest_table_p50000 -t id -i 100 --droptable python undo_partition.py -c host=localhost -p partman_test.id_taptest_table_p60000 -t id -i 100 --droptable python undo_partition.py -c host=localhost -p partman_test.id_taptest_table_p70000 -t id -i 100 --droptable python undo_partition.py -c host=localhost -p partman_test.id_taptest_table_p80000 -t id -i 100 --droptable python undo_partition.py -c host=localhost -p partman_test.id_taptest_table_p90000 -t id -i 100 --droptable ``` And finally do the last, top level partition: ``` python undo_partition.py -c host=localhost -p partman_test.id_taptest_table -t id -i 100 --droptable ``` Now there is only one table left with all the data ``` keith@keith=# SELECT tablename FROM pg_tables WHERE schemaname = 'partman_test' ORDER BY tablename; tablename ----------------- id_taptest_table keith@keith=# SELECT count(*) FROM partman_test.id_taptest_table ; count -------- 100000 (1 row) ``` ### Undo Partitioning: Sub-partition Time->Time->Time This is done in the same exact way as for ID->ID->ID except the undo_partition.py script would use the -t time setting and -i would use a time interval value. Hopefully these working examples can help you get started. Again, please see the `pg_partman.md` doc for the full details on all the functions and features of this extension. If you have any issues or questions, feel free to open an issue on the github page: https://github.com/pgpartman/pg_partman pg_partman-4.6.0/pg_partman.control000066400000000000000000000001571412761706100174130ustar00rootroot00000000000000default_version = '4.6.0' comment = 'Extension to manage partitioned tables by time or ID' relocatable = false pg_partman-4.6.0/sql/000077500000000000000000000000001412761706100144555ustar00rootroot00000000000000pg_partman-4.6.0/sql/functions/000077500000000000000000000000001412761706100164655ustar00rootroot00000000000000pg_partman-4.6.0/sql/functions/apply_cluster.sql000066400000000000000000000050401412761706100220730ustar00rootroot00000000000000CREATE FUNCTION @extschema@.apply_cluster(p_parent_schema text, p_parent_tablename text, p_child_schema text, p_child_tablename text) RETURNS void LANGUAGE plpgsql AS $$ DECLARE v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_indexdef text; v_relkind char; v_row record; v_sql text; BEGIN /* * Function to apply cluster from parent to child table * Adapted from code fork by https://github.com/dturon/pg_partman */ 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'); 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 = p_parent_schema AND c.relname = p_parent_tablename; IF v_relkind = 'p' THEN RAISE EXCEPTION 'This function cannot run on natively partitioned tables'; ELSIF v_relkind IS NULL THEN RAISE EXCEPTION 'Unable to find given table in system catalogs: %.%', p_parent_schema, p_parent_tablename; END IF; WITH parent_info AS ( SELECT c.oid AS parent_oid FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = p_parent_schema::name AND c.relname = p_parent_tablename::name ) SELECT substring(pg_get_indexdef(i.indexrelid) from ' USING .*$') AS index_def INTO v_parent_indexdef FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid JOIN parent_info p ON p.parent_oid = indrelid WHERE i.indisclustered = true; -- Loop over all existing indexes in child table to find one with matching definition FOR v_row IN WITH child_info AS ( SELECT c.oid AS child_oid 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 ) SELECT substring(pg_get_indexdef(i.indexrelid) from ' USING .*$') AS child_indexdef , c.relname AS child_indexname FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid JOIN child_info p ON p.child_oid = indrelid LOOP IF v_row.child_indexdef = v_parent_indexdef THEN v_sql = format('ALTER TABLE %I.%I CLUSTER ON %I', p_child_schema, p_child_tablename, v_row.child_indexname); RAISE DEBUG '%', v_sql; EXECUTE v_sql; END IF; END LOOP; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END; $$; pg_partman-4.6.0/sql/functions/apply_constraints.sql000066400000000000000000000255431412761706100227730ustar00rootroot00000000000000CREATE FUNCTION @extschema@.apply_constraints(p_parent_table text, p_child_table text DEFAULT NULL, p_analyze boolean DEFAULT FALSE, p_job_id bigint DEFAULT NULL) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_child_exists text; v_child_tablename text; v_col text; v_constraint_cols text[]; v_constraint_col_type text; v_constraint_name text; v_constraint_valid boolean; v_constraint_values record; v_control text; v_control_type text; v_datetime_string text; v_epoch text; v_existing_constraint_name text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_id bigint; v_last_partition_timestamp timestamptz; v_max_id bigint; v_max_timestamp timestamptz; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_optimize_constraint int; v_parent_schema text; v_parent_table text; v_parent_tablename text; v_partition_interval text; v_partition_suffix text; v_premake int; v_sql text; v_step_id bigint; v_suffix_position int; v_type text; BEGIN /* * Apply constraints managed by partman extension */ SELECT parent_table , partition_type , control , premake , partition_interval , optimize_constraint , epoch , datetime_string , constraint_cols , jobmon , constraint_valid INTO v_parent_table , v_type , v_control , v_premake , v_partition_interval , v_optimize_constraint , v_epoch , v_datetime_string , v_constraint_cols , v_jobmon , v_constraint_valid FROM @extschema@.part_config WHERE parent_table = p_parent_table AND constraint_cols IS NOT NULL; IF v_constraint_cols IS NULL THEN RAISE DEBUG 'apply_constraints: Given parent table (%) not set up for constraint management (constraint_cols is NULL)', p_parent_table; -- Returns silently to allow this function to be simply called by maintenance processes without having to check if config options are set. RETURN; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(v_parent_table, '.', 1)::name AND tablename = split_part(v_parent_table, '.', 2)::name; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE CONSTRAINT: %s', v_parent_table)); ELSE v_job_id = p_job_id; END IF; END IF; -- If p_child_table is null, figure out the partition that is the one right before the optimize_constraint value backwards. IF p_child_table IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying additional constraints: Automatically determining most recent child on which to apply constraints'); END IF; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(v_parent_table, 'DESC') LIMIT 1; IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN SELECT child_start_time INTO v_last_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_partition_interval, v_parent_table); v_partition_suffix := to_char(v_last_partition_timestamp - (v_partition_interval::interval * (v_optimize_constraint + v_premake + 1) ), v_datetime_string); ELSIF v_control_type = 'id' THEN SELECT child_start_id INTO v_last_partition_id FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_partition_interval, v_parent_table); v_partition_suffix := (v_last_partition_id - (v_partition_interval::bigint * (v_optimize_constraint + v_premake + 1) ))::text; END IF; RAISE DEBUG 'apply_constraint: v_parent_tablename: %, v_last_partition: %, v_last_partition_timestamp: %, v_partition_suffix: %' , v_parent_tablename, v_last_partition, v_last_partition_timestamp, v_partition_suffix; v_child_tablename := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Target child table: %s.%s', v_parent_schema, v_child_tablename)); END IF; ELSE v_child_tablename = split_part(p_child_table, '.', 2); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying additional constraints: Checking if target child table exists'); END IF; SELECT tablename FROM pg_catalog.pg_tables INTO v_child_exists WHERE schemaname = v_parent_schema::name AND tablename = v_child_tablename::name; IF v_child_exists IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Target child table (%s) does not exist. Skipping constraint creation.', v_child_tablename)); IF p_job_id IS NULL THEN PERFORM close_job(v_job_id); END IF; END IF; RAISE DEBUG 'Target child table (%) does not exist. Skipping constraint creation.', v_child_tablename; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; FOREACH v_col IN ARRAY v_constraint_cols LOOP SELECT con.conname INTO v_existing_constraint_name FROM pg_catalog.pg_constraint con JOIN pg_class c ON c.oid = con.conrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN pg_catalog.pg_attribute a ON con.conrelid = a.attrelid WHERE c.relname = v_child_tablename::name AND n.nspname = v_parent_schema::name AND con.conname LIKE 'partmanconstr_%' AND con.contype = 'c' AND a.attname = v_col::name AND ARRAY[a.attnum] OPERATOR(pg_catalog.<@) con.conkey AND a.attisdropped = false; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Applying additional constraints: Applying new constraint on column: %s', v_col)); END IF; IF v_existing_constraint_name IS NOT NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Partman managed constraint already exists on this table (%s) and column (%s). Skipping creation.', v_child_tablename, v_col)); END IF; RAISE DEBUG 'Partman managed constraint already exists on this table (%) and column (%). Skipping creation.', v_child_tablename, v_col ; CONTINUE; END IF; -- Ensure column name gets put on end of constraint name to help avoid naming conflicts v_constraint_name := @extschema@.check_name_length('partmanconstr_'||v_child_tablename, p_suffix := '_'||v_col); EXECUTE format('SELECT min(%I)::text AS min, max(%I)::text AS max FROM %I.%I', v_col, v_col, v_parent_schema, v_child_tablename) INTO v_constraint_values; IF v_constraint_values IS NOT NULL THEN v_sql := format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I <= %L)' , v_parent_schema , v_child_tablename , v_constraint_name , v_col , v_constraint_values.min , v_col , v_constraint_values.max); IF v_constraint_valid = false THEN v_sql := format('%s NOT VALID', v_sql); END IF; RAISE DEBUG 'Constraint creation query: %', v_sql; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('New constraint created: %s', v_sql)); END IF; ELSE RAISE DEBUG 'Given column (%) contains all NULLs. No constraint created', v_col; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Given column (%s) contains all NULLs. No constraint created', v_col)); END IF; END IF; END LOOP; IF p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Applying additional constraints: Running analyze on partition set: %s', v_parent_table)); END IF; RAISE DEBUG 'Running analyze on partition set: %', v_parent_table; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); 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 CONSTRAINT: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; pg_partman-4.6.0/sql/functions/apply_foreign_keys.sql000066400000000000000000000122131412761706100230760ustar00rootroot00000000000000CREATE FUNCTION @extschema@.apply_foreign_keys(p_parent_table text, p_child_table text, p_job_id bigint DEFAULT NULL, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_count int := 0; v_job_id bigint; v_jobmon text; v_jobmon_schema text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_ref_schema text; v_ref_table text; v_relkind char; v_row record; v_schemaname text; v_sql text; v_step_id bigint; v_tablename text; BEGIN /* * Apply foreign keys that exist on the given parent to the given child table */ SELECT jobmon INTO v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; SELECT n.nspname, c.relname, c.relkind INTO v_parent_schema, v_parent_tablename, v_relkind 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_relkind = 'p' THEN RAISE EXCEPTION 'This function cannot run on natively partitioned tables'; ELSIF v_relkind IS NULL THEN RAISE EXCEPTION 'Unable to find given table in system catalogs: %.%', v_parent_schema, v_parent_tablename; END IF; SELECT n.nspname, c.relname INTO v_schemaname, v_tablename FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = split_part(p_child_table, '.', 1)::name AND c.relname = split_part(p_child_table, '.', 2)::name; IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN APPLYING FOREIGN KEYS: %s', p_parent_table)); ELSE -- Don't create a new job, add steps into given job v_job_id := p_job_id; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Applying foreign keys to %s if they exist on parent', p_child_table)); END IF; IF v_tablename IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'CRITICAL', format('Target child table (%s) does not exist.', p_child_table)); PERFORM fail_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RAISE EXCEPTION 'Target child table (%) does not exist.', p_child_table; RETURN; END IF; FOR v_row 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 JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_parent_tablename AND n.nspname = v_parent_schema AND contype = 'f' LOOP v_sql := format('ALTER TABLE %I.%I ADD %s' , v_schemaname , v_tablename , v_row.constraint_def); IF p_debug THEN RAISE NOTICE 'Constraint creation query: %', v_sql; END IF; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'FK applied'); END IF; v_count := v_count + 1; END LOOP; IF v_count = 0 AND v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'No FKs found on parent'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; 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 APPLYING FOREIGN KEYS: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; pg_partman-4.6.0/sql/functions/apply_privileges.sql000066400000000000000000000164501412761706100225720ustar00rootroot00000000000000CREATE FUNCTION @extschema@.apply_privileges(p_parent_schema text, p_parent_tablename text, p_child_schema text, p_child_tablename text, p_job_id bigint DEFAULT NULL) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_child_grant record; v_child_owner text; v_grantees text[]; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_match boolean; v_parent_grant record; v_parent_owner text; v_revoke text; v_row_revoke record; v_sql text; v_step_id bigint; BEGIN /* * Apply privileges and ownership that exist on a given parent to the given child table */ SELECT jobmon INTO v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_schema ||'.'|| p_parent_tablename; IF v_jobmon IS NULL THEN RAISE EXCEPTION 'Given table is not managed by this extention: %.%', p_parent_schema, p_parent_tablename; END IF; 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 = p_parent_schema::name AND c.relname = p_parent_tablename::name; SELECT pg_get_userbyid(c.relowner) INTO v_child_owner 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_parent_owner IS NULL THEN RAISE EXCEPTION 'Given parent table does not exist: %.%', p_parent_schema, p_parent_tablename; END IF; IF v_child_owner IS NULL THEN RAISE EXCEPTION 'Given child table does not exist: %.%', p_child_schema, p_child_tablename; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; END IF; IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN EXECUTE format('SELECT %I.add_job(%L)', v_jobmon_schema, format('PARTMAN APPLYING PRIVILEGES TO CHILD TABLE: %s.%s', p_child_schema, p_child_tablename)) INTO v_job_id; ELSE v_job_id := p_job_id; END IF; EXECUTE format('SELECT %I.add_step(%L, %L)', v_jobmon_schema, v_job_id, format('Setting new child table privileges for %s.%s', p_child_schema, p_child_tablename)) INTO v_step_id; END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.update_step(%L, %L, %L)' , v_jobmon_schema , v_step_id , 'PENDING' , format('Applying privileges on child partition: %s.%s' , p_child_schema , p_child_tablename) ); END IF; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types , grantee FROM @extschema@.table_privs WHERE table_schema = p_parent_schema::name AND table_name = p_parent_tablename::name GROUP BY grantee LOOP -- Compare parent & child grants. Don't re-apply if it already exists v_match := false; v_sql := NULL; FOR v_child_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types , grantee FROM @extschema@.table_privs WHERE table_schema = p_child_schema::name AND table_name = p_child_tablename::name GROUP BY grantee LOOP IF v_parent_grant.types = v_child_grant.types AND v_parent_grant.grantee = v_child_grant.grantee THEN v_match := true; END IF; END LOOP; IF v_match = false THEN IF v_parent_grant.grantee = 'PUBLIC' THEN v_sql := 'GRANT %s ON %I.%I TO %s'; ELSE v_sql := 'GRANT %s ON %I.%I TO %I'; END IF; EXECUTE format(v_sql , array_to_string(v_parent_grant.types, ',') , p_child_schema , p_child_tablename , v_parent_grant.grantee); v_sql := NULL; SELECT string_agg(r, ',') INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN IF v_parent_grant.grantee = 'PUBLIC' THEN v_sql := 'REVOKE %s ON %I.%I FROM %s CASCADE'; ELSE v_sql := 'REVOKE %s ON %I.%I FROM %I CASCADE'; END IF; EXECUTE format(v_sql , v_revoke , p_child_schema , p_child_tablename , v_parent_grant.grantee); v_sql := NULL; END IF; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN FOR v_row_revoke IN SELECT role FROM ( SELECT DISTINCT grantee::text AS role FROM @extschema@.table_privs WHERE table_schema = p_child_schema::name AND table_name = p_child_tablename::name EXCEPT SELECT unnest(v_grantees)) x LOOP IF v_row_revoke.role IS NOT NULL THEN IF v_row_revoke.role = 'PUBLIC' THEN v_sql := 'REVOKE ALL ON %I.%I FROM %s'; ELSE v_sql := 'REVOKE ALL ON %I.%I FROM %I'; END IF; EXECUTE format(v_sql , p_child_schema , p_child_tablename , v_row_revoke.role); END IF; END LOOP; END IF; IF v_parent_owner <> v_child_owner THEN EXECUTE format('ALTER TABLE %I.%I OWNER TO %I' , p_child_schema , p_child_tablename , v_parent_owner); END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.update_step(%L, %L, %L)', v_jobmon_schema, v_step_id, 'OK', 'Done'); IF p_job_id IS NULL THEN EXECUTE format('SELECT %I.close_job(%L)', v_jobmon_schema, v_job_id); END IF; END IF; 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 RE-APPLYING PRIVILEGES TO ALL CHILD TABLES OF: %s'')', v_jobmon_schema, p_parent_tablename) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', v_jobmon_schema, v_job_id, p_parent_tablename) 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 $$; pg_partman-4.6.0/sql/functions/apply_publications.sql000066400000000000000000000014131412761706100231060ustar00rootroot00000000000000CREATE FUNCTION @extschema@.apply_publications(p_parent_table text, p_child_schema text, p_child_tablename text) RETURNS void LANGUAGE plpgsql AS $$ DECLARE v_publications text[]; v_row record; v_sql text; BEGIN /* * Function to ATLER PUBLICATION ... ADD TABLE to support logical replication */ SELECT c.publications INTO v_publications FROM @extschema@.part_config c WHERE c.parent_table = p_parent_table; -- Loop over all publicaions which the table needs to be added to FOR v_row IN SELECT pubname FROM unnest(v_publications) AS pubname LOOP v_sql = format('ALTER PUBLICATION %I ADD TABLE %I.%I', v_row.pubname, p_child_schema, p_child_tablename); RAISE DEBUG '%', v_sql; EXECUTE v_sql; END LOOP; END; $$; pg_partman-4.6.0/sql/functions/autovacuum_off.sql000066400000000000000000000022711412761706100222330ustar00rootroot00000000000000CREATE FUNCTION @extschema@.autovacuum_off(p_parent_schema text, p_parent_tablename text, p_source_schema text DEFAULT NULL, p_source_tablename text DEFAULT NULL) RETURNS boolean LANGUAGE plpgsql AS $$ DECLARE v_row record; v_sql text; BEGIN v_sql = format('ALTER TABLE %I.%I SET (autovacuum_enabled = false, toast.autovacuum_enabled = false)', p_parent_schema, p_parent_tablename); RAISE DEBUG 'partition_data sql: %', v_sql; EXECUTE v_sql; IF p_source_tablename IS NOT NULL THEN v_sql = format('ALTER TABLE %I.%I SET (autovacuum_enabled = false, toast.autovacuum_enabled = false)', p_source_schema, p_source_tablename); RAISE DEBUG 'partition_data sql: %', v_sql; EXECUTE v_sql; END IF; FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_schema||'.'||p_parent_tablename, 'ASC') LOOP v_sql = format('ALTER TABLE %I.%I SET (autovacuum_enabled = false, toast.autovacuum_enabled = false)', v_row.partition_schemaname, v_row.partition_tablename); RAISE DEBUG 'partition_data sql: %', v_sql; EXECUTE v_sql; END LOOP; RETURN true; END $$; pg_partman-4.6.0/sql/functions/autovacuum_reset.sql000066400000000000000000000022171412761706100226030ustar00rootroot00000000000000CREATE FUNCTION @extschema@.autovacuum_reset(p_parent_schema text, p_parent_tablename text, p_source_schema text DEFAULT NULL, p_source_tablename text DEFAULT NULL) RETURNS boolean LANGUAGE plpgsql AS $$ DECLARE v_row record; v_sql text; BEGIN v_sql = format('ALTER TABLE %I.%I RESET (autovacuum_enabled, toast.autovacuum_enabled)', p_parent_schema, p_parent_tablename); RAISE DEBUG 'partition_data sql: %', v_sql; EXECUTE v_sql; IF p_source_tablename IS NOT NULL THEN v_sql = format('ALTER TABLE %I.%I RESET (autovacuum_enabled, toast.autovacuum_enabled)', p_source_schema, p_source_tablename); RAISE DEBUG 'partition_data sql: %', v_sql; EXECUTE v_sql; END IF; FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_schema||'.'||p_parent_tablename, 'ASC') LOOP v_sql = format('ALTER TABLE %I.%I RESET (autovacuum_enabled, toast.autovacuum_enabled)', v_row.partition_schemaname, v_row.partition_tablename); RAISE DEBUG 'partition_data sql: %', v_sql; EXECUTE v_sql; END LOOP; RETURN true; END $$; pg_partman-4.6.0/sql/functions/check_control_type.sql000066400000000000000000000015021412761706100230620ustar00rootroot00000000000000CREATE FUNCTION @extschema@.check_control_type(p_parent_schema text, p_parent_tablename text, p_control text) RETURNS TABLE (general_type text, exact_type text) LANGUAGE sql STABLE AS $$ /* * Return column type for given table & column in that table * Returns NULL of objects don't exist */ SELECT CASE WHEN typname IN ('timestamptz', 'timestamp', 'date') THEN 'time' WHEN typname IN ('int2', 'int4', 'int8') THEN 'id' END , typname::text FROM pg_catalog.pg_type t JOIN pg_catalog.pg_attribute a ON t.oid = a.atttypid JOIN pg_catalog.pg_class c ON a.attrelid = c.oid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = p_parent_schema::name AND c.relname = p_parent_tablename::name AND a.attname = p_control::name $$; pg_partman-4.6.0/sql/functions/check_default.sql000066400000000000000000000057311412761706100217750ustar00rootroot00000000000000CREATE FUNCTION @extschema@.check_default(p_exact_count boolean DEFAULT true) RETURNS SETOF @extschema@.check_default_table LANGUAGE plpgsql STABLE SET search_path = @extschema@,pg_temp AS $$ DECLARE v_count bigint = 0; v_default_schemaname text; v_default_tablename text; v_parent_schemaname text; v_parent_tablename text; v_row record; v_sql text; v_trouble @extschema@.check_default_table%rowtype; BEGIN /* * Function to monitor for data getting inserted into parent/default tables */ FOR v_row IN SELECT parent_table, partition_type FROM @extschema@.part_config LOOP SELECT schemaname, tablename INTO v_parent_schemaname, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(v_row.parent_table, '.', 1)::name AND tablename = split_part(v_row.parent_table, '.', 2)::name; IF v_row.partition_type = 'partman' THEN -- trigger based checks parent table IF p_exact_count THEN v_sql := format('SELECT count(1) AS n FROM ONLY %I.%I', v_parent_schemaname, v_parent_tablename); ELSE v_sql := format('SELECT count(1) AS n FROM (SELECT 1 FROM ONLY %I.%I LIMIT 1) x', v_parent_schemaname, v_parent_tablename); END IF; EXECUTE v_sql INTO v_count; IF v_count > 0 THEN v_trouble.default_table := v_parent_schemaname ||'.'|| v_parent_tablename; v_trouble.count := v_count; RETURN NEXT v_trouble; END IF; ELSIF v_row.partition_type = 'native' AND current_setting('server_version_num')::int >= 110000 THEN -- native PG11+ checks default if it exists v_sql := format('SELECT n.nspname::text, c.relname::text FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = ''%I.%I''::regclass AND pg_get_expr(relpartbound, c.oid) = ''DEFAULT''' , v_parent_schemaname , v_parent_tablename); EXECUTE v_sql INTO v_default_schemaname, v_default_tablename; IF v_default_schemaname IS NOT NULL AND v_default_tablename IS NOT NULL THEN IF p_exact_count THEN v_sql := format('SELECT count(1) AS n FROM ONLY %I.%I', v_default_schemaname, v_default_tablename); ELSE v_sql := format('SELECT count(1) AS n FROM (SELECT 1 FROM ONLY %I.%I LIMIT 1) x', v_default_schemaname, v_default_tablename); END IF; EXECUTE v_sql INTO v_count; IF v_count > 0 THEN v_trouble.default_table := v_default_schemaname ||'.'|| v_default_tablename; v_trouble.count := v_count; RETURN NEXT v_trouble; END IF; END IF; END IF; v_count := 0; END LOOP; RETURN; END $$; pg_partman-4.6.0/sql/functions/check_name_length.sql000066400000000000000000000031171412761706100226260ustar00rootroot00000000000000CREATE 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 $$; pg_partman-4.6.0/sql/functions/check_subpart_sameconfig.sql000066400000000000000000000062521412761706100242230ustar00rootroot00000000000000CREATE FUNCTION @extschema@.check_subpart_sameconfig(p_parent_table text) RETURNS TABLE (sub_partition_type text , sub_control text , sub_partition_interval text , sub_constraint_cols text[] , sub_premake int , sub_optimize_trigger int , sub_optimize_constraint int , sub_epoch text , sub_inherit_fk boolean , sub_retention text , sub_retention_schema text , sub_retention_keep_table boolean , sub_retention_keep_index boolean , sub_infinite_time_partitions boolean , sub_automatic_maintenance text , sub_jobmon boolean , sub_trigger_exception_handling boolean , sub_upsert text , sub_trigger_return_null boolean , sub_template_table text , sub_inherit_privileges boolean , sub_constraint_valid boolean , sub_subscription_refresh text , sub_date_trunc_interval text) LANGUAGE sql STABLE SET search_path = @extschema@,pg_temp AS $$ /* * Check for consistent data in part_config_sub table. Was unable to get this working properly as either a constraint or trigger. * Would either delay raising an error until the next write (which I cannot predict) or disallow future edits to update a sub-partition set's configuration. * This is called by run_maintainance() and at least provides a consistent way to check that I know will run. * If anyone can get a working constraint/trigger, please help! */ WITH parent_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE n1.nspname = split_part(p_parent_table, '.', 1)::name AND c1.relname = split_part(p_parent_table, '.', 2)::name ) , child_tables AS ( SELECT n.nspname||'.'||c.relname AS tablename FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN parent_info pi ON h.inhparent = pi.oid ) -- Column order here must match the RETURNS TABLE definition -- 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 a.sub_partition_type , a.sub_control , a.sub_partition_interval , a.sub_constraint_cols , a.sub_premake , a.sub_optimize_trigger , a.sub_optimize_constraint , a.sub_epoch , a.sub_inherit_fk , a.sub_retention , a.sub_retention_schema , a.sub_retention_keep_table , a.sub_retention_keep_index , a.sub_infinite_time_partitions , a.sub_automatic_maintenance , a.sub_jobmon , a.sub_trigger_exception_handling , a.sub_upsert , a.sub_trigger_return_null , a.sub_template_table , a.sub_inherit_privileges , a.sub_constraint_valid , a.sub_subscription_refresh , a.sub_date_trunc_interval FROM @extschema@.part_config_sub a JOIN child_tables b on a.sub_parent = b.tablename; $$; pg_partman-4.6.0/sql/functions/check_subpartition_limits.sql000066400000000000000000000052201412761706100244460ustar00rootroot00000000000000CREATE FUNCTION @extschema@.check_subpartition_limits(p_parent_table text, p_type text, OUT sub_min text, OUT sub_max text) RETURNS record LANGUAGE plpgsql AS $$ DECLARE v_parent_schema text; v_parent_tablename text; v_top_control text; v_top_control_type text; v_top_epoch text; v_top_interval text; v_top_schema text; v_top_tablename text; BEGIN /* * Check if parent table is a subpartition of an already existing partition set managed by pg_partman * If so, return the limits of what child tables can be created under the given parent table based on its own suffix * If not, return NULL. Allows caller to check for NULL and then know if the given parent has sub-partition limits. */ SELECT n.nspname, c.relname INTO v_parent_schema, v_parent_tablename 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; WITH top_oid AS ( SELECT i.inhparent AS top_parent_oid FROM pg_catalog.pg_class c JOIN pg_catalog.pg_inherits i ON c.oid = i.inhrelid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = v_parent_schema AND c.relname = v_parent_tablename ) SELECT n.nspname, c.relname, p.partition_interval, p.control, p.epoch INTO v_top_schema, v_top_tablename, v_top_interval, v_top_control, v_top_epoch FROM pg_catalog.pg_class c JOIN top_oid t ON c.oid = t.top_parent_oid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace JOIN @extschema@.part_config p ON p.parent_table = n.nspname||'.'||c.relname WHERE c.oid = t.top_parent_oid; SELECT general_type INTO v_top_control_type FROM @extschema@.check_control_type(v_top_schema, v_top_tablename, v_top_control); IF v_top_control_type = 'id' AND v_top_epoch <> 'none' THEN v_top_control_type := 'time'; END IF; -- If sub-partition is different type than top parent, no need to set limits IF p_type = v_top_control_type THEN IF p_type = 'time' THEN SELECT child_start_time::text, child_end_time::text INTO sub_min, sub_max FROM @extschema@.show_partition_info(p_parent_table, v_top_interval, v_top_schema||'.'||v_top_tablename); ELSIF p_type = 'id' THEN SELECT child_start_id::text, child_end_id::text INTO sub_min, sub_max FROM @extschema@.show_partition_info(p_parent_table, v_top_interval, v_top_schema||'.'||v_top_tablename); ELSE RAISE EXCEPTION 'Reached unknown state in check_subpartition_limits(). Please report what lead to this condition to author'; END IF; END IF; RETURN; END $$; pg_partman-4.6.0/sql/functions/create_function_id.sql000066400000000000000000000306401412761706100230350ustar00rootroot00000000000000CREATE FUNCTION @extschema@.create_function_id(p_parent_table text, p_job_id bigint DEFAULT NULL) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_control text; v_control_type text; v_count int; v_current_partition_name text; v_current_partition_id bigint; v_datetime_string text; v_final_partition_id bigint; v_function_name 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_position int; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_max bigint; v_next_partition_id bigint; v_next_partition_name text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_optimize_trigger int; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_premake int; v_prev_partition_id bigint; v_prev_partition_name text; v_relkind char; v_row_max_id record; v_run_maint text; v_step_id bigint; v_top_parent text := p_parent_table; v_trig_func text; v_trigger_exception_handling boolean; v_trigger_return_null boolean; v_upsert text; BEGIN /* * Create the trigger function for the parent table of an id-based partition set */ SELECT partition_interval::bigint , control , premake , optimize_trigger , automatic_maintenance , jobmon , trigger_exception_handling , upsert , trigger_return_null INTO v_partition_interval , v_control , v_premake , v_optimize_trigger , v_run_maint , v_jobmon , v_trigger_exception_handling , v_upsert , v_trigger_return_null FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'partman'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no non-native pg_partman config found for %', p_parent_table; END IF; SELECT n.nspname, c.relname, c.relkind INTO v_parent_schema, v_parent_tablename, v_relkind 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_relkind = 'p' THEN RAISE EXCEPTION 'This function cannot run on natively partitioned tables'; ELSIF v_relkind IS NULL THEN RAISE EXCEPTION 'Unable to find given table in system catalogs: %.%', v_parent_schema, v_parent_tablename; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run create_function_id on partition set without id based control column. Found: %', v_control_type; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE FUNCTION: %s', p_parent_table)); ELSE v_job_id = p_job_id; END IF; v_step_id := add_step(v_job_id, format('Creating partition function for table %s', p_parent_table)); END IF; -- Get the highest level top parent if multi-level partitioned in order to get proper max() value below 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 INTO v_higher_parent_schema, v_higher_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 WHERE p.partition_type = 'id'; IF v_higher_parent_table IS NOT NULL THEN -- initially set in DECLARE v_top_parent := v_higher_parent_schema||'.'||v_higher_parent_table; END IF; END LOOP; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_top_parent, 'DESC') LOOP EXECUTE format('SELECT max(%I) FROM %I.%I', v_control, v_row_max_id.partition_schemaname, v_row_max_id.partition_tablename) INTO v_max; IF v_max IS NOT NULL THEN EXIT; END IF; END LOOP; IF v_max IS NULL THEN v_max := 0; END IF; v_current_partition_id = v_max - (v_max % v_partition_interval); v_next_partition_id := v_current_partition_id + v_partition_interval; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_current_partition_id::text, TRUE); v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_current_partition_id bigint; v_current_partition_name text; v_id_position int; v_last_partition text := %L; v_next_partition_id bigint; v_next_partition_name text; v_partition_created boolean; BEGIN IF TG_OP = ''INSERT'' THEN IF NEW.%I >= %s AND NEW.%I < %s THEN ' , v_parent_schema , v_function_name , v_last_partition , v_control , v_current_partition_id , v_control , v_next_partition_id ); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_current_partition_name::name; IF v_count > 0 THEN v_trig_func := v_trig_func || format(' INSERT INTO %I.%I VALUES (NEW.*) %s; ', v_parent_schema, v_current_partition_name, v_upsert); ELSE v_trig_func := v_trig_func || ' -- Child table for current values does not exist in this partition set, so write to parent RETURN NEW;'; END IF; FOR i IN 1..v_optimize_trigger LOOP v_prev_partition_id := v_current_partition_id - (v_partition_interval * i); v_next_partition_id := v_current_partition_id + (v_partition_interval * i); v_final_partition_id := v_next_partition_id + v_partition_interval; v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, v_prev_partition_id::text, TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, v_next_partition_id::text, TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles optimize_trigger being larger than premake (to go back in time further) and edge case of changing optimize_trigger immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_prev_partition_name::name; IF v_count > 0 THEN -- Only handle previous partitions if they're starting above zero IF v_prev_partition_id >= 0 THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %s AND NEW.%I < %s THEN INSERT INTO %I.%I VALUES (NEW.*) %s; ' , v_control , v_prev_partition_id , v_control , v_prev_partition_id + v_partition_interval , v_parent_schema , v_prev_partition_name , v_upsert ); END IF; END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_next_partition_name::name; IF v_count > 0 THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %s AND NEW.%I < %s THEN INSERT INTO %I.%I VALUES (NEW.*) %s;' , v_control , v_next_partition_id , v_control , v_final_partition_id , v_parent_schema , v_next_partition_name , v_upsert ); END IF; END LOOP; v_trig_func := v_trig_func ||format(' ELSE v_current_partition_id := NEW.%I - (NEW.%I %% %s); v_current_partition_name := @extschema@.check_name_length(%L, v_current_partition_id::text, TRUE); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = %L::name AND tablename = v_current_partition_name::name; IF v_count > 0 THEN EXECUTE format(''INSERT INTO %%I.%%I VALUES($1.*) %s'', %L, v_current_partition_name) USING NEW; ELSE RETURN NEW; END IF; END IF;' , v_control , v_control , v_partition_interval , v_parent_tablename , v_parent_schema , v_upsert , v_parent_schema ); v_trig_func := v_trig_func ||' END IF;'; IF v_trigger_return_null IS TRUE THEN v_trig_func := v_trig_func ||' RETURN NULL;'; ELSE v_trig_func := v_trig_func ||' RETURN NEW;'; END IF; IF v_trigger_exception_handling THEN v_trig_func := v_trig_func ||' EXCEPTION WHEN OTHERS THEN RAISE WARNING ''pg_partman insert into child table failed, row inserted into parent (%.%). ERROR: %'', TG_TABLE_SCHEMA, TG_TABLE_NAME, COALESCE(SQLERRM, ''unknown''); RETURN NEW;'; END IF; v_trig_func := v_trig_func ||' END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for current id interval: %s to %s', v_current_partition_id, v_final_partition_id-1)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); 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 FUNCTION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''Partition function maintenance for table %s 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 $$; pg_partman-4.6.0/sql/functions/create_function_time.sql000066400000000000000000000375661412761706100234150ustar00rootroot00000000000000CREATE FUNCTION @extschema@.create_function_time(p_parent_table text, p_job_id bigint DEFAULT NULL) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_control text; v_control_type text; v_count int; v_current_partition_name text; v_current_partition_timestamp timestamptz; v_datetime_string text; v_epoch text; v_final_partition_timestamp timestamptz; v_function_name text; v_infinite_time_partitions boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_new_length int; v_next_partition_name text; v_next_partition_timestamp timestamptz; v_parent_schema text; v_parent_tablename text; v_partition_expression text; v_partition_interval interval; v_prev_partition_name text; v_prev_partition_timestamp timestamptz; v_relkind char; v_row_max_time record; v_step_id bigint; v_trig_func text; v_optimize_trigger int; v_table_exists boolean; v_trigger_exception_handling boolean; v_trigger_return_null boolean; v_type text; v_upsert text; BEGIN /* * Create the trigger function for the parent table of a time-based partition set */ SELECT partition_type , partition_interval::interval , epoch , control , optimize_trigger , datetime_string , jobmon , trigger_exception_handling , upsert , trigger_return_null , infinite_time_partitions INTO v_type , v_partition_interval , v_epoch , v_control , v_optimize_trigger , v_datetime_string , v_jobmon , v_trigger_exception_handling , v_upsert , v_trigger_return_null , v_infinite_time_partitions FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'partman' OR partition_type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no non-native pg_partman config found for %', p_parent_table; END IF; SELECT n.nspname, c.relname, c.relkind INTO v_parent_schema, v_parent_tablename, v_relkind 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_relkind = 'p' THEN RAISE EXCEPTION 'This function cannot run on natively partitioned tables'; ELSIF v_relkind IS NULL THEN RAISE EXCEPTION 'Unable to find given table in system catalogs: %.%', v_parent_schema, v_parent_tablename; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'time' THEN IF (v_control_type = 'id' AND v_epoch = 'none') OR v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run on partition set without time based control column or epoch flag set with an id column. Found control: %, epoch: %', v_control_type, v_epoch; END IF; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE FUNCTION: %s', p_parent_table)); ELSE v_job_id = p_job_id; END IF; v_step_id := add_step(v_job_id, format('Creating partition function for table %s', p_parent_table)); END IF; IF v_infinite_time_partitions IS TRUE THEN -- Set it to "now" to line up with maintenance always making new partitions despite no new data -- Also, don't need to bother getting the max value in the partitions v_current_partition_timestamp := CURRENT_TIMESTAMP; ELSE v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) END; FOR v_row_max_time IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%s)::text FROM %I.%I' , v_partition_expression , v_row_max_time.partition_schemaname , v_row_max_time.partition_tablename ) INTO v_current_partition_timestamp; IF v_current_partition_timestamp IS NOT NULL THEN EXIT; END IF; END LOOP; IF v_current_partition_timestamp IS NULL THEN v_current_partition_timestamp := CURRENT_TIMESTAMP; END IF; END IF; -- end infinite time check -- Reset for use in trigger function v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(NEW.%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((NEW.%I/1000)::float)', v_control) ELSE format('NEW.%I', v_control) END; IF v_type = 'partman' THEN v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_partition_name text; v_partition_timestamp timestamptz; BEGIN IF TG_OP = ''INSERT'' THEN ' , v_parent_schema , v_function_name); SELECT suffix_timestamp, partition_table, table_exists INTO v_current_partition_timestamp, v_current_partition_name, v_table_exists FROM @extschema@.show_partition_name(p_parent_table, v_current_partition_timestamp::text); CASE WHEN v_partition_interval = '15 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', %s) + ''15min''::interval * floor(date_part(''minute'', %1$s) / 15.0);' , v_partition_expression); WHEN v_partition_interval = '30 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', %s) + ''30min''::interval * floor(date_part(''minute'', %1$s) / 30.0);' , v_partition_expression); WHEN v_partition_interval = '1 hour' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', %s);', v_partition_expression); WHEN v_partition_interval = '1 day' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''day'', %s);', v_partition_expression); WHEN v_partition_interval = '1 week' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''week'', %s);', v_partition_expression); WHEN v_partition_interval = '1 month' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''month'', %s);', v_partition_expression); WHEN v_partition_interval = '3 months' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''quarter'', %s);', v_partition_expression); WHEN v_partition_interval = '1 year' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''year'', %s);', v_partition_expression); END CASE; v_next_partition_timestamp := v_current_partition_timestamp + v_partition_interval::interval; v_trig_func := v_trig_func ||format(' IF %s >= %L AND %1$s < %3$L THEN ' , v_partition_expression , v_current_partition_timestamp , v_next_partition_timestamp); IF v_table_exists THEN v_trig_func := v_trig_func || format(' INSERT INTO %I.%I VALUES (NEW.*) %s; ', v_parent_schema, v_current_partition_name, v_upsert); ELSE v_trig_func := v_trig_func || ' -- Child table for current values does not exist in this partition set, so write to parent RETURN NEW;'; END IF; FOR i IN 1..v_optimize_trigger LOOP v_prev_partition_timestamp := v_current_partition_timestamp - (v_partition_interval::interval * i); v_next_partition_timestamp := v_current_partition_timestamp + (v_partition_interval::interval * i); v_final_partition_timestamp := v_next_partition_timestamp + (v_partition_interval::interval); v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, to_char(v_prev_partition_timestamp, v_datetime_string), TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, to_char(v_next_partition_timestamp, v_datetime_string), TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles optimize_trigger being larger than premake (to go back in time further) and edge case of changing optimize_trigger immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_prev_partition_name::name; IF v_count > 0 THEN v_trig_func := v_trig_func ||format(' ELSIF %s >= %L AND %1$s < %3$L THEN INSERT INTO %I.%I VALUES (NEW.*) %s;' , v_partition_expression , v_prev_partition_timestamp , v_prev_partition_timestamp + v_partition_interval::interval , v_parent_schema , v_prev_partition_name , v_upsert); END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_next_partition_name::name; IF v_count > 0 THEN v_trig_func := v_trig_func ||format(' ELSIF %s >= %L AND %1$s < %3$L THEN INSERT INTO %I.%I VALUES (NEW.*) %s;' , v_partition_expression , v_next_partition_timestamp , v_final_partition_timestamp , v_parent_schema , v_next_partition_name , v_upsert); END IF; END LOOP; v_trig_func := v_trig_func||format(' ELSE v_partition_name := @extschema@.check_name_length(%L, to_char(v_partition_timestamp, %L), TRUE); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = %L::name AND tablename = v_partition_name::name; IF v_count > 0 THEN EXECUTE format(''INSERT INTO %%I.%%I VALUES($1.*) %s'', %L, v_partition_name) USING NEW; ELSE RETURN NEW; END IF; END IF;' , v_parent_tablename , v_datetime_string , v_parent_schema , v_upsert , v_parent_schema); v_trig_func := v_trig_func ||' END IF;'; IF v_trigger_return_null IS TRUE THEN v_trig_func := v_trig_func ||' RETURN NULL;'; ELSE v_trig_func := v_trig_func ||' RETURN NEW;'; END IF; IF v_trigger_exception_handling THEN v_trig_func := v_trig_func ||' EXCEPTION WHEN OTHERS THEN RAISE WARNING ''pg_partman insert into child table failed, row inserted into parent (%.%). ERROR: %'', TG_TABLE_SCHEMA, TG_TABLE_NAME, COALESCE(SQLERRM, ''unknown''); RETURN NEW;'; END IF; v_trig_func := v_trig_func ||' END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for current time interval: %s to %s' , v_current_partition_timestamp , v_final_partition_timestamp-'1sec'::interval)); END IF; ELSIF v_type = 'time-custom' THEN v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_child_schemaname text; v_child_table text; v_child_tablename text; v_upsert text; BEGIN ' , v_parent_schema , v_function_name); v_trig_func := v_trig_func || format(' SELECT c.child_table, p.upsert INTO v_child_table, v_upsert FROM @extschema@.custom_time_partitions c JOIN @extschema@.part_config p ON c.parent_table = p.parent_table WHERE c.partition_range @> %s AND c.parent_table = %L;' , v_partition_expression , v_parent_schema||'.'||v_parent_tablename); v_trig_func := v_trig_func || ' SELECT schemaname, tablename INTO v_child_schemaname, v_child_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(v_child_table, ''.'', 1)::name AND tablename = split_part(v_child_table, ''.'', 2)::name; IF v_child_schemaname IS NOT NULL AND v_child_tablename IS NOT NULL THEN EXECUTE format(''INSERT INTO %I.%I VALUES ($1.*) %s'', v_child_schemaname, v_child_tablename, v_upsert) USING NEW; ELSE RETURN NEW; END IF;'; IF v_trigger_return_null IS TRUE THEN v_trig_func := v_trig_func ||' RETURN NULL;'; ELSE v_trig_func := v_trig_func ||' RETURN NEW;'; END IF; IF v_trigger_exception_handling THEN v_trig_func := v_trig_func ||' EXCEPTION WHEN OTHERS THEN RAISE WARNING ''pg_partman insert into child table failed, row inserted into parent (%.%). ERROR: %'', TG_TABLE_SCHEMA, TG_TABLE_NAME, COALESCE(SQLERRM, ''unknown''); RETURN NEW;'; END IF; v_trig_func := v_trig_func ||' END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for custom time table: %s', p_parent_table)); END IF; ELSE RAISE EXCEPTION 'ERROR: Invalid time partitioning type given: %', v_type; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); 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 FUNCTION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''Partition function maintenance for table %s 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 $$; pg_partman-4.6.0/sql/functions/create_parent.sql000066400000000000000000001072211412761706100220250ustar00rootroot00000000000000CREATE 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 , p_date_trunc_interval text DEFAULT NULL) 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' AND current_setting('server_version_num')::int >= 110000 THEN RAISE EXCEPTION 'The pg_partman upsert feature is not supported with native partitioning in PG11+. Use the built-in support for INSERT ON CONFLICT with native partitioning instead.'; 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)); v_datetime_string := 'YYYY'; IF p_date_trunc_interval IS NOT NULL THEN v_base_timestamp := date_trunc(p_date_trunc_interval, v_start_time); IF v_time_interval >= '1 day' THEN v_datetime_string := v_datetime_string || '_MM_DD'; ELSE v_datetime_string := v_datetime_string || '_MM_DD_HH24MISS'; END IF; ELSE 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 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 END IF; -- end p_date_trunc_interval IF RAISE DEBUG 'create_parent(): parent_table: %, v_base_timestamp: %', p_parent_table, v_base_timestamp; 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 $$; pg_partman-4.6.0/sql/functions/create_partition_id.sql000066400000000000000000000370051412761706100232230ustar00rootroot00000000000000CREATE FUNCTION @extschema@.create_partition_id(p_parent_table text, p_partition_ids bigint[], p_analyze boolean DEFAULT true, p_start_partition text DEFAULT NULL) RETURNS boolean LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_control_type text; v_exists text; v_grantees text[]; v_hasoids boolean; v_id bigint; v_inherit_fk boolean; v_inherit_privileges boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_grant record; v_parent_schema text; v_parent_tablename text; v_parent_tablespace text; v_partition_interval bigint; v_partition_created boolean := false; v_partition_name text; v_partition_type text; v_publications text[]; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_sub_control text; v_sub_partition_type text; v_sub_id_max bigint; v_sub_id_min bigint; v_template_table text; v_unlogged char; BEGIN /* * Function to create id partitions */ SELECT control , partition_type , partition_interval , inherit_fk , jobmon , template_table , publications , inherit_privileges INTO v_control , v_partition_type , v_partition_interval , v_inherit_fk , v_jobmon , v_template_table , v_publications , v_inherit_privileges FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT n.nspname, c.relname, t.spcname INTO v_parent_schema, v_parent_tablename, v_parent_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 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; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'id' THEN RAISE EXCEPTION 'ERROR: Given parent table is not set up for id/serial partitioning'; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'id'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; FOREACH v_id IN ARRAY p_partition_ids LOOP -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_id_min IS NOT NULL THEN IF v_id < v_sub_id_min OR v_id >= v_sub_id_max THEN CONTINUE; END IF; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_id::text, TRUE); -- If child table already exists, skip creation -- Have to check pg_class because if subpartitioned, table will not be in pg_tables SELECT c.relname INTO v_exists 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_partition_name::name; IF v_exists IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_id||' to '||(v_id + v_partition_interval)-1); END IF; v_sql := 'CREATE'; -- As of PG12, the unlogged/logged status of a native parent table cannot be changed via an ALTER TABLE in order to affect its children. -- As of v4.2x, the unlogged state will be managed via the template table SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c 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; IF v_unlogged = 'u' and v_partition_type != 'native' THEN v_sql := v_sql || ' UNLOGGED'; END IF; -- Close parentheses on LIKE are below due to differing requirements of native subpartitioning -- Same INCLUDING list is used in create_parent() v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING STORAGE INCLUDING COMMENTS ' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); IF current_setting('server_version_num')::int >= 120000 THEN v_sql := v_sql || ' INCLUDING GENERATED '; END IF; SELECT sub_partition_type, sub_control INTO v_sub_partition_type, v_sub_control FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; IF v_sub_partition_type = 'native' THEN -- INCLUDING INDEXES isn't necessary for native partitioning. It isn't supported in v10 and -- for v11+ index inheritance is automatically handled when the partition is attached v_sql := v_sql || format(') PARTITION BY RANGE (%I) ', v_sub_control); ELSE v_sql := v_sql || format(' INCLUDING INDEXES) ', v_sub_control); END IF; IF current_setting('server_version_num')::int < 120000 THEN -- column removed from pgclass in pg12 SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c 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; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; END IF; RAISE DEBUG 'create_partition_id v_sql: %', v_sql; EXECUTE v_sql; IF v_partition_type = 'native' THEN IF current_setting('server_version_num')::int >= 120000 THEN -- PG12 fixed tablespace marking on the parent of a native partition set -- Versions older than 12 handle tablespace setting via inherit_template_properties() call below IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; END IF; IF v_template_table IS NOT NULL THEN PERFORM @extschema@.inherit_template_properties(p_parent_table, v_parent_schema, v_partition_name); END IF; EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , v_id , v_id + v_partition_interval); ELSE -- non-native IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %s AND %I < %s )' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_control , v_id , v_control , v_id + v_partition_interval); EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I', v_parent_schema, v_partition_name, v_parent_schema, v_parent_tablename); -- Indexes cannot be created on the parent, so clustering cannot be used for native yet. PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); -- Foreign keys to other tables not supported on native parent tables IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; END IF; -- NOTE: Privileges not automatically inherited for native. Only do so if config flag is set IF v_partition_type != 'native' OR (v_partition_type = 'native' AND v_inherit_privileges = TRUE) THEN PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition -- 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 FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_infinite_time_partitions , sub_automatic_maintenance , sub_jobmon , sub_trigger_exception_handling , sub_upsert , sub_trigger_return_null , sub_template_table , sub_inherit_privileges , sub_constraint_valid , sub_subscription_refresh , sub_date_trunc_interval FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Subpartitioning '||v_partition_name); END IF; 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_inherit_fk := %L , p_epoch := %L , p_template_table := %L , p_jobmon := %L , p_start_partition := %L , p_date_trunc_interval := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_automatic_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_template_table , v_row.sub_jobmon , p_start_partition , v_row.sub_date_trunc_interval); RAISE DEBUG 'create_partition_id (create_parent loop): %', v_sql; EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions , trigger_exception_handling = v_row.sub_trigger_exception_handling , upsert = v_row.sub_upsert , inherit_privileges = v_row.sub_inherit_privileges , trigger_return_null = v_row.sub_trigger_return_null , constraint_valid = v_row.sub_constraint_valid , subscription_refresh = v_row.sub_subscription_refresh WHERE parent_table = v_parent_schema||'.'||v_partition_name; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; -- end sub partitioning LOOP -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table, p_job_id := v_job_id); IF v_publications IS NOT NULL THEN -- NOTE: Publications currently not supported on parent table, but are supported on the table partitions if individually assigned. PERFORM @extschema@.apply_publications(p_parent_table, v_parent_schema, v_partition_name); END IF; v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s', p_parent_table)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; pg_partman-4.6.0/sql/functions/create_partition_time.sql000066400000000000000000000531041412761706100235630ustar00rootroot00000000000000CREATE FUNCTION @extschema@.create_partition_time(p_parent_table text, p_partition_times timestamptz[], p_analyze boolean DEFAULT true, p_start_partition text DEFAULT NULL) RETURNS boolean LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_control_type text; v_datetime_string text; v_epoch text; v_exists smallint; v_grantees text[]; v_hasoids boolean; v_inherit_privileges boolean; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_grant record; v_parent_schema text; v_parent_tablename text; v_part_col text; v_partition_created boolean := false; v_partition_name text; v_partition_suffix text; v_parent_tablespace text; v_partition_expression text; v_partition_interval interval; v_partition_timestamp_end timestamptz; v_partition_timestamp_start timestamptz; v_publications text[]; v_quarter text; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_sub_control text; v_sub_parent text; v_sub_partition_type text; v_sub_timestamp_max timestamptz; v_sub_timestamp_min timestamptz; v_template_table text; v_trunc_value text; v_time timestamptz; v_partition_type text; v_unlogged char; v_year text; BEGIN /* * Function to create a child table in a time-based partition set */ SELECT partition_type , control , partition_interval , epoch , inherit_fk , jobmon , datetime_string , template_table , publications , inherit_privileges INTO v_partition_type , v_control , v_partition_interval , v_epoch , v_inherit_fk , v_jobmon , v_datetime_string , v_template_table , v_publications , v_inherit_privileges FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT n.nspname, c.relname, t.spcname INTO v_parent_schema, v_parent_tablename, v_parent_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 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; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'time' THEN IF (v_control_type = 'id' AND v_epoch = 'none') OR v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run on partition set without time based control column or epoch flag set with an id column. Found control: %, epoch: %', v_control_type, v_epoch; END IF; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::timestamptz, sub_max::timestamptz INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'time'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) END; RAISE DEBUG 'create_partition_time: v_partition_expression: %', v_partition_expression; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_timestamp_start := v_time; BEGIN v_partition_timestamp_end := v_time + v_partition_interval; 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_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_time||' skipped'); CONTINUE; END; -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_timestamp_min IS NOT NULL THEN IF v_time < v_sub_timestamp_min OR v_time >= v_sub_timestamp_max THEN RAISE DEBUG 'create_partition_time: p_parent_table: %, v_time: %, v_sub_timestamp_min: %, v_sub_timestamp_max: %' , p_parent_table, v_time, v_sub_timestamp_min, v_sub_timestamp_max; CONTINUE; END IF; END IF; -- This suffix generation code is in partition_data_time() as well v_partition_suffix := to_char(v_time, v_datetime_string); v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); -- Check if child exists. SELECT count(*) INTO v_exists 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_partition_name::name; IF v_exists > 0 THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Creating new partition %s.%s with interval from %s to %s' , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end-'1sec'::interval)); END IF; v_sql := 'CREATE'; -- As of PG12, the unlogged/logged status of a native parent table cannot be changed via an ALTER TABLE in order to affect its children. -- As of v4.2x, the unlogged state will be managed via the template table SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c 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; IF v_unlogged = 'u' and v_partition_type != 'native' THEN v_sql := v_sql || ' UNLOGGED'; END IF; -- Close parentheses on LIKE are below due to differing requirements of native subpartitioning -- Same INCLUDING list is used in create_parent() v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING STORAGE INCLUDING COMMENTS ' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); IF current_setting('server_version_num')::int >= 120000 THEN v_sql := v_sql || ' INCLUDING GENERATED '; END IF; SELECT sub_partition_type, sub_control INTO v_sub_partition_type, v_sub_control FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; IF v_sub_partition_type = 'native' THEN -- INCLUDING INDEXES isn't necessary for native partitioning. It isn't supported in v10 and -- for v11+ index inheritance is automatically handled when the partition is attached v_sql := v_sql || format(') PARTITION BY RANGE (%I) ', v_sub_control); ELSE v_sql := v_sql || format(' INCLUDING INDEXES) ', v_sub_control); END IF; IF current_setting('server_version_num')::int < 120000 THEN -- column removed from pgclass in pg12 SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c 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; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; END IF; RAISE DEBUG 'create_partition_time v_sql: %', v_sql; EXECUTE v_sql; IF v_partition_type = 'native' THEN IF current_setting('server_version_num')::int >= 120000 THEN -- PG12 fixed tablespace marking on the parent of a native partition set -- Versions older than 12 handle tablespace setting via inherit_template_properties() call below IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; END IF; IF v_template_table IS NOT NULL THEN PERFORM @extschema@.inherit_template_properties(p_parent_table, v_parent_schema, v_partition_name); END IF; IF v_epoch = 'none' THEN -- Attach with normal, time-based values for native constraint EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end); ELSE -- Must attach with integer based values for native constraint and epoch IF v_epoch = 'seconds' THEN EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , EXTRACT('epoch' FROM v_partition_timestamp_start)::bigint , EXTRACT('epoch' FROM v_partition_timestamp_end)::bigint); ELSIF v_epoch = 'milliseconds' THEN EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , EXTRACT('epoch' FROM v_partition_timestamp_start)::bigint * 1000 , EXTRACT('epoch' FROM v_partition_timestamp_end)::bigint * 1000); END IF; -- Create secondary, time-based constraint since native's constraint is already integer based EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%s >= %L AND %4$s < %6$L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_partition_expression , v_partition_timestamp_start , v_partition_timestamp_end); END IF; ELSE -- non-native IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; -- Non-native always gets time-based constraint EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%s >= %L AND %4$s < %6$L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_partition_expression , v_partition_timestamp_start , v_partition_timestamp_end); IF v_epoch = 'seconds' THEN -- Non-native needs secondary, integer based constraint for epoch EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_int_check' , v_control , EXTRACT('epoch' from v_partition_timestamp_start)::bigint , v_control , EXTRACT('epoch' from v_partition_timestamp_end)::bigint ); ELSIF v_epoch = 'milliseconds' THEN EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_int_check' , v_control , EXTRACT('epoch' from v_partition_timestamp_start)::bigint * 1000 , v_control , EXTRACT('epoch' from v_partition_timestamp_end)::bigint * 1000); END IF; EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); -- If custom time, set extra config options. IF v_partition_type = 'time-custom' THEN INSERT INTO @extschema@.custom_time_partitions (parent_table, child_table, partition_range) VALUES ( p_parent_table, v_parent_schema||'.'||v_partition_name, tstzrange(v_partition_timestamp_start, v_partition_timestamp_end, '[)') ); END IF; -- Indexes cannot be created on the parent, so clustering cannot be used for native yet. PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); -- Foreign keys to other tables not supported in native IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; END IF; -- end native check -- NOTE: Privileges not automatically inherited for native. Only do so if config flag is set IF v_partition_type != 'native' OR (v_partition_type = 'native' AND v_inherit_privileges = TRUE) THEN PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition -- 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 FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_infinite_time_partitions , sub_automatic_maintenance , sub_jobmon , sub_trigger_exception_handling , sub_upsert , sub_trigger_return_null , sub_template_table , sub_inherit_privileges , sub_constraint_valid , sub_subscription_refresh , sub_date_trunc_interval FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Subpartitioning %s.%s', v_parent_schema, v_partition_name)); END IF; 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_inherit_fk := %L , p_epoch := %L , p_template_table := %L , p_jobmon := %L , p_start_partition := %L , p_date_trunc_interval := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_automatic_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_template_table , v_row.sub_jobmon , p_start_partition , v_row.sub_date_trunc_interval); RAISE DEBUG 'create_partition_time (create_parent loop): %', v_sql; EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions , trigger_exception_handling = v_row.sub_trigger_exception_handling , upsert = v_row.sub_upsert , inherit_privileges = v_row.sub_inherit_privileges , trigger_return_null = v_row.sub_trigger_return_null , constraint_valid = v_row.sub_constraint_valid , subscription_refresh = v_row.sub_subscription_refresh WHERE parent_table = v_parent_schema||'.'||v_partition_name; END LOOP; -- end sub partitioning LOOP -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table, p_job_id := v_job_id); IF v_publications IS NOT NULL THEN -- NOTE: Publications currently not supported on parent table, but are supported on the table partitions if individually assigned. PERFORM @extschema@.apply_publications(p_parent_table, v_parent_schema, v_partition_name); END IF; v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s. Attempted intervals: %s', p_parent_table, p_partition_times)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; 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_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; pg_partman-4.6.0/sql/functions/create_sub_parent.sql000066400000000000000000000301141412761706100226720ustar00rootroot00000000000000CREATE 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 , p_date_trunc_interval text DEFAULT NULL) 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' AND current_setting('server_version_num')::int >= 110000 THEN RAISE EXCEPTION 'The pg_partman upsert feature is not supported with native partitioning in PG11+. Use the built-in support for INSERT ON CONFLICT with native partitioning instead.'; 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 , sub_date_trunc_interval) 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 , p_date_trunc_interval); 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 intereval of sub-parent is less than 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) OR (p_date_trunc_interval = 'week') 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 time periods'; 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 , p_date_trunc_interval := %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 , p_date_trunc_interval); 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 $$; pg_partman-4.6.0/sql/functions/create_trigger.sql000066400000000000000000000033421412761706100221760ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION @extschema@.create_trigger(p_parent_table text) RETURNS void LANGUAGE plpgsql AS $$ DECLARE v_function_name text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_relkind char; v_trig_name text; v_trig_sql text; BEGIN /* * Function to create partitioning trigger on parent table */ 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'); SELECT n.nspname, c.relname, c.relkind INTO v_parent_schema, v_parent_tablename, v_relkind 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_relkind = 'p' THEN RAISE EXCEPTION 'This function cannot run on natively partitioned tables'; ELSIF v_relkind IS NULL THEN RAISE EXCEPTION 'Unable to find given table in system catalogs: %.%', v_parent_schema, v_parent_tablename; END IF; v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); -- Ensure function name matches the naming pattern v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); v_trig_sql := format('CREATE TRIGGER %I BEFORE INSERT ON %I.%I FOR EACH ROW EXECUTE PROCEDURE %I.%I()' , v_trig_name , v_parent_schema , v_parent_tablename , v_parent_schema , v_function_name); EXECUTE v_trig_sql; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END $$; pg_partman-4.6.0/sql/functions/drop_constraints.sql000066400000000000000000000117101412761706100226010ustar00rootroot00000000000000/* * Drop constraints managed by pg_partman */ CREATE FUNCTION @extschema@.drop_constraints(p_parent_table text, p_child_table text, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_child_schemaname text; v_child_tablename text; v_col text; v_constraint_cols text[]; v_existing_constraint_name text; v_exists boolean := FALSE; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_sql text; v_step_id bigint; BEGIN SELECT constraint_cols , jobmon INTO v_constraint_cols , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_constraint_cols IS NULL THEN RAISE EXCEPTION 'Given parent table (%) not set up for constraint management (constraint_cols is NULL)', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; SELECT schemaname, tablename INTO v_child_schemaname, v_child_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_child_table, '.', 1)::name AND tablename = split_part(p_child_table, '.', 2)::name; IF v_child_tablename IS NULL THEN RAISE EXCEPTION 'Unable to find given child table in system catalogs: %', p_child_table; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN DROP CONSTRAINT: %s', p_parent_table)); v_step_id := add_step(v_job_id, 'Entering constraint drop loop'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; FOREACH v_col IN ARRAY v_constraint_cols LOOP SELECT con.conname INTO v_existing_constraint_name FROM pg_catalog.pg_constraint con JOIN pg_class c ON c.oid = con.conrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN pg_catalog.pg_attribute a ON con.conrelid = a.attrelid WHERE c.relname = v_child_tablename AND n.nspname = v_child_schemaname AND con.conname LIKE 'partmanconstr_%' AND con.contype = 'c' AND a.attname = v_col AND ARRAY[a.attnum] OPERATOR(pg_catalog.<@) con.conkey AND a.attisdropped = false; IF v_existing_constraint_name IS NOT NULL THEN v_exists := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Dropping constraint on column: %s', v_col)); END IF; v_sql := format('ALTER TABLE %I.%I DROP CONSTRAINT %I', v_child_schemaname, v_child_tablename, v_existing_constraint_name); IF p_debug THEN RAISE NOTICE 'Constraint drop query: %', v_sql; END IF; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Drop constraint query: %s', v_sql)); END IF; END IF; END LOOP; IF v_jobmon_schema IS NOT NULL AND v_exists IS FALSE THEN v_step_id := add_step(v_job_id, format('No constraints found to drop on child table: %s', p_child_table)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; 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 DROP CONSTRAINT: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; pg_partman-4.6.0/sql/functions/drop_partition_column.sql000066400000000000000000000023671412761706100236300ustar00rootroot00000000000000CREATE FUNCTION @extschema@.drop_partition_column(p_parent_table text, p_column text) RETURNS void LANGUAGE plpgsql AS $$ DECLARE v_parent_oid oid; v_parent_schema text; v_parent_tablename text; v_row record; BEGIN /* * Function to ensure a column is dropped in all child tables, no matter when it was created */ SELECT c.oid, n.nspname, c.relname INTO v_parent_oid, v_parent_schema, v_parent_tablename 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 'Given parent table does not exist: %', p_parent_table; END IF; EXECUTE format('ALTER TABLE %I.%I DROP COLUMN IF EXISTS %I', v_parent_schema, v_parent_tablename, p_column); FOR v_row IN SELECT n.nspname AS child_schema, c.relname AS child_table 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 inhparent = v_parent_oid LOOP EXECUTE format('ALTER TABLE %I.%I DROP COLUMN IF EXISTS %I', v_row.child_schema, v_row.child_table, p_column); END LOOP; END $$; pg_partman-4.6.0/sql/functions/drop_partition_id.sql000066400000000000000000000325411412761706100227240ustar00rootroot00000000000000CREATE FUNCTION @extschema@.drop_partition_id(p_parent_table text, p_retention bigint DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_control text; v_control_type text; v_count int; v_drop_cascade_fk boolean; v_drop_count int := 0; v_index record; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_max bigint; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_partition_id bigint; v_partition_type text; v_retention bigint; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_row record; v_row_max_id record; v_sql text; v_step_id bigint; v_sub_parent text; BEGIN /* * Function to drop child tables from an id-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman drop_partition_id')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_id already running.'; RETURN 0; END IF; IF p_retention IS NULL THEN SELECT partition_interval::bigint , partition_type , control , retention::bigint , retention_keep_table , retention_keep_index , retention_schema , jobmon , drop_cascade_fk INTO v_partition_interval , v_partition_type , v_control , v_retention , v_retention_keep_table , v_retention_keep_index , v_retention_schema , v_jobmon , v_drop_cascade_fk FROM @extschema@.part_config WHERE parent_table = p_parent_table AND retention IS NOT NULL; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE -- Allow override of configuration options SELECT partition_interval::bigint , partition_type , control , retention_keep_table , retention_keep_index , retention_schema , jobmon , drop_cascade_fk INTO v_partition_interval , v_partition_type , v_control , v_retention_keep_table , v_retention_keep_index , v_retention_schema , v_jobmon , v_drop_cascade_fk FROM @extschema@.part_config WHERE parent_table = p_parent_table; v_retention := p_retention; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'id' THEN RAISE EXCEPTION 'Data type of control column in given partition set is not an integer type'; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%I) FROM %I.%I', v_control, v_row_max_id.partition_schemaname, v_row_max_id.partition_tablename) INTO v_max; IF v_max IS NOT NULL THEN EXIT; END IF; END LOOP; SELECT sub_parent INTO v_sub_parent FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; -- Loop through child tables of the given parent -- Must go in ascending order to avoid dropping what may be the "last" partition in the set after dropping tables that match retention period FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'ASC') LOOP SELECT child_start_id INTO v_partition_id FROM @extschema@.show_partition_info(v_row.partition_schemaname||'.'||v_row.partition_tablename , v_partition_interval::text , p_parent_table); -- Add one interval since partition names contain the start of the constraint period IF v_retention <= (v_max - (v_partition_id + v_partition_interval)) THEN -- Do not allow final partition to be dropped if it is not a sub-partition parent SELECT count(*) INTO v_count FROM @extschema@.show_partitions(p_parent_table); IF v_count = 1 AND v_sub_parent IS NULL THEN RAISE WARNING 'Attempt to drop final partition in partition set % as part of retention policy. If you see this message multiple times for the same table, advise reviewing retention policy and/or data entry into the partition set. Also consider setting "infinite_time_partitions = true" if there are large gaps in data insertion.).', p_parent_table; CONTINUE; END IF; -- Only create a jobmon entry if there's actual retention work done IF v_jobmon_schema IS NOT NULL AND v_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN DROP ID PARTITION: %s', p_parent_table)); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Detach/Uninherit table %s.%s from %s', v_row.partition_schemaname, v_row.partition_tablename, p_parent_table)); END IF; IF v_retention_keep_table = true THEN -- No need to detach partition before dropping since it's going away anyway -- Avoids issue of FKs not allowing detachment (Github Issue #294). IF v_partition_type = 'native' THEN EXECUTE format('ALTER TABLE %I.%I DETACH PARTITION %I.%I' , v_parent_schema , v_parent_tablename , v_row.partition_schemaname , v_row.partition_tablename); ELSE EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_row.partition_schemaname , v_row.partition_tablename , v_parent_schema , v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop table %s.%s', v_row.partition_schemaname, v_row.partition_tablename)); END IF; v_sql := 'DROP TABLE %I.%I'; IF v_drop_cascade_fk OR v_sub_parent IS NOT NULL THEN v_sql := v_sql || ' CASCADE'; END IF; EXECUTE format(v_sql, v_row.partition_schemaname, v_row.partition_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN IF v_partition_type = 'partman' OR ( v_partition_type = 'native' AND current_setting('server_version_num')::int < 110000) THEN -- Cannot drop child indexes on native partition sets in PG11+ FOR v_index IN WITH child_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE c1.relname = v_row.partition_tablename::name AND n1.nspname = v_row.partition_schema::name ) SELECT c.relname as name , con.conname FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid LEFT JOIN pg_catalog.pg_constraint con ON i.indexrelid = con.conindid JOIN child_info ON i.indrelid = child_info.oid LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop index %s from %s.%s' , v_index.name , v_row.partition_schemaname , v_row.partition_tablename)); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I DROP CONSTRAINT %I', v_row.partition_schemaname, v_row.partition_tablename, v_index.conname); ELSE EXECUTE format('DROP INDEX %I.%I', v_row.partition_schemaname, v_index.name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; -- end native/11 check END IF; -- end v_retention_keep_index IF ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Moving table %s.%s to schema %s' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema)); END IF; EXECUTE format('ALTER TABLE %I.%I SET SCHEMA %I' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if -- If child table is a subpartition, remove it from part_config & part_config_sub (should cascade due to FK) DELETE FROM @extschema@.part_config WHERE parent_table = v_row.partition_schemaname ||'.'||v_row.partition_tablename; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', format('%s partitions dropped.', v_drop_count)); 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_drop_count; 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 DROP ID PARTITION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; pg_partman-4.6.0/sql/functions/drop_partition_time.sql000066400000000000000000000333151412761706100232660ustar00rootroot00000000000000CREATE FUNCTION @extschema@.drop_partition_time(p_parent_table text, p_retention interval DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL, p_reference_timestamp timestamptz DEFAULT CURRENT_TIMESTAMP) RETURNS int LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_control text; v_control_type text; v_count int; v_datetime_string text; v_drop_cascade_fk boolean; v_drop_count int := 0; v_epoch text; v_index record; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_partition_timestamp timestamptz; v_partition_type text; v_retention interval; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_row record; v_sql text; v_step_id bigint; v_sub_parent text; BEGIN /* * Function to drop child tables from a time-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman drop_partition_time')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_time already running.'; RETURN 0; END IF; -- Allow override of configuration options IF p_retention IS NULL THEN SELECT partition_type , control , partition_interval::interval , epoch , retention::interval , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_partition_type , v_control , v_partition_interval , v_epoch , v_retention , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND retention IS NOT NULL; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE SELECT partition_type , partition_interval::interval , epoch , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_partition_type , v_partition_interval , v_epoch , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; v_retention := p_retention; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'time' THEN IF (v_control_type = 'id' AND v_epoch = 'none') OR v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run on partition set without time based control column or epoch flag set with an id column. Found control: %, epoch: %', v_control_type, v_epoch; END IF; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; SELECT sub_parent INTO v_sub_parent FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; -- Loop through child tables of the given parent -- Must go in ascending order to avoid dropping what may be the "last" partition in the set after dropping tables that match retention period FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'ASC') LOOP -- pull out datetime portion of partition's tablename to make the next one SELECT child_start_time INTO v_partition_timestamp FROM @extschema@.show_partition_info(v_row.partition_schemaname||'.'||v_row.partition_tablename , v_partition_interval::text , p_parent_table); -- Add one interval since partition names contain the start of the constraint period IF v_retention < (p_reference_timestamp - (v_partition_timestamp + v_partition_interval)) THEN -- Do not allow final partition to be dropped if it is not a sub-partition parent SELECT count(*) INTO v_count FROM @extschema@.show_partitions(p_parent_table); IF v_count = 1 AND v_sub_parent IS NULL THEN RAISE WARNING 'Attempt to drop final partition in partition set % as part of retention policy. If you see this message multiple times for the same table, advise reviewing retention policy and/or data entry into the partition set. Also consider setting "infinite_time_partitions = true" if there are large gaps in data insertion.).', p_parent_table; CONTINUE; END IF; -- Only create a jobmon entry if there's actual retention work done IF v_jobmon_schema IS NOT NULL AND v_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN DROP TIME PARTITION: %s', p_parent_table)); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Detach/Uninherit table %s.%s from %s' , v_row.partition_schemaname , v_row.partition_tablename , p_parent_table)); END IF; IF v_retention_keep_table = true THEN -- No need to detach partition before dropping since it's going away anyway -- Avoids issue of FKs not allowing detachment (Github Issue #294). IF v_partition_type = 'native' THEN EXECUTE format('ALTER TABLE %I.%I DETACH PARTITION %I.%I' , v_parent_schema , v_parent_tablename , v_row.partition_schemaname , v_row.partition_tablename); ELSE EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_row.partition_schemaname , v_row.partition_tablename , v_parent_schema , v_parent_tablename); END IF; END IF; IF v_partition_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_row.partition_schemaname||'.'||v_row.partition_tablename; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop table %s.%s', v_row.partition_schemaname, v_row.partition_tablename)); END IF; v_sql := 'DROP TABLE %I.%I'; IF v_drop_cascade_fk OR v_sub_parent IS NOT NULL THEN v_sql := v_sql || ' CASCADE'; END IF; EXECUTE format(v_sql, v_row.partition_schemaname, v_row.partition_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN IF v_partition_type = 'partman' OR ( v_partition_type = 'native' AND current_setting('server_version_num')::int < 110000) THEN -- Cannot drop child indexes on native partition sets in PG11+ FOR v_index IN WITH child_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE c1.relname = v_row.partition_tablename::name AND n1.nspname = v_row.partition_schemaname::name ) SELECT c.relname as name , con.conname FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid LEFT JOIN pg_catalog.pg_constraint con ON i.indexrelid = con.conindid JOIN child_info ON i.indrelid = child_info.oid LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop index %s from %s.%s' , v_index.name , v_row.partition_schemaname , v_row.partition_tablename)); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I DROP CONSTRAINT %I' , v_row.partition_schemaname , v_row.partition_tablename , v_index.conname); ELSE EXECUTE format('DROP INDEX %I.%I', v_parent_schema, v_index.name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; -- end native/11 check END IF; -- end v_retention_keep_index IF ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Moving table %s.%s to schema %s' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema)); END IF; EXECUTE format('ALTER TABLE %I.%I SET SCHEMA %I', v_row.partition_schemaname, v_row.partition_tablename, v_retention_schema); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if -- If child table is a subpartition, remove it from part_config & part_config_sub (should cascade due to FK) DELETE FROM @extschema@.part_config WHERE parent_table = v_row.partition_schemaname||'.'||v_row.partition_tablename; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', format('%s partitions dropped.', v_drop_count)); 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_drop_count; 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 DROP TIME PARTITION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; pg_partman-4.6.0/sql/functions/dump_partition_table_definition.sql000066400000000000000000000131111412761706100256200ustar00rootroot00000000000000CREATE FUNCTION @extschema@.dump_partitioned_table_definition( p_parent_table TEXT, p_ignore_template_table BOOLEAN DEFAULT false ) RETURNS TEXT LANGUAGE PLPGSQL STABLE AS $$ DECLARE v_create_parent_definition TEXT; v_update_part_config_definition TEXT; -- Columns from part_config table. v_parent_table TEXT; -- NOT NULL v_control TEXT; -- NOT NULL v_partition_type TEXT; -- NOT NULL v_partition_interval TEXT; -- NOT NULL v_constraint_cols TEXT[]; v_premake integer; -- NOT NULL v_optimize_trigger integer; -- NOT NULL v_optimize_constraint integer; -- NOT NULL v_epoch text; -- NOT NULL v_inherit_fk BOOLEAN; -- NOT NULL v_retention TEXT; v_retention_schema TEXT; v_retention_keep_table BOOLEAN; -- NOT NULL v_retention_keep_index BOOLEAN; -- NOT NULL v_infinite_time_partitions BOOLEAN; -- NOT NULL v_datetime_string TEXT; v_automatic_maintenance TEXT; -- NOT NULL v_jobmon BOOLEAN; -- NOT NULL v_sub_partition_set_full BOOLEAN; -- NOT NULL v_trigger_exception_handling BOOLEAN; v_upsert TEXT; -- NOT NULL v_trigger_return_null BOOLEAN; -- NOT NULL v_template_table TEXT; v_publications TEXT[]; v_inherit_privileges BOOLEAN; -- DEFAULT false v_constraint_valid BOOLEAN; -- DEFAULT true NOT NULL v_subscription_refresh text; v_drop_cascade_fk boolean; -- DEFAULT false NOT NULL BEGIN SELECT pc.parent_table, pc.control, pc.partition_type, pc.partition_interval, pc.constraint_cols, pc.premake, pc.optimize_trigger, pc.optimize_constraint, pc.epoch, pc.inherit_fk, pc.retention, pc.retention_schema, pc.retention_keep_table, pc.retention_keep_index, pc.infinite_time_partitions, pc.datetime_string, pc.automatic_maintenance, pc.jobmon, pc.sub_partition_set_full, pc.trigger_exception_handling, pc.upsert, pc.trigger_return_null, pc.template_table, pc.publications, pc.inherit_privileges, pc.constraint_valid, pc.subscription_refresh, pc.drop_cascade_fk INTO v_parent_table, v_control, v_partition_type, v_partition_interval, v_constraint_cols, v_premake, v_optimize_trigger, v_optimize_constraint, v_epoch, v_inherit_fk, v_retention, v_retention_schema, v_retention_keep_table, v_retention_keep_index, v_infinite_time_partitions, v_datetime_string, v_automatic_maintenance, v_jobmon, v_sub_partition_set_full, v_trigger_exception_handling, v_upsert, v_trigger_return_null, v_template_table, v_publications, v_inherit_privileges, v_constraint_valid, v_subscription_refresh, v_drop_cascade_fk FROM @extschema@.part_config pc WHERE pc.parent_table = p_parent_table; IF v_partition_type = 'partman' THEN CASE WHEN v_partition_interval::INTERVAL = '1 year'::INTERVAL THEN v_partition_interval := 'yearly'; WHEN v_partition_interval::INTERVAL = '3 months'::INTERVAL THEN v_partition_interval := 'quarterly'; WHEN v_partition_interval::INTERVAL = '1 month'::INTERVAL THEN v_partition_interval := 'monthly'; WHEN v_partition_interval::INTERVAL = '1 week'::INTERVAL THEN v_partition_interval := 'weekly'; WHEN v_partition_interval::INTERVAL = '1 day'::INTERVAL THEN v_partition_interval := 'daily'; WHEN v_partition_interval::INTERVAL = '1 hour'::INTERVAL THEN v_partition_interval := 'hourly'; WHEN v_partition_interval::INTERVAL = '30 mins'::INTERVAL THEN v_partition_interval := 'half-hour'; WHEN v_partition_interval::INTERVAL = '15 mins'::INTERVAL THEN v_partition_interval := 'quarter-hour'; ELSE RAISE EXCEPTION 'Partitioning interval not recognized for "partman" partitioning type'; END CASE; END IF; IF v_partition_type = 'native' AND p_ignore_template_table THEN v_template_table := NULL; END IF; v_create_parent_definition := format( E'SELECT @extschema@.create_parent( \tp_parent_table := %L, \tp_control := %L, \tp_type := %L, \tp_interval := %L, \tp_constraint_cols := %L, \tp_premake := %s, \tp_automatic_maintenance := %L, \tp_inherit_fk := %L, \tp_epoch := %L, \tp_upsert := %L, \tp_publications := %L, \tp_trigger_return_null := %L, \tp_template_table := %L, \tp_jobmon := %L \t-- v_start_partition is intentionally ignored as there \t-- isn''t any obviously correct definition. );', v_parent_table, v_control, v_partition_type, v_partition_interval, v_constraint_cols, v_premake, v_automatic_maintenance, v_inherit_fk, v_epoch, v_upsert, v_publications, v_trigger_return_null, v_template_table, v_jobmon ); v_update_part_config_definition := format( E'UPDATE @extschema@.part_config SET \toptimize_trigger = %s, \toptimize_constraint = %s, \tretention = %L, \tretention_schema = %L, \tretention_keep_table = %L, \tretention_keep_index = %L, \tinfinite_time_partitions = %L, \tdatetime_string = %L, \tsub_partition_set_full = %L, \ttrigger_exception_handling = %L, \tinherit_privileges = %L, \tconstraint_valid = %L, \tsubscription_refresh = %L WHERE parent_table = %L;', v_optimize_trigger, v_optimize_constraint, v_retention, v_retention_schema, v_retention_keep_table, v_retention_keep_index, v_infinite_time_partitions, v_datetime_string, v_sub_partition_set_full, v_trigger_exception_handling, v_inherit_privileges, v_constraint_valid, v_subscription_refresh, v_parent_table ); RETURN concat_ws(E'\n', v_create_parent_definition, v_update_part_config_definition ); END $$; pg_partman-4.6.0/sql/functions/inherit_template_properties.sql000066400000000000000000000233531412761706100250250ustar00rootroot00000000000000CREATE 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 $$; pg_partman-4.6.0/sql/functions/partition_data_id.sql000066400000000000000000000245371412761706100226770ustar00rootroot00000000000000CREATE FUNCTION @extschema@.partition_data_id(p_parent_table text , p_batch_count int DEFAULT 1 , p_batch_interval bigint DEFAULT NULL , p_lock_wait numeric DEFAULT 0 , p_order text DEFAULT 'ASC' , p_analyze boolean DEFAULT true , p_source_table text DEFAULT NULL , p_ignored_columns text[] DEFAULT NULL) RETURNS bigint LANGUAGE plpgsql AS $$ DECLARE v_col text; v_column_list text; v_control text; v_control_type text; v_current_partition_name text; v_default_exists boolean; v_default_schemaname text; v_default_tablename text; v_epoch text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_id bigint; v_min_partition_id bigint; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_tablename text; v_partition_interval bigint; v_partition_id bigint[]; v_partition_type text; v_rowcount bigint; v_source_schemaname text; v_source_tablename text; v_sql text; v_start_control bigint; v_total_rows bigint := 0; BEGIN /* * Populate the child table(s) of an id-based partition set with old data from the original parent */ SELECT partition_interval::bigint , partition_type , control , epoch INTO v_partition_interval , v_partition_type , v_control , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: No entry in part_config found for given table: %', p_parent_table; END IF; SELECT schemaname, tablename INTO v_source_schemaname, v_source_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- Preserve real parent tablename for use below v_parent_tablename := v_source_tablename; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_source_schemaname, v_source_tablename, v_control); IF v_control_type <> 'id' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN RAISE EXCEPTION 'Control column for given partition set is not id/serial based or epoch flag is set for time-based partitioning.'; END IF; IF p_source_table IS NOT NULL THEN -- Set source table to user given source table instead of parent table v_source_schemaname := NULL; v_source_tablename := NULL; SELECT schemaname, tablename INTO v_source_schemaname, v_source_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_source_table, '.', 1)::name AND tablename = split_part(p_source_table, '.', 2)::name; IF v_source_tablename IS NULL THEN RAISE EXCEPTION 'Given source table does not exist in system catalogs: %', p_source_table; END IF; ELSIF v_partition_type = 'native' AND current_setting('server_version_num')::int >= 110000 THEN IF p_batch_interval IS NOT NULL AND p_batch_interval != v_partition_interval THEN -- This is true because all data for a given child table must be moved out of the default partition before the child table can be created. -- So cannot create the child table when only some of the data has been moved out of the default partition. RAISE EXCEPTION 'Custom intervals are not allowed when moving data out of the DEFAULT partition in a native set. Please leave p_interval/p_batch_interval parameters unset or NULL to allow use of partition set''s default partitioning interval.'; END IF; -- Set source table to default table if PG11+, p_source_table is not set, and it exists -- Otherwise just return with a DEBUG that no data source exists v_sql := format('SELECT n.nspname::text, c.relname::text FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = ''%I.%I''::regclass AND pg_get_expr(relpartbound, c.oid) = ''DEFAULT''' , v_source_schemaname , v_source_tablename); EXECUTE v_sql INTO v_default_schemaname, v_default_tablename; IF v_default_tablename IS NOT NULL THEN v_source_schemaname := v_default_schemaname; v_source_tablename := v_default_tablename; v_default_exists := true; EXECUTE format ('CREATE TEMP TABLE IF NOT EXISTS partman_temp_data_storage (LIKE %I.%I INCLUDING INDEXES) ON COMMIT DROP', v_source_schemaname, v_source_tablename); ELSE RAISE DEBUG 'No default table found when partition_data_id() was called'; RETURN v_total_rows; END IF; END IF; 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'); IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; -- Generate column list to use in SELECT/INSERT statements below. Allows for exclusion of GENERATED (or any other desired) columns. v_sql := format ('SELECT ''"''||string_agg(attname, ''","'')||''"'' 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 n.nspname = %L AND c.relname = %L AND a.attnum > 0 AND a.attisdropped = false' , v_source_schemaname , v_source_tablename); IF p_ignored_columns IS NOT NULL THEN FOREACH v_col IN ARRAY p_ignored_columns LOOP v_sql := v_sql || format(' AND attname != %L ', v_col); END LOOP; END IF; EXECUTE v_sql INTO v_column_list; FOR i IN 1..p_batch_count LOOP IF p_order = 'ASC' THEN EXECUTE format('SELECT min(%I) FROM ONLY %I.%I', v_control, v_source_schemaname, v_source_tablename) INTO v_start_control; IF v_start_control IS NULL THEN EXIT; END IF; v_min_partition_id = v_start_control - (v_start_control % v_partition_interval); v_partition_id := ARRAY[v_min_partition_id]; -- Check if custom batch interval overflows current partition maximum IF (v_start_control + p_batch_interval) >= (v_min_partition_id + v_partition_interval) THEN v_max_partition_id := v_min_partition_id + v_partition_interval; ELSE v_max_partition_id := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_control, v_source_schemaname, v_source_tablename) INTO v_start_control; IF v_start_control IS NULL THEN EXIT; END IF; v_min_partition_id = v_start_control - (v_start_control % v_partition_interval); -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_id := v_min_partition_id + v_partition_interval; v_partition_id := ARRAY[v_min_partition_id]; -- Make sure minimum doesn't underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_id THEN v_min_partition_id = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN v_sql := format('SELECT %s FROM ONLY %I.%I WHERE %I >= %s AND %I < %s FOR UPDATE NOWAIT' , v_column_list , v_source_schemaname , v_source_tablename , v_control , v_min_partition_id , v_control , v_max_partition_id); EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; v_current_partition_name := @extschema@.check_name_length(COALESCE(v_parent_tablename), v_min_partition_id::text, TRUE); IF v_default_exists THEN -- Child tables cannot be created in native partitioning if data that belongs to it exists in the default -- Have to move data out to temporary location, create child table, then move it back -- Temp table created above to avoid excessive temp creation in loop EXECUTE format('WITH partition_data AS ( DELETE FROM %1$I.%2$I WHERE %3$I >= %4$s AND %3$I < %5$s RETURNING *) INSERT INTO partman_temp_data_storage (%6$s) SELECT %6$s FROM partition_data' , v_source_schemaname , v_source_tablename , v_control , v_min_partition_id , v_max_partition_id , v_column_list); PERFORM @extschema@.create_partition_id(p_parent_table, v_partition_id, p_analyze); EXECUTE format('WITH partition_data AS ( DELETE FROM partman_temp_data_storage RETURNING *) INSERT INTO %1$I.%2$I (%3$s) SELECT %3$s FROM partition_data' , v_source_schemaname , v_current_partition_name , v_column_list); ELSE PERFORM @extschema@.create_partition_id(p_parent_table, v_partition_id, p_analyze); EXECUTE format('WITH partition_data AS ( DELETE FROM ONLY %1$I.%2$I WHERE %3$I >= %4$s AND %3$I < %5$s RETURNING *) INSERT INTO %1$I.%6$I (%7$s) SELECT %7$s FROM partition_data' , v_source_schemaname , v_source_tablename , v_control , v_min_partition_id , v_max_partition_id , v_current_partition_name , v_column_list); END IF; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; IF v_partition_type = 'partman' THEN PERFORM @extschema@.create_function_id(p_parent_table); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_total_rows; END $$; pg_partman-4.6.0/sql/functions/partition_data_time.sql000066400000000000000000000351261412761706100232350ustar00rootroot00000000000000CREATE FUNCTION @extschema@.partition_data_time( p_parent_table text , p_batch_count int DEFAULT 1 , p_batch_interval interval DEFAULT NULL , p_lock_wait numeric DEFAULT 0 , p_order text DEFAULT 'ASC' , p_analyze boolean DEFAULT true , p_source_table text DEFAULT NULL , p_ignored_columns text[] DEFAULT NULL) RETURNS bigint LANGUAGE plpgsql AS $$ DECLARE v_col text; v_column_list text; v_control text; v_control_type text; v_datetime_string text; v_current_partition_name text; v_default_exists boolean; v_default_schemaname text; v_default_tablename text; v_epoch text; v_last_partition text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_timestamp timestamptz; v_min_partition_timestamp timestamptz; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_tablename text; v_parent_tablename_real text; v_partition_expression text; v_partition_interval interval; v_partition_suffix text; v_partition_timestamp timestamptz[]; v_partition_type text; v_source_schemaname text; v_source_tablename text; v_rowcount bigint; v_sql text; v_start_control timestamptz; v_total_rows bigint := 0; BEGIN /* * Populate the child table(s) of a time-based partition set with old data from the original parent */ SELECT partition_type , partition_interval::interval , control , datetime_string , epoch INTO v_partition_type , v_partition_interval , v_control , v_datetime_string , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: No entry in part_config found for given table: %', p_parent_table; END IF; SELECT schemaname, tablename INTO v_source_schemaname, v_source_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- Preserve real parent tablename for use below v_parent_tablename := v_source_tablename; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_source_schemaname, v_source_tablename, v_control); IF v_control_type <> 'time' THEN IF (v_control_type = 'id' AND v_epoch = 'none') OR v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run on partition set without time based control column or epoch flag set with an id column. Found control: %, epoch: %', v_control_type, v_epoch; END IF; END IF; -- Replace the parent variables with the source variables if using source table for child table data IF p_source_table IS NOT NULL THEN -- Set source table to user given source table instead of parent table v_source_schemaname := NULL; v_source_tablename := NULL; SELECT schemaname, tablename INTO v_source_schemaname, v_source_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_source_table, '.', 1)::name AND tablename = split_part(p_source_table, '.', 2)::name; IF v_source_tablename IS NULL THEN RAISE EXCEPTION 'Given source table does not exist in system catalogs: %', p_source_table; END IF; ELSIF v_partition_type = 'native' AND current_setting('server_version_num')::int >= 110000 THEN IF p_batch_interval IS NOT NULL AND p_batch_interval != v_partition_interval THEN -- This is true because all data for a given child table must be moved out of the default partition before the child table can be created. -- So cannot create the child table when only some of the data has been moved out of the default partition. RAISE EXCEPTION 'Custom intervals are not allowed when moving data out of the DEFAULT partition in a native set. Please leave p_interval/p_batch_interval parameters unset or NULL to allow use of partition set''s default partitioning interval.'; END IF; -- Set source table to default table if PG11+, p_source_table is not set, and it exists -- Otherwise just return with a DEBUG that no data source exists v_sql := format('SELECT n.nspname::text, c.relname::text FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = ''%I.%I''::regclass AND pg_get_expr(relpartbound, c.oid) = ''DEFAULT''' , v_source_schemaname , v_source_tablename); EXECUTE v_sql INTO v_default_schemaname, v_default_tablename; IF v_default_tablename IS NOT NULL THEN v_source_schemaname := v_default_schemaname; v_source_tablename := v_default_tablename; v_default_exists := true; EXECUTE format ('CREATE TEMP TABLE IF NOT EXISTS partman_temp_data_storage (LIKE %I.%I INCLUDING INDEXES) ON COMMIT DROP', v_source_schemaname, v_source_tablename); ELSE RAISE DEBUG 'No default table found when partition_data_id() was called'; RETURN v_total_rows; END IF; END IF; 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'); IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) END; -- Generate column list to use in SELECT/INSERT statements below. Allows for exclusion of GENERATED (or any other desired) columns. v_sql := format ('SELECT ''"''||string_agg(attname, ''","'')||''"'' 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 n.nspname = %L AND c.relname = %L AND a.attnum > 0 AND a.attisdropped = false' , v_source_schemaname , v_source_tablename); IF p_ignored_columns IS NOT NULL THEN FOREACH v_col IN ARRAY p_ignored_columns LOOP v_sql := v_sql || format(' AND attname != %L ', v_col); END LOOP; END IF; EXECUTE v_sql INTO v_column_list; FOR i IN 1..p_batch_count LOOP IF p_order = 'ASC' THEN EXECUTE format('SELECT min(%s) FROM ONLY %I.%I', v_partition_expression, v_source_schemaname, v_source_tablename) INTO v_start_control; ELSIF p_order = 'DESC' THEN EXECUTE format('SELECT max(%s) FROM ONLY %I.%I', v_partition_expression, v_source_schemaname, v_source_tablename) INTO v_start_control; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; IF v_start_control IS NULL THEN EXIT; END IF; IF v_partition_type = 'partman' THEN CASE WHEN v_partition_interval = '15 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '15min'::interval * floor(date_part('minute', v_start_control) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '30min'::interval * floor(date_part('minute', v_start_control) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control); WHEN v_partition_interval = '1 day' THEN v_min_partition_timestamp := date_trunc('day', v_start_control); WHEN v_partition_interval = '1 week' THEN v_min_partition_timestamp := date_trunc('week', v_start_control); WHEN v_partition_interval = '1 month' THEN v_min_partition_timestamp := date_trunc('month', v_start_control); WHEN v_partition_interval = '3 months' THEN v_min_partition_timestamp := date_trunc('quarter', v_start_control); WHEN v_partition_interval = '1 year' THEN v_min_partition_timestamp := date_trunc('year', v_start_control); END CASE; ELSIF v_partition_type IN ('time-custom', 'native') THEN SELECT child_start_time INTO v_min_partition_timestamp FROM @extschema@.show_partition_info(v_source_schemaname||'.'||v_last_partition , v_partition_interval::text , p_parent_table); v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; LOOP IF v_start_control >= v_min_partition_timestamp AND v_start_control < v_max_partition_timestamp THEN EXIT; ELSE BEGIN IF v_start_control >= v_max_partition_timestamp THEN -- Keep going forward in time, checking if child partition time interval encompasses the current v_start_control value v_min_partition_timestamp := v_max_partition_timestamp; v_max_partition_timestamp := v_max_partition_timestamp + v_partition_interval; ELSE -- Keep going backwards in time, checking if child partition time interval encompasses the current v_start_control value v_max_partition_timestamp := v_min_partition_timestamp; v_min_partition_timestamp := v_min_partition_timestamp - v_partition_interval; END IF; EXCEPTION WHEN datetime_field_overflow THEN RAISE EXCEPTION 'Attempted partition time interval is outside PostgreSQL''s supported time range. Unable to create partition with interval before timestamp % ', v_min_partition_timestamp; END; END IF; END LOOP; END IF; v_partition_timestamp := ARRAY[v_min_partition_timestamp]; IF p_order = 'ASC' THEN -- Ensure batch interval given as parameter doesn't cause maximum to overflow the current partition maximum IF (v_start_control + p_batch_interval) >= (v_min_partition_timestamp + v_partition_interval) THEN v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; ELSE v_max_partition_timestamp := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; -- Ensure batch interval given as parameter doesn't cause minimum to underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_timestamp THEN v_min_partition_timestamp = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT %s FROM ONLY %I.%I WHERE %s >= %L AND %3$s < %5$L FOR UPDATE NOWAIT' , v_column_list , v_source_schemaname , v_source_tablename , v_partition_expression , v_min_partition_timestamp , v_max_partition_timestamp); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; -- This suffix generation code is in create_partition_time() as well v_partition_suffix := to_char(v_min_partition_timestamp, v_datetime_string); v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); IF v_default_exists THEN -- Child tables cannot be created in native partitioning if data that belongs to it exists in the default -- Have to move data out to temporary location, create child table, then move it back -- Temp table created above to avoid excessive temp creation in loop EXECUTE format('WITH partition_data AS ( DELETE FROM %1$I.%2$I WHERE %3$s >= %4$L AND %3$s < %5$L RETURNING *) INSERT INTO partman_temp_data_storage (%6$s) SELECT %6$s FROM partition_data' , v_source_schemaname , v_source_tablename , v_partition_expression , v_min_partition_timestamp , v_max_partition_timestamp , v_column_list); PERFORM @extschema@.create_partition_time(p_parent_table, v_partition_timestamp, p_analyze); EXECUTE format('WITH partition_data AS ( DELETE FROM partman_temp_data_storage RETURNING *) INSERT INTO %I.%I (%3$s) SELECT %3$s FROM partition_data' , v_source_schemaname , v_current_partition_name , v_column_list); ELSE PERFORM @extschema@.create_partition_time(p_parent_table, v_partition_timestamp, p_analyze); EXECUTE format('WITH partition_data AS ( DELETE FROM ONLY %I.%I WHERE %s >= %L AND %3$s < %5$L RETURNING *) INSERT INTO %1$I.%6$I (%7$s) SELECT %7$s FROM partition_data' , v_source_schemaname , v_source_tablename , v_partition_expression , v_min_partition_timestamp , v_max_partition_timestamp , v_current_partition_name , v_column_list); END IF; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; IF v_partition_type IN ('partman', 'time-custom') THEN PERFORM @extschema@.create_function_time(p_parent_table); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_total_rows; END $$; pg_partman-4.6.0/sql/functions/partition_gap_fill.sql000066400000000000000000000231121412761706100230530ustar00rootroot00000000000000CREATE FUNCTION partition_gap_fill(p_parent_table text) RETURNS integer LANGUAGE plpgsql AS $$ DECLARE v_child_created boolean; v_children_created_count int := 0; v_control text; v_control_type text; v_current_child_start_id bigint; v_current_child_start_timestamp timestamptz; v_expected_next_child_id bigint; v_expected_next_child_timestamp timestamptz; v_final_child_schemaname text; v_final_child_start_id bigint; v_final_child_start_timestamp timestamptz; v_final_child_tablename text; v_interval_id bigint; v_interval_time interval; v_previous_child_schemaname text; v_previous_child_tablename text; v_previous_child_start_id bigint; v_previous_child_start_timestamp timestamptz; v_parent_schema text; v_parent_table text; v_parent_tablename text; v_partition_interval text; v_row record; BEGIN SELECT parent_table, partition_interval, control INTO v_parent_table, v_partition_interval, v_control 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; END IF; SELECT n.nspname, c.relname INTO v_parent_schema, v_parent_tablename 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_tablename IS NULL THEN RAISE EXCEPTION 'Unable to find given parent table in system catalogs. Ensure it is schema qualified: %', p_parent_table; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); SELECT partition_schemaname, partition_tablename INTO v_final_child_schemaname, v_final_child_tablename FROM @extschema@.show_partitions(v_parent_table, 'DESC') LIMIT 1; IF v_control_type = 'time' THEN v_interval_time := v_partition_interval::interval; SELECT child_start_time INTO v_final_child_start_timestamp FROM @extschema@.show_partition_info(format('%s', v_final_child_schemaname||'.'||v_final_child_tablename), p_parent_table := v_parent_table); FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_parent_table, 'ASC') LOOP RAISE DEBUG 'v_row.partition_tablename: %, v_final_child_start_timestamp: %', v_row.partition_tablename, v_final_child_start_timestamp; IF v_previous_child_tablename IS NULL THEN v_previous_child_schemaname := v_row.partition_schemaname; v_previous_child_tablename := v_row.partition_tablename; SELECT child_start_time INTO v_previous_child_start_timestamp FROM @extschema@.show_partition_info(format('%s', v_previous_child_schemaname||'.'||v_previous_child_tablename), p_parent_table := v_parent_table); CONTINUE; END IF; v_expected_next_child_timestamp := v_previous_child_start_timestamp + v_interval_time; RAISE DEBUG 'v_expected_next_child_timestamp: %', v_expected_next_child_timestamp; IF v_expected_next_child_timestamp = v_final_child_start_timestamp THEN EXIT; END IF; SELECT child_start_time INTO v_current_child_start_timestamp FROM @extschema@.show_partition_info(format('%s', v_row.partition_schemaname||'.'||v_row.partition_tablename), p_parent_table := v_parent_table); RAISE DEBUG 'v_current_child_start_timestamp: %', v_current_child_start_timestamp; IF v_expected_next_child_timestamp != v_current_child_start_timestamp THEN v_child_created := @extschema@.create_partition_time(v_parent_table, ARRAY[v_expected_next_child_timestamp]); IF v_child_created THEN v_children_created_count := v_children_created_count + 1; v_child_created := false; END IF; SELECT partition_schema, partition_table INTO v_previous_child_schemaname, v_previous_child_tablename FROM @extschema@.show_partition_name(v_parent_table, v_expected_next_child_timestamp::text); -- Need to stay in another inner loop until the next expected child timestamp matches the current one -- Once it does, exit. This means gap is filled. LOOP v_previous_child_start_timestamp := v_expected_next_child_timestamp; v_expected_next_child_timestamp := v_expected_next_child_timestamp + v_interval_time; IF v_expected_next_child_timestamp = v_current_child_start_timestamp THEN EXIT; ELSE RAISE DEBUG 'inner loop: v_previous_child_start_timestamp: %, v_expected_next_child_timestamp: %, v_children_created_count: %' , v_previous_child_start_timestamp, v_expected_next_child_timestamp, v_children_created_count; v_child_created := @extschema@.create_partition_time(v_parent_table, ARRAY[v_expected_next_child_timestamp]); IF v_child_created THEN v_children_created_count := v_children_created_count + 1; v_child_created := false; END IF; END IF; END LOOP; -- end expected child loop END IF; v_previous_child_schemaname := v_row.partition_schemaname; v_previous_child_tablename := v_row.partition_tablename; SELECT child_start_time INTO v_previous_child_start_timestamp FROM @extschema@.show_partition_info(format('%s', v_previous_child_schemaname||'.'||v_previous_child_tablename), p_parent_table := v_parent_table); END LOOP; -- end time loop ELSIF v_control_type = 'id' THEN v_interval_id := v_partition_interval::bigint; SELECT child_start_id INTO v_final_child_start_id FROM @extschema@.show_partition_info(format('%s', v_final_child_schemaname||'.'||v_final_child_tablename), p_parent_table := v_parent_table); FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_parent_table, 'ASC') LOOP RAISE DEBUG 'v_row.partition_tablename: %, v_final_child_start_id: %', v_row.partition_tablename, v_final_child_start_id; IF v_previous_child_tablename IS NULL THEN v_previous_child_schemaname := v_row.partition_schemaname; v_previous_child_tablename := v_row.partition_tablename; SELECT child_start_id INTO v_previous_child_start_id FROM @extschema@.show_partition_info(format('%s', v_previous_child_schemaname||'.'||v_previous_child_tablename), p_parent_table := v_parent_table); CONTINUE; END IF; v_expected_next_child_id := v_previous_child_start_id + v_interval_id; RAISE DEBUG 'v_expected_next_child_id: %', v_expected_next_child_id; IF v_expected_next_child_id = v_final_child_start_id THEN EXIT; END IF; SELECT child_start_id INTO v_current_child_start_id FROM @extschema@.show_partition_info(format('%s', v_row.partition_schemaname||'.'||v_row.partition_tablename), p_parent_table := v_parent_table); RAISE DEBUG 'v_current_child_start_id: %', v_current_child_start_id; IF v_expected_next_child_id != v_current_child_start_id THEN v_child_created := @extschema@.create_partition_id(v_parent_table, ARRAY[v_expected_next_child_id]); IF v_child_created THEN v_children_created_count := v_children_created_count + 1; v_child_created := false; END IF; SELECT partition_schema, partition_table INTO v_previous_child_schemaname, v_previous_child_tablename FROM @extschema@.show_partition_name(v_parent_table, v_expected_next_child_id::text); -- Need to stay in another inner loop until the next expected child id matches the current one -- Once it does, exit. This means gap is filled. LOOP v_previous_child_start_id := v_expected_next_child_id; v_expected_next_child_id := v_expected_next_child_id + v_interval_id; IF v_expected_next_child_id = v_current_child_start_id THEN EXIT; ELSE RAISE DEBUG 'inner loop: v_previous_child_start_id: %, v_expected_next_child_id: %, v_children_created_count: %' , v_previous_child_start_id, v_expected_next_child_id, v_children_created_count; v_child_created := @extschema@.create_partition_id(v_parent_table, ARRAY[v_expected_next_child_id]); IF v_child_created THEN v_children_created_count := v_children_created_count + 1; v_child_created := false; END IF; END IF; END LOOP; -- end expected child loop END IF; v_previous_child_schemaname := v_row.partition_schemaname; v_previous_child_tablename := v_row.partition_tablename; SELECT child_start_id INTO v_previous_child_start_id FROM @extschema@.show_partition_info(format('%s', v_previous_child_schemaname||'.'||v_previous_child_tablename), p_parent_table := v_parent_table); END LOOP; -- end id loop END IF; -- end time/id if RETURN v_children_created_count; END $$; pg_partman-4.6.0/sql/functions/reapply_privileges.sql000066400000000000000000000070371412761706100231220ustar00rootroot00000000000000CREATE FUNCTION @extschema@.reapply_privileges(p_parent_table text) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_row record; v_step_id bigint; BEGIN /* * Function to re-apply ownership & privileges on all child tables in a partition set using parent table as reference */ SELECT jobmon INTO v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon IS NULL THEN RAISE EXCEPTION 'Given table is not managed by this extention: %', p_parent_table; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; IF v_parent_tablename IS NULL THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RAISE EXCEPTION 'Given parent table does not exist: %', p_parent_table; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN RE-APPLYING PRIVILEGES TO ALL CHILD TABLES OF: %s', p_parent_table)); END IF; FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'ASC', p_include_default := true) LOOP PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_row.partition_schemaname, v_row.partition_tablename, v_job_id); END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); 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 RE-APPLYING PRIVILEGES TO ALL CHILD TABLES OF: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; pg_partman-4.6.0/sql/functions/run_maintenance.sql000066400000000000000000000544271412761706100223700ustar00rootroot00000000000000CREATE FUNCTION @extschema@.run_maintenance(p_parent_table text DEFAULT NULL, p_analyze boolean DEFAULT NULL, p_jobmon boolean DEFAULT true) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_analyze boolean; v_check_subpart int; v_control_type text; v_create_count int := 0; v_current_partition text; v_current_partition_id bigint; v_current_partition_timestamp timestamptz; v_default_tablename text; v_drop_count int := 0; v_is_default text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_created boolean; v_last_partition_id bigint; v_last_partition_timestamp timestamptz; v_max_id_parent bigint; v_max_time_default timestamptz; v_new_search_path text := '@extschema@,pg_temp'; v_next_partition_id bigint; v_next_partition_timestamp timestamptz; v_old_search_path text; v_parent_exists text; v_parent_oid oid; v_parent_schema text; v_parent_tablename text; v_partition_expression text; v_premade_count int; v_premake_id_max bigint; v_premake_id_min bigint; v_premake_timestamp_min timestamptz; v_premake_timestamp_max timestamptz; v_row record; v_row_max_id record; v_row_max_time record; v_row_sub record; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_sub_id_max bigint; v_sub_id_max_suffix bigint; v_sub_id_min bigint; v_sub_parent text; v_sub_refresh_done text[]; v_sub_timestamp_max timestamptz; v_sub_timestamp_max_suffix timestamptz; v_sub_timestamp_min timestamptz; v_tablename text; v_tables_list_sql text; BEGIN /* * Function to manage pre-creation of the next partitions in a set. * Also manages dropping old partitions if the retention option is set. * If p_parent_table is passed, will only run run_maintenance() on that one table (no matter what the configuration table may have set for it) * Otherwise, will run on all tables in the config table with p_automatic_maintenance() set to true. * For large partition sets, running analyze can cause maintenance to take longer than expected. Can set p_analyze to false to avoid a forced analyze run on PG versions before 11. 11+ does not analyze by default anymore. * Be aware that constraint exclusion may not work properly until an analyze on the partition set is run. */ v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman maintenance already running.'; RETURN; 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'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN RUN MAINTENANCE'); v_step_id := add_step(v_job_id, 'Running maintenance loop'); END IF; v_tables_list_sql := 'SELECT parent_table , partition_type , partition_interval , control , premake , undo_in_progress , sub_partition_set_full , epoch , infinite_time_partitions , retention , subscription_refresh FROM @extschema@.part_config WHERE undo_in_progress = false'; IF p_parent_table IS NULL THEN v_tables_list_sql := v_tables_list_sql || ' AND automatic_maintenance = ''on'''; ELSE v_tables_list_sql := v_tables_list_sql || format(' AND parent_table = %L', p_parent_table); END IF; FOR v_row IN EXECUTE v_tables_list_sql LOOP CONTINUE WHEN v_row.undo_in_progress; -- When sub-partitioning, retention may drop tables that were already put into the query loop values. -- Check if they still exist in part_config before continuing v_parent_exists := NULL; SELECT parent_table INTO v_parent_exists FROM @extschema@.part_config WHERE parent_table = v_row.parent_table; RAISE DEBUG 'Parent table possibly removed from part_config by retenion'; CONTINUE WHEN v_parent_exists IS NULL; -- Check for consistent data in part_config_sub table. Was unable to get this working properly as either a constraint or trigger. -- Would either delay raising an error until the next write (which I cannot predict) or disallow future edits to update a sub-partition set's configuration. -- This way at least provides a consistent way to check that I know will run. If anyone can get a working constraint/trigger, please help! SELECT sub_parent INTO v_sub_parent FROM @extschema@.part_config_sub WHERE sub_parent = v_row.parent_table; IF v_sub_parent IS NOT NULL THEN SELECT count(*) INTO v_check_subpart FROM @extschema@.check_subpart_sameconfig(v_row.parent_table); IF v_check_subpart > 1 THEN RAISE EXCEPTION 'Inconsistent data in part_config_sub table. Sub-partition tables that are themselves sub-partitions cannot have differing configuration values among their siblings. Run this query: "SELECT * FROM @extschema@.check_subpart_sameconfig(''%'');" This should only return a single row or nothing. If multiple rows are returned, the results are differing configurations in the part_config_sub table for children of the given parent. Determine the child tables of the given parent and look up their entries based on the "part_config_sub.sub_parent" column. Update the differing values to be consistent for your desired values.', v_row.parent_table; END IF; END IF; -- Shouldn't need to analyze tables for most statistics for native sets on PG11+ by default anymore IF p_analyze IS NULL THEN IF v_row.partition_type = 'native' AND current_setting('server_version_num')::int >= 110000 THEN v_analyze := false; ELSE v_analyze := true; END IF; END IF; SELECT n.nspname, c.relname, c.oid INTO v_parent_schema, v_parent_tablename, 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(v_row.parent_table, '.', 1)::name AND c.relname = split_part(v_row.parent_table, '.', 2)::name; -- Used below to see if there's any data in the parent (<=PG10) or default (PG11+) child table. IF v_row.partition_type = 'native' AND current_setting('server_version_num')::int >= 110000 THEN -- Always returns the default partition first if it exists SELECT partition_tablename INTO v_default_tablename FROM @extschema@.show_partitions(v_row.parent_table, p_include_default := true) LIMIT 1; SELECT pg_get_expr(relpartbound, v_parent_oid) INTO v_is_default FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n on c.relnamespace = n.oid WHERE n.nspname = v_parent_schema AND c.relname = v_default_tablename; IF v_is_default != 'DEFAULT' THEN v_default_tablename := v_parent_tablename; END IF; ELSE v_default_tablename := v_parent_tablename; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_row.control); v_partition_expression := CASE WHEN v_row.epoch = 'seconds' THEN format('to_timestamp(%I)', v_row.control) WHEN v_row.epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_row.control) ELSE format('%I', v_row.control) END; RAISE DEBUG 'run_maint: v_partition_expression: %', v_partition_expression; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LIMIT 1; RAISE DEBUG 'run_maint: parent_table: %, v_last_partition: %', v_row.parent_table, v_last_partition; IF v_control_type = 'time' OR (v_control_type = 'id' AND v_row.epoch <> 'none') THEN -- Run retention if needed IF v_row.retention IS NOT NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); IF v_drop_count > 0 AND v_row.partition_type <> 'native' THEN PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); END IF; END IF; IF v_row.sub_partition_set_full THEN CONTINUE; END IF; SELECT child_start_time INTO v_last_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_row.partition_interval, v_row.parent_table); -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. IF v_row.infinite_time_partitions IS TRUE THEN -- Set it to "now" so new partitions continue to be created -- For infinite_time_partitions, don't bother getting the max value in the partitions v_current_partition_timestamp = CURRENT_TIMESTAMP; ELSE FOR v_row_max_time IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%s)::text FROM %I.%I' , v_partition_expression , v_row_max_time.partition_schemaname , v_row_max_time.partition_tablename ) INTO v_current_partition_timestamp; IF v_current_partition_timestamp IS NOT NULL THEN SELECT suffix_timestamp INTO v_current_partition_timestamp FROM @extschema@.show_partition_name(v_row.parent_table, v_current_partition_timestamp::text); EXIT; END IF; END LOOP; END IF; -- end infinite time check -- Check for values in the parent/default table. If they are there and greater than all child values, use that instead -- This allows maintenance to continue working properly if there is a large gap in data insertion. Data will remain in default, but new tables will be created EXECUTE format('SELECT max(%s) FROM ONLY %I.%I', v_partition_expression, v_parent_schema, v_default_tablename) INTO v_max_time_default; RAISE DEBUG 'run_maint: v_current_partition_timestamp: %, v_max_time_default: %', v_current_partition_timestamp, v_max_time_default; IF v_current_partition_timestamp IS NULL AND v_max_time_default IS NULL THEN -- Partition set is completely empty and infinite time partitions not set -- Nothing to do CONTINUE; END IF; IF v_current_partition_timestamp IS NULL OR (v_max_time_default > v_current_partition_timestamp) THEN SELECT suffix_timestamp INTO v_current_partition_timestamp FROM @extschema@.show_partition_name(v_row.parent_table, v_max_time_default::text); END IF; -- If this is a subpartition, determine if the last child table has been made. If so, mark it as full so future maintenance runs can skip it SELECT sub_min::timestamptz, sub_max::timestamptz INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'time'); IF v_sub_timestamp_max IS NOT NULL THEN SELECT suffix_timestamp INTO v_sub_timestamp_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_timestamp_max::text); IF v_sub_timestamp_max_suffix = v_last_partition_timestamp THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; -- Check and see how many premade partitions there are. v_premade_count = round(EXTRACT('epoch' FROM age(v_last_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); v_next_partition_timestamp := v_last_partition_timestamp; RAISE DEBUG 'run_maint before loop: current_partition_timestamp: %, v_premade_count: %, v_sub_timestamp_min: %, v_sub_timestamp_max: %' , v_current_partition_timestamp , v_premade_count , v_sub_timestamp_min , v_sub_timestamp_max; -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed WHILE (v_premade_count < v_row.premake) LOOP RAISE DEBUG 'run_maint: parent_table: %, v_premade_count: %, v_next_partition_timestamp: %', v_row.parent_table, v_premade_count, v_next_partition_timestamp; IF v_next_partition_timestamp < v_sub_timestamp_min OR v_next_partition_timestamp > v_sub_timestamp_max THEN -- With subpartitioning, no need to run if the timestamp is not in the parent table's range EXIT; END IF; BEGIN v_next_partition_timestamp := v_next_partition_timestamp + v_row.partition_interval::interval; EXCEPTION WHEN datetime_field_overflow THEN v_premade_count := v_row.premake; -- do this so it can exit the premake check loop and continue in the outer for loop IF v_jobmon_schema IS NOT NULL THEN 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', format('Child partition creation skipped for parent table: %s', v_partition_time)); END IF; RAISE WARNING 'Attempted partition time interval is outside PostgreSQL''s supported time range. Child partition creation skipped for parent table %', v_row.parent_table; CONTINUE; END; v_last_partition_created := @extschema@.create_partition_time(v_row.parent_table , ARRAY[v_next_partition_timestamp] , v_analyze); IF v_last_partition_created THEN v_create_count := v_create_count + 1; IF v_row.partition_type <> 'native' THEN PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); END IF; END IF; v_premade_count = round(EXTRACT('epoch' FROM age(v_next_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); END LOOP; ELSIF v_control_type = 'id' THEN -- Run retention if needed IF v_row.retention IS NOT NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); IF v_drop_count > 0 AND v_row.partition_type <> 'native' THEN PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); END IF; END IF; IF v_row.sub_partition_set_full THEN CONTINUE; END IF; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%I)::text FROM %I.%I' , v_row.control , v_row_max_id.partition_schemaname , v_row_max_id.partition_tablename) INTO v_current_partition_id; IF v_current_partition_id IS NOT NULL THEN SELECT suffix_id INTO v_current_partition_id FROM @extschema@.show_partition_name(v_row.parent_table, v_current_partition_id::text); EXIT; END IF; END LOOP; -- Check for values in the parent/default table. If they are there and greater than all child values, use that instead -- This allows maintenance to continue working properly if there is a large gap in data insertion. Data will remain in parent, but new tables will be created EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_row.control, v_parent_schema, v_default_tablename) INTO v_max_id_parent; IF v_max_id_parent > v_current_partition_id THEN SELECT suffix_id INTO v_current_partition_id FROM @extschema@.show_partition_name(v_row.parent_table, v_max_id_parent::text); END IF; IF v_current_partition_id IS NULL THEN -- Partition set is completely empty. Nothing to do CONTINUE; END IF; SELECT child_start_id INTO v_last_partition_id FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_row.partition_interval, v_row.parent_table); -- Determine if this table is a child of a subpartition parent. If so, get limits to see if run_maintenance even needs to run for it. SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'id'); IF v_sub_id_max IS NOT NULL THEN SELECT suffix_id INTO v_sub_id_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_id_max::text); IF v_sub_id_max_suffix = v_last_partition_id THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; v_next_partition_id := v_last_partition_id; v_premade_count := ((v_last_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed. WHILE (v_premade_count < v_row.premake) LOOP RAISE DEBUG 'run_maint: parent_table: %, v_premade_count: %, v_next_partition_id: %', v_row.parent_table, v_premade_count, v_next_partition_id; IF v_next_partition_id < v_sub_id_min OR v_next_partition_id > v_sub_id_max THEN -- With subpartitioning, no need to run if the id is not in the parent table's range EXIT; END IF; v_next_partition_id := v_next_partition_id + v_row.partition_interval::bigint; v_last_partition_created := @extschema@.create_partition_id(v_row.parent_table, ARRAY[v_next_partition_id], v_analyze); IF v_last_partition_created THEN v_create_count := v_create_count + 1; IF v_row.partition_type <> 'native' THEN PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); END IF; END IF; v_premade_count := ((v_next_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); END LOOP; END IF; -- end main IF check for time or id -- Refresh subscriptions in order to catch new tables that may have been created in the publication -- Keep track of which ones have been refreshed so it doesn't needlessly run more than once -- in a single maintenance run IF v_row.subscription_refresh IS NOT NULL THEN IF v_sub_refresh_done @> ARRAY[v_row.subscription_refresh] THEN CONTINUE; ELSE v_sql = format('ALTER SUBSCRIPTION %I REFRESH PUBLICATION', v_row.subscription_refresh); RAISE DEBUG '%', v_sql; EXECUTE v_sql; PERFORM array_append(v_sub_refresh_done, v_row.subscription_refresh); END IF; END IF; END LOOP; -- end of main loop through part_config IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Partition maintenance finished. %s partitions made. %s partitions dropped.', v_create_count, v_drop_count)); 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'); 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 RUN MAINTENANCE'')', v_jobmon_schema) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; pg_partman-4.6.0/sql/functions/show_partition_info.sql000066400000000000000000000146121412761706100232760ustar00rootroot00000000000000CREATE FUNCTION @extschema@.show_partition_info(p_child_table text , p_partition_interval text DEFAULT NULL , p_parent_table text DEFAULT NULL , OUT child_start_time timestamptz , OUT child_end_time timestamptz , OUT child_start_id bigint , OUT child_end_id bigint , OUT suffix text) RETURNS record LANGUAGE plpgsql STABLE AS $$ DECLARE v_child_schema text; v_child_tablename text; v_start_time_string text; v_control text; v_control_type text; v_datetime_string text; v_epoch text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_table text; v_partition_interval text; v_partition_type text; v_quarter text; v_start_time_epoch double precision; v_suffix text; v_suffix_position int; v_year text; BEGIN /* * Show the data boundries for a given child table as well as the suffix that will be used. * Passing the parent table argument improves performance by avoiding a catalog lookup. * Passing an interval lets you set one different than the default configured one if desired. */ 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'); SELECT n.nspname, c.relname INTO v_child_schema, v_child_tablename FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = split_part(p_child_table, '.', 1)::name AND c.relname = split_part(p_child_table, '.', 2)::name; IF v_child_tablename IS NULL THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RAISE EXCEPTION 'Child table given does not exist (%)', p_child_table; END IF; IF p_parent_table IS NULL THEN SELECT n.nspname||'.'|| c.relname INTO v_parent_table FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhparent JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhrelid::regclass = p_child_table::regclass; ELSE v_parent_table := p_parent_table; END IF; IF p_partition_interval IS NULL THEN SELECT control, partition_interval, partition_type, datetime_string, epoch INTO v_control, v_partition_interval, v_partition_type, v_datetime_string, v_epoch FROM @extschema@.part_config WHERE parent_table = v_parent_table; ELSE v_partition_interval := p_partition_interval; SELECT control, partition_type, datetime_string, epoch INTO v_control, v_partition_type, v_datetime_string, v_epoch FROM @extschema@.part_config WHERE parent_table = v_parent_table; END IF; IF v_control IS NULL THEN RAISE EXCEPTION 'Parent table of given child not managed by pg_partman: %', v_parent_table; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_child_schema, v_child_tablename, v_control); v_suffix_position := (length(v_child_tablename) - position('p_' in reverse(v_child_tablename))) + 2; v_suffix := substring(v_child_tablename from v_suffix_position); RAISE DEBUG 'show_partition_info: v_child_schema: %, v_child_tablename: %, v_suffix: %', v_child_schema, v_child_tablename, v_suffix; IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN IF v_partition_type = 'native' THEN -- Look at actual partition bounds in catalog and pull values from there. -- For native partitioning, handles any possible interval type much better than old methods in remaining conditions of this IF block SELECT (regexp_match(pg_get_expr(c.relpartbound, c.oid, true) , $REGEX$\(([^)]+)\) TO \(([^)]+)\)$REGEX$))[1]::text INTO v_start_time_string FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_child_tablename AND n.nspname = v_child_schema; IF v_control_type = 'time' THEN child_start_time := v_start_time_string::timestamptz; ELSIF (v_control_type = 'id' AND v_epoch <> 'none') THEN -- bigint data type is stored as a single-quoted string in the partition expression. Must strip quotes for valid type-cast. v_start_time_string := trim(BOTH '''' FROM v_start_time_string); IF v_epoch = 'seconds' THEN child_start_time := to_timestamp(v_start_time_string::double precision); ELSIF v_epoch = 'milliseconds' THEN child_start_time := to_timestamp((v_start_time_string::double precision) / 1000); END IF; ELSE RAISE EXCEPTION 'Unexpected code path in show_partition_info(). Please report this bug with the configuration that lead to it.'; END IF; ELSIF v_partition_interval::interval <> '3 months' OR (v_partition_interval::interval = '3 months' AND v_partition_type = 'time-custom') THEN child_start_time := to_timestamp(v_suffix, v_datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(v_suffix, 'q', 1); v_quarter := split_part(v_suffix, 'q', 2); CASE WHEN v_quarter = '1' THEN child_start_time := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN child_start_time := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN child_start_time := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN child_start_time := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); ELSE -- handle case when partition name did not use "q" convetion child_start_time := to_timestamp(v_suffix, v_datetime_string); END CASE; END IF; child_end_time := (child_start_time + v_partition_interval::interval); ELSIF v_control_type = 'id' THEN -- Note for future updates, if trigger based partitioning is dropped, do catalog lookup for integer boundaries similar to time. child_start_id := v_suffix::bigint; child_end_id := (child_start_id + v_partition_interval::bigint) - 1; ELSE RAISE EXCEPTION 'Invalid partition type encountered in show_partition_info()'; END IF; suffix = v_suffix; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN; END $$; pg_partman-4.6.0/sql/functions/show_partition_name.sql000066400000000000000000000166011412761706100232630ustar00rootroot00000000000000CREATE FUNCTION @extschema@.show_partition_name(p_parent_table text, p_value text, OUT partition_schema text, OUT partition_table text, OUT suffix_timestamp timestamptz, OUT suffix_id bigint, OUT table_exists boolean) RETURNS record LANGUAGE plpgsql STABLE AS $$ DECLARE v_child_end_time timestamptz; v_child_exists text; v_child_larger boolean := false; v_child_smaller boolean := false; v_child_start_time timestamptz; v_control text; v_control_type text; v_datetime_string text; v_epoch text; v_given_timestamp timestamptz; v_max_range timestamptz; v_min_range timestamptz; v_parent_schema text; v_parent_tablename text; v_partition_interval text; v_row record; v_type text; BEGIN /* * Given a parent table and partition value, return the name of the child partition it would go in. * If using epoch time partitioning, give the text representation of the timestamp NOT the epoch integer value (use to_timestamp() to convert epoch values). * Also returns just the suffix value and true if the child table exists or false if it does not */ SELECT partition_type , control , partition_interval , datetime_string , epoch INTO v_type , v_control , v_partition_interval , v_datetime_string , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_type IS NULL THEN RAISE EXCEPTION 'Parent table given is not managed by pg_partman (%)', p_parent_table; END IF; SELECT n.nspname, c.relname INTO v_parent_schema, v_parent_tablename 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_tablename IS NULL THEN RAISE EXCEPTION 'Parent table given does not exist (%)', p_parent_table; END IF; partition_schema := v_parent_schema; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF ( (v_control_type = 'time') OR (v_control_type = 'id' AND v_epoch <> 'none') ) THEN v_given_timestamp := p_value::timestamptz; FOR v_row IN SELECT partition_schemaname ||'.'|| partition_tablename AS child_table FROM @extschema@.show_partitions(p_parent_table, 'DESC') LOOP SELECT child_start_time INTO v_child_start_time FROM @extschema@.show_partition_info(v_row.child_table, v_partition_interval, p_parent_table); -- Don't use child_end_time from above function to avoid edge cases around user supplied timestamps v_child_end_time := v_child_start_time + v_partition_interval::interval; IF v_given_timestamp >= v_child_end_time THEN -- given value is higher than any existing child table. handled below. v_child_larger := true; EXIT; END IF; IF v_given_timestamp >= v_child_start_time THEN -- found target child table v_child_smaller := false; suffix_timestamp := v_child_start_time; EXIT; END IF; -- Should only get here if no matching child table was found. handled below. v_child_smaller := true; END LOOP; IF v_child_start_time IS NULL OR v_child_end_time IS NULL THEN -- This should never happen since there should never be a partition set without children. -- Handling just in case so issues can be reported with context RAISE EXCEPTION 'Unexpected code path encountered in show_partition_name(). Please report this issue to author with relevant partition config info.'; END IF; IF v_child_larger THEN LOOP -- keep adding interval until found v_child_start_time := v_child_start_time + v_partition_interval::interval; v_child_end_time := v_child_end_time + v_partition_interval::interval; IF v_given_timestamp >= v_child_start_time AND v_given_timestamp < v_child_end_time THEN suffix_timestamp := v_child_start_time; EXIT; END IF; END LOOP; ELSIF v_child_smaller THEN LOOP -- keep subtracting interval until found v_child_start_time := v_child_start_time - v_partition_interval::interval; v_child_end_time := v_child_end_time - v_partition_interval::interval; IF v_given_timestamp >= v_child_start_time AND v_given_timestamp < v_child_end_time THEN suffix_timestamp := v_child_start_time; EXIT; END IF; END LOOP; END IF; partition_table := @extschema@.check_name_length(v_parent_tablename, to_char(suffix_timestamp, v_datetime_string), TRUE); ELSIF v_control_type = 'id' AND v_type <> 'time-custom' THEN suffix_id := (p_value::bigint - (p_value::bigint % v_partition_interval::bigint)); partition_table := @extschema@.check_name_length(v_parent_tablename, suffix_id::text, TRUE); ELSIF v_type = 'time-custom' THEN SELECT child_table, lower(partition_range) INTO partition_table, suffix_timestamp FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND partition_range @> p_value::timestamptz; IF partition_table IS NULL THEN SELECT max(upper(partition_range)) INTO v_max_range FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table; SELECT min(lower(partition_range)) INTO v_min_range FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table; IF p_value::timestamptz >= v_max_range THEN suffix_timestamp := v_max_range; LOOP -- Keep incrementing higher until given value is below the upper range suffix_timestamp := suffix_timestamp + v_partition_interval::interval; IF p_value::timestamptz < suffix_timestamp THEN -- Have to subtract one interval because the value would actually be in the partition previous -- to this partition timestamp since the partition names contain the lower boundary suffix_timestamp := suffix_timestamp - v_partition_interval::interval; EXIT; END IF; END LOOP; ELSIF p_value::timestamptz < v_min_range THEN suffix_timestamp := v_min_range; LOOP -- Keep decrementing lower until given value is below or equal to the lower range suffix_timestamp := suffix_timestamp - v_partition_interval::interval; IF p_value::timestamptz >= suffix_timestamp THEN EXIT; END IF; END LOOP; ELSE RAISE EXCEPTION 'Unable to determine a valid child table for the given parent table and value'; END IF; partition_table := @extschema@.check_name_length(v_parent_tablename, to_char(suffix_timestamp, v_datetime_string), TRUE); END IF; END IF; SELECT tablename INTO v_child_exists FROM pg_catalog.pg_tables WHERE schemaname = partition_schema::name AND tablename = partition_table::name; IF v_child_exists IS NOT NULL THEN table_exists := true; ELSE table_exists := false; END IF; RETURN; END $$; pg_partman-4.6.0/sql/functions/show_partitions.sql000066400000000000000000000103341412761706100224430ustar00rootroot00000000000000CREATE FUNCTION @extschema@.show_partitions (p_parent_table text, p_order text DEFAULT 'ASC', p_include_default boolean DEFAULT false) RETURNS TABLE (partition_schemaname text, partition_tablename text) LANGUAGE plpgsql STABLE SET search_path = @extschema@,pg_temp AS $$ DECLARE v_control text; v_control_type text; v_datetime_string text; v_default_sql text; v_epoch text; v_parent_schema text; v_parent_tablename text; v_partition_interval text; v_partition_type text; v_sql text; BEGIN /* * Function to list all child partitions in a set in logical order. * Default partition is not listed by default since that's the common usage internally * If p_include_default is set true, default is always listed first. */ IF upper(p_order) NOT IN ('ASC', 'DESC') THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RAISE EXCEPTION 'p_order paramter must be one of the following values: ASC, DESC'; END IF; SELECT partition_type , partition_interval , datetime_string , control , epoch INTO v_partition_type , v_partition_interval , v_datetime_string , v_control , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_partition_type IS NULL THEN RAISE EXCEPTION 'Given parent table not managed by pg_partman: %', p_parent_table; END IF; SELECT n.nspname, c.relname INTO v_parent_schema, v_parent_tablename 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_tablename IS NULL THEN RAISE EXCEPTION 'Given parent table not found in system catalogs: %', p_parent_table; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); RAISE DEBUG 'show_partitions: v_parent_schema: %, v_parent_tablename: %, v_datetime_string: %', v_parent_schema, v_parent_tablename, v_datetime_string; v_sql := format('SELECT n.nspname::text AS partition_schemaname, c.relname::text AS partition_name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = ''%I.%I''::regclass' , v_parent_schema , v_parent_tablename); IF v_partition_type = 'native' AND current_setting('server_version_num')::int >= 110000 THEN IF p_include_default THEN -- Return the default partition immediately as first item in list v_default_sql := v_sql || format(' AND pg_get_expr(relpartbound, c.oid) = ''DEFAULT'''); RAISE DEBUG 'show_partitions: v_default_sql: %', v_default_sql; RETURN QUERY EXECUTE v_default_sql; END IF; v_sql := v_sql || format(' AND pg_get_expr(relpartbound, c.oid) != ''DEFAULT'''); END IF; IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN IF v_partition_interval::interval <> '3 months' OR (v_partition_interval::interval = '3 months' AND v_partition_type = 'time-custom') THEN v_sql := v_sql || format(' ORDER BY to_timestamp(substring(c.relname from ((length(c.relname) - position(''p_'' in reverse(c.relname))) + 2) ), %L) %s' , v_datetime_string , p_order); ELSE -- For quarterly, to_timestamp() doesn't recognize "Q" in datetime string. -- First order by just the year, then order by the quarter number (should be last character in table name) v_sql := v_sql || format(' ORDER BY to_timestamp(substring(c.relname from ((length(c.relname) - position(''p_'' in reverse(c.relname))) + 2) for 4), ''YYYY'') %s , substring(reverse(c.relname) from 1 for 1) %s' , p_order , p_order); END IF; ELSIF v_control_type = 'id' THEN v_sql := v_sql || format(' ORDER BY substring(c.relname from ((length(c.relname) - position(''p_'' in reverse(c.relname))) + 2) )::bigint %s' , p_order); END IF; RAISE DEBUG 'show_partitions: v_sql: %', v_sql; RETURN QUERY EXECUTE v_sql; END $$; pg_partman-4.6.0/sql/functions/stop_sub_partition.sql000066400000000000000000000021461412761706100231400ustar00rootroot00000000000000CREATE FUNCTION @extschema@.stop_sub_partition(p_parent_table text, p_jobmon boolean DEFAULT true) RETURNS boolean LANGUAGE plpgsql AS $$ DECLARE v_job_id bigint; v_jobmon_schema text; v_step_id bigint; BEGIN /* * Stop a given parent table from causing its children to be subpartitioned */ 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; END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.add_job(''PARTMAN STOP SUBPARTITIONING'')', v_jobmon_schema) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''Stopped subpartitioning for %s'')', v_jobmon_schema, v_job_id, p_parent_table) INTO v_step_id; END IF; DELETE FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.update_step(%s, %L, %L)', v_jobmon_schema, v_step_id, 'OK', 'Done'); EXECUTE format('SELECT %I.close_job(%s)', v_jobmon_schema, v_job_id); END IF; RETURN true; END $$; pg_partman-4.6.0/sql/functions/undo_partition.sql000066400000000000000000000544161412761706100222560ustar00rootroot00000000000000CREATE FUNCTION @extschema@.undo_partition( p_parent_table text , p_batch_count int DEFAULT 1 , p_batch_interval text DEFAULT NULL , p_keep_table boolean DEFAULT true , p_lock_wait numeric DEFAULT 0 , p_target_table text DEFAULT NULL , p_ignored_columns text[] DEFAULT NULL , p_drop_cascade boolean DEFAULT false , OUT partitions_undone int , OUT rows_undone bigint) RETURNS record LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_batch_interval_id bigint; v_batch_interval_time interval; v_batch_loop_count int := 0; v_child_loop_total bigint := 0; v_child_table text; v_col text; v_column_list text; v_control text; v_control_type text; v_child_min_id bigint; v_child_min_time timestamptz; v_epoch text; v_function_name text; v_jobmon boolean; v_jobmon_schema text; v_job_id bigint; v_inner_loop_count int; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_new_search_path text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_expression text; v_partition_interval text; v_partition_type text; v_relkind char; v_row record; v_rowcount bigint; v_sql text; v_step_id bigint; v_sub_count int; v_target_schema text; v_target_tablename text; v_template_schema text; v_template_siblings int; v_template_table text; v_template_tablename text; v_total bigint := 0; v_trig_name text; v_undo_count int := 0; BEGIN /* * For native, moves data to new, target table since data cannot be moved to parent. * Leaves old parent table as is and does not change name of new table. * For trigger-based, moves data to parent */ v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition_native')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition_native already running.'; partitions_undone = -1; RETURN; END IF; IF p_parent_table = p_target_table THEN RAISE EXCEPTION 'Target table cannot be the same as the parent table'; END IF; SELECT partition_interval::text , partition_type , control , jobmon , epoch , template_table INTO v_partition_interval , v_partition_type , v_control , v_jobmon , v_epoch , v_template_table FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_control IS NULL THEN RAISE EXCEPTION 'No configuration found for pg_partman for given parent table: %', p_parent_table; END IF; IF v_partition_type = 'native' AND p_target_table IS NULL THEN RAISE EXCEPTION 'Natively partitioned tables require setting the p_target_table option'; END IF; SELECT n.nspname, c.relname, c.relkind INTO v_parent_schema, v_parent_tablename, v_relkind 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_tablename IS NULL THEN RAISE EXCEPTION 'Given parent table not found in system catalogs: %', p_parent_table; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN IF p_batch_interval IS NULL THEN v_batch_interval_time := v_partition_interval::interval; ELSE v_batch_interval_time := p_batch_interval::interval; END IF; ELSIF v_control_type = 'id' THEN IF p_batch_interval IS NULL THEN v_batch_interval_id := v_partition_interval::bigint; ELSE v_batch_interval_id := p_batch_interval::bigint; END IF; ELSE RAISE EXCEPTION 'Data type of control column in given partition set must be either data/time or integer.'; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Check if any child tables are themselves partitioned or part of an inheritance tree. Prevent undo at this level if so. -- Need to lock child tables at all levels before multi-level undo can be performed safely. FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table) LOOP SELECT count(*) INTO v_sub_count FROM pg_catalog.pg_inherits i JOIN pg_catalog.pg_class c ON i.inhparent = c.oid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_row.partition_tablename::name AND n.nspname = v_row.partition_schemaname::name; IF v_sub_count > 0 THEN RAISE EXCEPTION 'Child table for this parent has child table(s) itself (%). Run undo partitioning on this table or remove inheritance first to ensure all data is properly moved to parent', v_row.partition_schemaname||'.'||v_row.partition_tablename; END IF; END LOOP; IF p_target_table IS NOT NULL THEN SELECT n.nspname, c.relname INTO v_target_schema, v_target_tablename FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = split_part(p_target_table, '.', 1)::name AND c.relname = split_part(p_target_table, '.', 2)::name; ELSE v_target_schema := v_parent_schema; v_target_tablename := v_parent_tablename; END IF; IF v_target_tablename IS NULL THEN RAISE EXCEPTION 'Given target table not found in system catalogs: %', p_target_table; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN UNDO PARTITIONING: %s', p_parent_table)); v_step_id := add_step(v_job_id, format('Undoing partitioning for table %s', p_parent_table)); END IF; v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) END; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; IF v_partition_type != 'native' THEN -- Stop data going into child tables on non-native partition sets. v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); -- Double-check for proper object existence SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t JOIN pg_catalog.pg_class c ON t.tgrelid = c.oid WHERE tgname = v_trig_name::name AND c.relname = v_parent_tablename::name; SELECT proname INTO v_function_name FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid WHERE n.nspname = v_parent_schema::name AND proname = v_function_name::name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_parent_tablename); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; partitions_undone = -1; RETURN; END IF; END IF; -- END p_lock_wait IF EXECUTE format('DROP TRIGGER IF EXISTS %I ON %I.%I', v_trig_name, v_parent_schema, v_parent_tablename); END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later IF v_function_name IS NOT NULL THEN EXECUTE format('DROP FUNCTION IF EXISTS %I.%I()', v_parent_schema, v_function_name); END IF; END IF; -- end pg_partman trigger cleanup IF v_jobmon_schema IS NOT NULL THEN IF (v_trig_name IS NOT NULL OR v_function_name IS NOT NULL) THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); ELSE PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process.'); END IF; END IF; -- Generate column list to use in SELECT/INSERT statements below. Allows for exclusion of GENERATED (or any other desired) columns. v_sql := format ('SELECT ''"''||string_agg(attname, ''","'')||''"'' 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 n.nspname = %L AND c.relname = %L AND a.attnum > 0 AND a.attisdropped = false' , v_target_schema , v_target_tablename); IF p_ignored_columns IS NOT NULL THEN FOREACH v_col IN ARRAY p_ignored_columns LOOP v_sql := v_sql || format(' AND attname != %L ', v_col); END LOOP; END IF; EXECUTE v_sql INTO v_column_list; <> LOOP -- Get ordered list of child table in set. Store in variable one at a time per loop until none are left or batch count is reached. -- This easily allows it to loop over same child table until empty or move onto next child table after it's dropped -- Include the native default table to ensure all data there is removed as well (final parameter = true) SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC', TRUE) LIMIT 1; EXIT outer_child_loop WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Removing child partition: %s.%s', v_parent_schema, v_child_table)); END IF; IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN EXECUTE format('SELECT min(%s) FROM %I.%I', v_partition_expression, v_parent_schema, v_child_table) INTO v_child_min_time; ELSIF v_control_type = 'id' THEN EXECUTE format('SELECT min(%s) FROM %I.%I', v_partition_expression, v_parent_schema, v_child_table) INTO v_child_min_id; END IF; IF v_child_min_time IS NULL AND v_child_min_id IS NULL THEN -- No rows left in this child table. Remove from partition set. -- lockwait timeout for table drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_child_table); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; partitions_undone = -1; RETURN; END IF; END IF; -- END p_lock_wait IF v_lock_obtained := FALSE; -- reset for reuse later IF v_partition_type = 'native' THEN EXECUTE format('ALTER TABLE %I.%I DETACH PARTITION %I.%I' , v_parent_schema , v_parent_tablename , v_parent_schema , v_child_table); ELSE EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_parent_schema , v_child_table , v_parent_schema , v_parent_tablename); END IF; IF p_keep_table = false THEN v_sql := 'DROP TABLE %I.%I'; IF p_drop_cascade THEN v_sql := v_sql || ' CASCADE'; END IF; EXECUTE format(v_sql, v_parent_schema, v_child_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table DROPPED. Moved %s rows to target table', v_child_loop_total)); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table DETACHED/UNINHERITED from parent, not DROPPED. Moved %s rows to target table', v_child_loop_total)); END IF; END IF; IF v_partition_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_parent_schema||'.'||v_child_table; END IF; v_undo_count := v_undo_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached CONTINUE outer_child_loop; -- skip data moving steps below END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM %I.%I WHERE %I <= %L FOR UPDATE NOWAIT' , v_parent_schema , v_child_table , v_control , v_child_min_time + (v_batch_interval_time * v_inner_loop_count)); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; partitions_undone = -1; RETURN; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval EXECUTE format('WITH move_data AS ( DELETE FROM %I.%I WHERE %s <= %L RETURNING %s ) INSERT INTO %I.%I (%5$s) SELECT %5$s FROM move_data' , v_parent_schema , v_child_table , v_partition_expression , v_child_min_time + (v_batch_interval_time * v_inner_loop_count) , v_column_list , v_target_schema , v_target_tablename); GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Moved %s rows to target table.', v_child_loop_total)); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; -- Check again if table is empty and go to outer loop again to drop it if so EXECUTE format('SELECT min(%s) FROM %I.%I', v_partition_expression, v_parent_schema, v_child_table) INTO v_child_min_time; CONTINUE outer_child_loop WHEN v_child_min_time IS NULL; ELSIF v_control_type = 'id' THEN IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM %I.%I WHERE %I <= %L FOR UPDATE NOWAIT' , v_parent_schema , v_child_table , v_control , v_child_min_id + (v_batch_interval_id * v_inner_loop_count)); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; partitions_undone = -1; RETURN; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval EXECUTE format('WITH move_data AS ( DELETE FROM %I.%I WHERE %s <= %L RETURNING %s) INSERT INTO %I.%I (%5$s) SELECT %5$s FROM move_data' , v_parent_schema , v_child_table , v_partition_expression , v_child_min_id + (v_batch_interval_id * v_inner_loop_count) , v_column_list , v_target_schema , v_target_tablename); GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Moved %s rows to target table.', v_child_loop_total)); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; -- Check again if table is empty and go to outer loop again to drop it if so EXECUTE format('SELECT min(%s) FROM %I.%I', v_partition_expression, v_parent_schema, v_child_table) INTO v_child_min_id; CONTINUE outer_child_loop WHEN v_child_min_id IS NULL; END IF; -- end v_control_type check EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC', TRUE) LIMIT 1; IF v_child_table IS NULL THEN DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; -- Check if any other config entries still have this template table and don't remove if so -- Allows other sibling/parent tables to still keep using in case entire partition set isn't being undone SELECT count(*) INTO v_template_siblings FROM @extschema@.part_config WHERE template_table = v_template_table; 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(v_template_table, '.', 1)::name AND c.relname = split_part(v_template_table, '.', 2)::name; IF v_template_siblings = 0 AND v_template_tablename IS NOT NULL THEN EXECUTE format('DROP TABLE IF EXISTS %I.%I', v_template_schema, v_template_tablename); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Moved % row(s) to the target table. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', format('Moved %s row(s) to the target table. Removed %s partitions.', v_total, v_undo_count)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); partitions_undone := v_undo_count; rows_undone := v_total; 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 UNDO PARTITIONING: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; pg_partman-4.6.0/sql/procedures/000077500000000000000000000000001412761706100166305ustar00rootroot00000000000000pg_partman-4.6.0/sql/procedures/partition_data_proc.sql000066400000000000000000000136771412761706100234140ustar00rootroot00000000000000CREATE PROCEDURE @extschema@.partition_data_proc (p_parent_table text, p_interval text DEFAULT NULL, p_batch int DEFAULT NULL, p_wait int DEFAULT 1, p_source_table text DEFAULT NULL, p_order text DEFAULT 'ASC', p_lock_wait int DEFAULT 0, p_lock_wait_tries int DEFAULT 10, p_quiet boolean DEFAULT false, p_ignored_columns text[] DEFAULT NULL) LANGUAGE plpgsql AS $$ DECLARE v_adv_lock boolean; v_batch_count int := 0; v_control text; v_control_type text; v_epoch text; v_is_autovac_off boolean := false; v_lockwait_count int := 0; v_parent_schema text; v_parent_tablename text; v_row record; v_rows_moved bigint; v_source_schema text; v_source_tablename text; v_sql text; v_total bigint := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman partition_data_proc'), hashtext(p_parent_table)); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman partition_data_proc already running for given parent table: %.', p_parent_table; RETURN; END IF; SELECT control, epoch INTO v_control, v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: No entry in part_config found for given table: %', p_parent_table; END IF; SELECT n.nspname, c.relname INTO v_parent_schema, v_parent_tablename 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_tablename IS NULL THEN RAISE EXCEPTION 'Unable to find given parent table in system catalogs. Ensure it is schema qualified: %', p_parent_table; END IF; IF p_source_table IS NOT NULL THEN SELECT n.nspname, c.relname INTO v_source_schema, v_source_tablename FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = split_part(p_source_table, '.', 1)::name AND c.relname = split_part(p_source_table, '.', 2)::name; IF v_source_tablename IS NULL THEN RAISE EXCEPTION 'Unable to find given source table in system catalogs. Ensure it is schema qualified: %', p_source_table; END IF; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type = 'id' AND v_epoch <> 'none' THEN v_control_type := 'time'; END IF; /* -- Currently no way to catch exception and reset autovac settings back to normal. Until I can do that, leaving this feature out for now -- Leaving the functions to turn off/reset in to let people do that manually if desired IF p_autovacuum_on = false THEN -- Add this parameter back to definition when this is working -- Turn off autovac for parent, source table if set, and all child tables v_is_autovac_off := @extschema@.autovacuum_off(v_parent_schema, v_parent_tablename, v_source_schema, v_source_tablename); COMMIT; END IF; */ v_sql := format('SELECT %I.partition_data_%s (%L, p_lock_wait := %L, p_order := %L, p_analyze := false' , '@extschema@', v_control_type, p_parent_table, p_lock_wait, p_order); IF p_interval IS NOT NULL THEN v_sql := v_sql || format(', p_batch_interval := %L', p_interval); END IF; IF p_source_table IS NOT NULL THEN v_sql := v_sql || format(', p_source_table := %L', p_source_table); END IF; IF p_ignored_columns IS NOT NULL THEN v_sql := v_sql || format(', p_ignored_columns := %L', p_ignored_columns); END IF; v_sql := v_sql || ')'; RAISE DEBUG 'partition_data sql: %', v_sql; LOOP EXECUTE v_sql INTO v_rows_moved; -- If lock wait timeout, do not increment the counter IF v_rows_moved != -1 THEN v_batch_count := v_batch_count + 1; v_total := v_total + v_rows_moved; v_lockwait_count := 0; ELSE v_lockwait_count := v_lockwait_count + 1; IF v_lockwait_count > p_lock_wait_tries THEN RAISE EXCEPTION 'Quitting due to inability to get lock on next batch of rows to be moved'; END IF; END IF; IF p_quiet = false THEN IF v_rows_moved > 0 THEN RAISE NOTICE 'Batch: %, Rows moved: %', v_batch_count, v_rows_moved; ELSIF v_rows_moved = -1 THEN RAISE NOTICE 'Unable to obtain row locks for data to be moved. Trying again...'; END IF; END IF; -- If no rows left or given batch argument limit is reached IF v_rows_moved = 0 OR (p_batch > 0 AND v_batch_count >= p_batch) THEN EXIT; END IF; COMMIT; PERFORM pg_sleep(p_wait); RAISE DEBUG 'v_rows_moved: %, v_batch_count: %, v_total: %, v_lockwait_count: %, p_wait: %', p_wait, v_rows_moved, v_batch_count, v_total, v_lockwait_count; END LOOP; /* IF v_is_autovac_off = true THEN -- Reset autovac back to default if it was turned off by this procedure PERFORM @extschema@.autovacuum_reset(v_parent_schema, v_parent_tablename, v_source_schema, v_source_tablename); COMMIT; END IF; */ IF p_quiet = false THEN RAISE NOTICE 'Total rows moved: %', v_total; END IF; RAISE NOTICE 'Ensure to VACUUM ANALYZE the parent (and source table if used) after partitioning data'; /* Leaving here until I can figure out what's wrong with procedures and exception handling EXCEPTION WHEN QUERY_CANCELED THEN ROLLBACK; -- Reset autovac back to default if it was turned off by this procedure IF v_is_autovac_off = true THEN PERFORM @extschema@.autovacuum_reset(v_parent_schema, v_parent_tablename, v_source_schema, v_source_tablename); END IF; RAISE EXCEPTION '%', SQLERRM; WHEN OTHERS THEN ROLLBACK; -- Reset autovac back to default if it was turned off by this procedure IF v_is_autovac_off = true THEN PERFORM @extschema@.autovacuum_reset(v_parent_schema, v_parent_tablename, v_source_schema, v_source_tablename); END IF; RAISE EXCEPTION '%', SQLERRM; */ END; $$; pg_partman-4.6.0/sql/procedures/reapply_constraints_proc.sql000066400000000000000000000127601412761706100245050ustar00rootroot00000000000000CREATE PROCEDURE @extschema@.reapply_constraints_proc(p_parent_table text, p_drop_constraints boolean DEFAULT false, p_apply_constraints boolean DEFAULT false, p_wait int DEFAULT 0, p_dryrun boolean DEFAULT false) LANGUAGE plpgsql AS $$ DECLARE v_adv_lock boolean; v_child_stop text; v_control text; v_control_type text; v_datetime_string text; v_epoch text; v_last_partition text; v_last_partition_id bigint; v_last_partition_timestamp timestamptz; v_optimize_constraint int; v_parent_schema text; v_parent_tablename text; v_partition_interval text; v_partition_suffix text; v_premake int; v_row record; v_sql text; BEGIN /* * Procedure for reapplying additional constraints managed by pg_partman on child tables. See docs for additional info on this special constraint management. * Procedure can run in two distinct modes: 1) Drop all constraints 2) Apply all constraints. * If both modes are run in a single call, drop is run before apply. * Typical usage would be to run the drop mode, edit the data, then run apply mode to re-create all constraints on a partition set." */ v_adv_lock := pg_try_advisory_lock(hashtext('pg_partman reapply_constraints')); IF v_adv_lock = false THEN RAISE NOTICE 'Partman reapply_constraints_proc already running or another session has not released its advisory lock.'; RETURN; END IF; SELECT control, premake, optimize_constraint, datetime_string, epoch, partition_interval INTO v_control, v_premake, v_optimize_constraint, v_datetime_string, v_epoch, v_partition_interval FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_premake IS NULL THEN RAISE EXCEPTION 'Unable to find given parent in pg_partman config: %. This procedure is only meant to be called on pg_partman managed partition sets.', p_parent_table; END IF; SELECT n.nspname, c.relname INTO v_parent_schema, v_parent_tablename 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_tablename IS NULL THEN RAISE EXCEPTION 'Unable to find given parent table in system catalogs. Ensure it is schema qualified: %', p_parent_table; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); -- Determine child table to stop creating constraints on based on optimize_constraint value -- Same code in apply_constraints.sql SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN SELECT child_start_time INTO v_last_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_partition_interval, p_parent_table); v_partition_suffix := to_char(v_last_partition_timestamp - (v_partition_interval::interval * (v_optimize_constraint + v_premake + 1) ), v_datetime_string); ELSIF v_control_type = 'id' THEN SELECT child_start_id INTO v_last_partition_id FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_partition_interval, p_parent_table); v_partition_suffix := (v_last_partition_id - (v_partition_interval::bigint * (v_optimize_constraint + v_premake + 1) ))::text; END IF; v_child_stop := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); v_sql := format('SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(%L, %L)', p_parent_table, 'ASC'); RAISE DEBUG 'reapply_constraint: v_parent_tablename: % , v_partition_suffix: %, v_child_stop: %, v_sql: %', v_parent_tablename, v_partition_suffix, v_child_stop, v_sql; v_row := NULL; FOR v_row IN EXECUTE v_sql LOOP IF p_drop_constraints THEN IF p_dryrun THEN RAISE NOTICE 'DRYRUN NOTICE: Dropping constraints on child table: %.%', v_row.partition_schemaname, v_row.partition_tablename; ELSE RAISE DEBUG 'reapply_constraint drop: %.%', v_row.partition_schemaname, v_row.partition_tablename; PERFORM @extschema@.drop_constraints(p_parent_table, format('%s.%s', v_row.partition_schemaname, v_row.partition_tablename)::text); END IF; END IF; -- end drop COMMIT; IF p_apply_constraints THEN IF p_dryrun THEN RAISE NOTICE 'DRYRUN NOTICE: Applying constraints on child table: %.%', v_row.partition_schemaname, v_row.partition_tablename; ELSE RAISE DEBUG 'reapply_constraint apply: %.%', v_row.partition_schemaname, v_row.partition_tablename; PERFORM @extschema@.apply_constraints(p_parent_table, format('%s.%s', v_row.partition_schemaname, v_row.partition_tablename)::text); END IF; END IF; -- end apply IF v_row.partition_tablename = v_child_stop THEN RAISE DEBUG 'reapply_constraint: Reached stop at %.%', v_row.partition_schemaname, v_row.partition_tablename; EXIT; -- stop creating constraints after optimize target is reached END IF; COMMIT; PERFORM pg_sleep(p_wait); END LOOP; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); PERFORM pg_advisory_unlock(hashtext('pg_partman reapply_constraints')); END $$; pg_partman-4.6.0/sql/procedures/run_maintenance_proc.sql000066400000000000000000000033131412761706100235420ustar00rootroot00000000000000CREATE PROCEDURE @extschema@.run_maintenance_proc(p_wait int DEFAULT 0, p_analyze boolean DEFAULT NULL, p_jobmon boolean DEFAULT true) LANGUAGE plpgsql AS $$ DECLARE v_adv_lock boolean; v_row record; v_sql text; v_tables_list_sql text; BEGIN v_adv_lock := pg_try_advisory_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = false THEN RAISE NOTICE 'Partman maintenance already running or another session has not released its advisory lock.'; RETURN; END IF; v_tables_list_sql := 'SELECT parent_table FROM @extschema@.part_config WHERE undo_in_progress = false AND automatic_maintenance = ''on'''; FOR v_row IN EXECUTE v_tables_list_sql LOOP /* * Run maintenance with a commit between each partition set * TODO - Once PG11 is more mainstream, see about more full conversion of run_maintenance function as well as turning * create_partition* functions into procedures to commit after every child table is made. May need to wait * for more PROCEDURE features as well (return values, search_path, etc). * - Also see about swapping names so this is the main object to call for maintenance instead of a function. */ v_sql := format('SELECT %I.run_maintenance(%L, p_jobmon := %L', '@extschema@', v_row.parent_table, p_jobmon); IF p_analyze IS NOT NULL THEN v_sql := v_sql || format(', p_analyze := %L', p_analyze); END IF; v_sql := v_sql || ')'; RAISE DEBUG 'v_sql run_maintenance_proc: %', v_sql; EXECUTE v_sql; COMMIT; PERFORM pg_sleep(p_wait); END LOOP; PERFORM pg_advisory_unlock(hashtext('pg_partman run_maintenance')); END $$; pg_partman-4.6.0/sql/procedures/undo_partition_proc.sql000066400000000000000000000141041412761706100234320ustar00rootroot00000000000000CREATE PROCEDURE @extschema@.undo_partition_proc( p_parent_table text , p_interval text DEFAULT NULL , p_batch int DEFAULT NULL , p_wait int DEFAULT 1 , p_target_table text DEFAULT NULL , p_keep_table boolean DEFAULT true , p_lock_wait int DEFAULT 0 , p_lock_wait_tries int DEFAULT 10 , p_quiet boolean DEFAULT false , p_ignored_columns text[] DEFAULT NULL , p_drop_cascade boolean DEFAULT false) LANGUAGE plpgsql AS $$ DECLARE v_adv_lock boolean; v_batch_count int := 0; v_is_autovac_off boolean := false; v_lockwait_count int := 0; v_parent_schema text; v_parent_tablename text; v_partition_type text; v_partitions_undone int; v_partitions_undone_total int := 0; v_row record; v_rows_undone bigint; v_target_schema text; v_target_tablename text; v_sql text; v_total bigint := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition_proc'), hashtext(p_parent_table)); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman undo_partition_proc already running for given parent table: %.', p_parent_table; RETURN; END IF; SELECT partition_type INTO v_partition_type FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: No entry in part_config found for given table: %', p_parent_table; END IF; IF v_partition_type = 'native' AND p_target_table IS NULL THEN RAISE EXCEPTION 'Natively partitioned table sets require setting the p_target_table parameter to undo partitioning.'; END IF; SELECT n.nspname, c.relname INTO v_parent_schema, v_parent_tablename 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_tablename IS NULL THEN RAISE EXCEPTION 'Unable to find given parent table in system catalogs. Ensure it is schema qualified: %', p_parent_table; END IF; IF p_target_table IS NOT NULL THEN SELECT n.nspname, c.relname INTO v_target_schema, v_target_tablename FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = split_part(p_target_table, '.', 1)::name AND c.relname = split_part(p_target_table, '.', 2)::name; IF v_target_tablename IS NULL THEN RAISE EXCEPTION 'Unable to find given target table in system catalogs. Ensure it is schema qualified: %', p_target_table; END IF; END IF; /* -- Currently no way to catch exception and reset autovac settings back to normal. Until I can do that, leaving this feature out for now -- Leaving the functions to turn off/reset in to let people do that manually if desired IF p_autovacuum_on = false THEN -- Add this parameter back to definition when this is working -- Turn off autovac for parent, source table if set, and all child tables v_is_autovac_off := @extschema@.autovacuum_off(v_parent_schema, v_parent_tablename, v_source_schema, v_source_tablename); COMMIT; END IF; */ v_sql := format('SELECT partitions_undone, rows_undone FROM %I.undo_partition (%L, p_keep_table := %L, p_lock_wait := %L' , '@extschema@' , p_parent_table , p_keep_table , p_lock_wait); IF p_interval IS NOT NULL THEN v_sql := v_sql || format(', p_batch_interval := %L', p_interval); END IF; IF p_target_table IS NOT NULL THEN v_sql := v_sql || format(', p_target_table := %L', p_target_table); END IF; IF p_ignored_columns IS NOT NULL THEN v_sql := v_sql || format(', p_ignored_columns := %L', p_ignored_columns); END IF; IF p_drop_cascade IS NOT NULL THEN v_sql := v_sql || format(', p_drop_cascade := %L', p_drop_cascade); END IF; v_sql := v_sql || ')'; RAISE DEBUG 'partition_data sql: %', v_sql; LOOP EXECUTE v_sql INTO v_partitions_undone, v_rows_undone; -- If lock wait timeout, do not increment the counter IF v_rows_undone != -1 THEN v_batch_count := v_batch_count + 1; v_partitions_undone_total := v_partitions_undone_total + v_partitions_undone; v_total := v_total + v_rows_undone; v_lockwait_count := 0; ELSE v_lockwait_count := v_lockwait_count + 1; IF v_lockwait_count > p_lock_wait_tries THEN RAISE EXCEPTION 'Quitting due to inability to get lock on next batch of rows to be moved'; END IF; END IF; IF p_quiet = false THEN IF v_rows_undone > 0 THEN RAISE NOTICE 'Batch: %, Partitions undone this batch: %, Rows undone this batch: %', v_batch_count, v_partitions_undone, v_rows_undone; ELSIF v_rows_undone = -1 THEN RAISE NOTICE 'Unable to obtain row locks for data to be moved. Trying again...'; END IF; END IF; COMMIT; -- If no rows left or given batch argument limit is reached IF v_rows_undone = 0 OR (p_batch > 0 AND v_batch_count >= p_batch) THEN EXIT; END IF; -- undo_partition functions will remove config entry once last child is dropped -- Added here to handle edge-case SELECT partition_type INTO v_partition_type FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN EXIT; END IF; PERFORM pg_sleep(p_wait); RAISE DEBUG 'v_partitions_undone: %, v_rows_undone: %, v_batch_count: %, v_total: %, v_lockwait_count: %, p_wait: %', v_partitions_undone, p_wait, v_rows_undone, v_batch_count, v_total, v_lockwait_count; END LOOP; /* IF v_is_autovac_off = true THEN -- Reset autovac back to default if it was turned off by this procedure PERFORM @extschema@.autovacuum_reset(v_parent_schema, v_parent_tablename, v_source_schema, v_source_tablename); COMMIT; END IF; */ IF p_quiet = false THEN RAISE NOTICE 'Total partitions undone: %, Total rows moved: %', v_partitions_undone_total, v_total; END IF; RAISE NOTICE 'Ensure to VACUUM ANALYZE the old parent & target table after undo has finished'; END $$; pg_partman-4.6.0/sql/tables/000077500000000000000000000000001412761706100157275ustar00rootroot00000000000000pg_partman-4.6.0/sql/tables/tables.sql000066400000000000000000000171471412761706100177340ustar00rootroot00000000000000CREATE TABLE @extschema@.part_config ( parent_table text NOT NULL , control text NOT NULL , partition_type text NOT NULL , partition_interval text NOT NULL , constraint_cols text[] , premake int NOT NULL DEFAULT 4 , optimize_trigger int NOT NULL DEFAULT 4 , optimize_constraint int NOT NULL DEFAULT 30 , epoch text NOT NULL DEFAULT 'none' , inherit_fk boolean NOT NULL DEFAULT true , retention text , retention_schema text , retention_keep_table boolean NOT NULL DEFAULT true , retention_keep_index boolean NOT NULL DEFAULT true , infinite_time_partitions boolean NOT NULL DEFAULT false , datetime_string text , automatic_maintenance text NOT NULL DEFAULT 'on' , jobmon boolean NOT NULL DEFAULT true , sub_partition_set_full boolean NOT NULL DEFAULT false , undo_in_progress boolean NOT NULL DEFAULT false , trigger_exception_handling BOOLEAN DEFAULT false , upsert text NOT NULL DEFAULT '' , trigger_return_null boolean NOT NULL DEFAULT true , template_table text , publications text[] , inherit_privileges boolean DEFAULT false , constraint_valid boolean DEFAULT true NOT NULL , subscription_refresh text , drop_cascade_fk BOOLEAN NOT NULL DEFAULT false , CONSTRAINT part_config_parent_table_pkey PRIMARY KEY (parent_table) , CONSTRAINT positive_premake_check CHECK (premake > 0) , CONSTRAINT publications_no_empty_set_chk CHECK (publications <> '{}') ); CREATE INDEX part_config_type_idx ON @extschema@.part_config (partition_type); SELECT pg_catalog.pg_extension_config_dump('part_config', ''); -- FK set deferrable because create_parent() & create_sub_parent() inserts to this table before part_config CREATE TABLE @extschema@.part_config_sub ( sub_parent text , sub_partition_type text NOT NULL , sub_control text NOT NULL , sub_partition_interval text NOT NULL , sub_constraint_cols text[] , sub_premake int NOT NULL DEFAULT 4 , sub_optimize_trigger int NOT NULL DEFAULT 4 , sub_optimize_constraint int NOT NULL DEFAULT 30 , sub_epoch text NOT NULL DEFAULT 'none' , sub_inherit_fk boolean NOT NULL DEFAULT true , sub_retention text , sub_retention_schema text , sub_retention_keep_table boolean NOT NULL DEFAULT true , sub_retention_keep_index boolean NOT NULL DEFAULT true , sub_infinite_time_partitions boolean NOT NULL DEFAULT false , sub_automatic_maintenance text NOT NULL DEFAULT 'on' , sub_jobmon boolean NOT NULL DEFAULT true , sub_trigger_exception_handling BOOLEAN DEFAULT false , sub_upsert TEXT NOT NULL DEFAULT '' , sub_trigger_return_null boolean NOT NULL DEFAULT true , sub_template_table text , sub_inherit_privileges boolean DEFAULT false , sub_constraint_valid boolean DEFAULT true NOT NULL , sub_subscription_refresh text , sub_date_trunc_interval TEXT , CONSTRAINT part_config_sub_pkey PRIMARY KEY (sub_parent) , CONSTRAINT part_config_sub_sub_parent_fkey FOREIGN KEY (sub_parent) REFERENCES @extschema@.part_config (parent_table) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED , CONSTRAINT positive_premake_check CHECK (sub_premake > 0) ); SELECT pg_catalog.pg_extension_config_dump('part_config_sub', ''); -- Ensure the control column cannot be one of the additional constraint columns. ALTER TABLE @extschema@.part_config ADD CONSTRAINT control_constraint_col_chk CHECK ((constraint_cols @> ARRAY[control]) <> true); ALTER TABLE @extschema@.part_config_sub ADD CONSTRAINT control_constraint_col_chk CHECK ((sub_constraint_cols @> ARRAY[sub_control]) <> true); ALTER TABLE @extschema@.part_config ADD CONSTRAINT retention_schema_not_empty_chk CHECK (retention_schema <> ''); ALTER TABLE @extschema@.part_config_sub ADD CONSTRAINT retention_schema_not_empty_chk CHECK (sub_retention_schema <> ''); CREATE TABLE @extschema@.custom_time_partitions ( parent_table text NOT NULL , child_table text NOT NULL , partition_range tstzrange NOT NULL , PRIMARY KEY (parent_table, child_table)); CREATE INDEX custom_time_partitions_partition_range_idx ON @extschema@.custom_time_partitions USING gist (partition_range); SELECT pg_catalog.pg_extension_config_dump('custom_time_partitions', ''); /* * Custom view to help improve privilege lookups for pg_partman. * information_schema is a performance bottleneck since indexes aren't being used properly. */ CREATE VIEW @extschema@.table_privs AS SELECT u_grantor.rolname AS grantor, grantee.rolname AS grantee, nc.nspname AS table_schema, c.relname AS table_name, c.prtype AS privilege_type FROM ( SELECT oid, relname, relnamespace, relkind, relowner, (aclexplode(coalesce(relacl, acldefault('r', relowner)))).* FROM pg_class ) AS c (oid, relname, relnamespace, relkind, relowner, grantor, grantee, prtype, grantable), pg_namespace nc, pg_roles u_grantor, ( SELECT oid, rolname FROM pg_roles UNION ALL SELECT 0::oid, 'PUBLIC' ) AS grantee (oid, rolname) WHERE c.relnamespace = nc.oid AND c.relkind IN ('r', 'v', 'p') AND c.grantee = grantee.oid AND c.grantor = u_grantor.oid AND c.prtype IN ('INSERT', 'SELECT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER') AND (pg_has_role(u_grantor.oid, 'USAGE') OR pg_has_role(grantee.oid, 'USAGE') OR grantee.rolname = 'PUBLIC' ); -- Put constraint functions & definitions here because having them in a separate file makes the ordering of their creation harder to control. Some require the above tables to exist first. /* * Check for valid config values for automatic maintenance * (not boolean to allow future values) */ CREATE FUNCTION @extschema@.check_automatic_maintenance_value (p_automatic_maintenance text) RETURNS boolean LANGUAGE plpgsql IMMUTABLE AS $$ DECLARE v_result boolean; BEGIN SELECT p_automatic_maintenance IN ('on', 'off') INTO v_result; RETURN v_result; END $$; ALTER TABLE @extschema@.part_config ADD CONSTRAINT part_config_automatic_maintenance_check CHECK (@extschema@.check_automatic_maintenance_value(automatic_maintenance)); ALTER TABLE @extschema@.part_config_sub ADD CONSTRAINT part_config_sub_automatic_maintenance_check CHECK (@extschema@.check_automatic_maintenance_value(sub_automatic_maintenance)); /* * Check function for config table epoch types */ 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; BEGIN SELECT p_type IN ('none', 'seconds', 'milliseconds') INTO v_result; RETURN v_result; END $$; ALTER TABLE @extschema@.part_config ADD CONSTRAINT part_config_epoch_check CHECK (@extschema@.check_epoch_type(epoch)); ALTER TABLE @extschema@.part_config_sub ADD CONSTRAINT part_config_sub_epoch_check CHECK (@extschema@.check_epoch_type(sub_epoch)); /* * Check for valid config table partition types */ 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 $$; ALTER TABLE @extschema@.part_config ADD CONSTRAINT part_config_type_check CHECK (@extschema@.check_partition_type(partition_type)); ALTER TABLE @extschema@.part_config_sub ADD CONSTRAINT part_config_sub_type_check CHECK (@extschema@.check_partition_type(sub_partition_type)); pg_partman-4.6.0/sql/types/000077500000000000000000000000001412761706100156215ustar00rootroot00000000000000pg_partman-4.6.0/sql/types/types.sql000066400000000000000000000001231412761706100175020ustar00rootroot00000000000000CREATE TYPE @extschema@.check_default_table AS (default_table text, count bigint); pg_partman-4.6.0/src/000077500000000000000000000000001412761706100144455ustar00rootroot00000000000000pg_partman-4.6.0/src/pg_partman_bgw.c000066400000000000000000000464471412761706100176170ustar00rootroot00000000000000/* * pg_partman_bgw.c * * A background worker process for the pg_partman extension to allow * partition maintenance to be scheduled and run within the database * itself without required a third-party scheduler (ex. cron) * */ #include "postgres.h" /* These are always necessary for a bgworker */ #include "miscadmin.h" #include "postmaster/bgworker.h" #include "storage/ipc.h" #include "storage/latch.h" #include "storage/lwlock.h" #include "storage/proc.h" #include "storage/shmem.h" /* these headers are used by this particular worker's code */ #include "access/xact.h" #include "executor/spi.h" #include "fmgr.h" #include "lib/stringinfo.h" #include "pgstat.h" #include "utils/builtins.h" #include "utils/snapmgr.h" #include "tcop/utility.h" #include "commands/async.h" #if (PG_VERSION_NUM >= 100000) #include "utils/varlena.h" #endif PG_MODULE_MAGIC; void _PG_init(void); void pg_partman_bgw_main(Datum); void pg_partman_bgw_run_maint(Datum); /* flags set by signal handlers */ static volatile sig_atomic_t got_sighup = false; static volatile sig_atomic_t got_sigterm = false; /* GUC variables */ static int pg_partman_bgw_interval = 3600; // Default hourly static char *pg_partman_bgw_role = "postgres"; // Default to postgres role // Do not analyze by default on PG11+ #if (PG_VERSION_NUM >= 110000) static char *pg_partman_bgw_analyze = "off"; #else static char *pg_partman_bgw_analyze = "on"; #endif static char *pg_partman_bgw_jobmon = "on"; static char *pg_partman_bgw_dbname = NULL; #if (PG_VERSION_NUM < 100500) static bool (*split_function_ptr)(char *, char, List **) = &SplitIdentifierString; #else static bool (*split_function_ptr)(char *, char, List **) = &SplitGUCList; #endif /* * Signal handler for SIGTERM * Set a flag to let the main loop to terminate, and set our latch to wake * it up. */ static void pg_partman_bgw_sigterm(SIGNAL_ARGS) { int save_errno = errno; got_sigterm = true; if (MyProc) SetLatch(&MyProc->procLatch); errno = save_errno; } /* * Signal handler for SIGHUP * Set a flag to tell the main loop to reread the config file, and set * our latch to wake it up. */ static void pg_partman_bgw_sighup(SIGNAL_ARGS) { int save_errno = errno; got_sighup = true; if (MyProc) SetLatch(&MyProc->procLatch); errno = save_errno; } /* * Entrypoint of this module. */ void _PG_init(void) { BackgroundWorker worker; DefineCustomIntVariable("pg_partman_bgw.interval", "How often run_maintenance() is called (in seconds).", NULL, &pg_partman_bgw_interval, 3600, 1, INT_MAX, PGC_SIGHUP, 0, NULL, NULL, NULL); #if (PG_VERSION_NUM >= 110000) DefineCustomStringVariable("pg_partman_bgw.analyze", "Whether to run an analyze on a partition set whenever a new partition is created during run_maintenance(). Set to 'on' to send TRUE (default). Set to 'off' to send FALSE.", NULL, &pg_partman_bgw_analyze, "off", PGC_SIGHUP, 0, NULL, NULL, NULL); #else DefineCustomStringVariable("pg_partman_bgw.analyze", "Whether to run an analyze on a partition set whenever a new partition is created during run_maintenance(). Set to 'on' to send TRUE (default). Set to 'off' to send FALSE.", NULL, &pg_partman_bgw_analyze, "on", PGC_SIGHUP, 0, NULL, NULL, NULL); #endif DefineCustomStringVariable("pg_partman_bgw.dbname", "CSV list of specific databases in the cluster to run pg_partman BGW on.", NULL, &pg_partman_bgw_dbname, NULL, PGC_SIGHUP, 0, NULL, NULL, NULL); DefineCustomStringVariable("pg_partman_bgw.jobmon", "Whether to log run_maintenance() calls to pg_jobmon if it is installed. Set to 'on' to send TRUE (default). Set to 'off' to send FALSE.", NULL, &pg_partman_bgw_jobmon, "on", PGC_SIGHUP, 0, NULL, NULL, NULL); DefineCustomStringVariable("pg_partman_bgw.role", "Role to be used by BGW. Must have execute permissions on run_maintenance()", NULL, &pg_partman_bgw_role, "postgres", PGC_SIGHUP, 0, NULL, NULL, NULL); /* Kept as comment for reference for future development DefineCustomStringVariable("pg_partman_bgw.maintenance_db", "The BGW requires connecting to a local database for reading system catalogs. By default it uses template1. You can change that with this setting if needed.", NULL, &pg_partman_bgw_maint_db, "template1", PGC_SIGHUP, 0, NULL, NULL, NULL); */ if (!process_shared_preload_libraries_in_progress) return; // Start BGW when database starts sprintf(worker.bgw_name, "pg_partman master background worker"); worker.bgw_flags = BGWORKER_SHMEM_ACCESS | BGWORKER_BACKEND_DATABASE_CONNECTION; worker.bgw_start_time = BgWorkerStart_RecoveryFinished; worker.bgw_restart_time = 600; #if (PG_VERSION_NUM < 100000) worker.bgw_main = pg_partman_bgw_main; #endif #if (PG_VERSION_NUM >= 100000) sprintf(worker.bgw_library_name, "pg_partman_bgw"); sprintf(worker.bgw_function_name, "pg_partman_bgw_main"); #endif worker.bgw_main_arg = CStringGetDatum(pg_partman_bgw_dbname); worker.bgw_notify_pid = 0; RegisterBackgroundWorker(&worker); } void pg_partman_bgw_main(Datum main_arg) { StringInfoData buf; /* Establish signal handlers before unblocking signals. */ pqsignal(SIGHUP, pg_partman_bgw_sighup); pqsignal(SIGTERM, pg_partman_bgw_sigterm); /* We're now ready to receive signals */ BackgroundWorkerUnblockSignals(); /* Keep for when master requires persistent connection elog(LOG, "%s master process initialized with role %s on database %s" , MyBgworkerEntry->bgw_name , pg_partman_bgw_role , pg_partman_bgw_dbname); */ elog(LOG, "%s master process initialized with role %s" , MyBgworkerEntry->bgw_name , pg_partman_bgw_role); initStringInfo(&buf); /* * Main loop: do this until the SIGTERM handler tells us to terminate */ while (!got_sigterm) { BackgroundWorker worker; BackgroundWorkerHandle *handle; BgwHandleStatus status; char *rawstring; int dbcounter; int rc; int full_string_length; List *elemlist; ListCell *l; pid_t pid; /* Using Latch loop method suggested in latch.h * Uses timeout flag in WaitLatch() further below instead of sleep to allow clean shutdown */ ResetLatch(&MyProc->procLatch); CHECK_FOR_INTERRUPTS(); /* In case of a SIGHUP, just reload the configuration. */ if (got_sighup) { got_sighup = false; ProcessConfigFile(PGC_SIGHUP); } elog(DEBUG1, "After sighup check (got_sighup: %d)", got_sighup); /* In case of a SIGTERM in middle of loop, stop all further processing and return from BGW function to allow it to exit cleanly. */ if (got_sigterm) { elog(LOG, "pg_partman master BGW received SIGTERM. Shutting down. (got_sigterm: %d)", got_sigterm); return; } // Use method of shared_preload_libraries to split the pg_partman_bgw_dbname string found in src/backend/utils/init/miscinit.c // Need a modifiable copy of string if (pg_partman_bgw_dbname != NULL) { rawstring = pstrdup(pg_partman_bgw_dbname); // Parse string into list of identifiers if (!(*split_function_ptr)(rawstring, ',', &elemlist)) { // syntax error in list pfree(rawstring); list_free(elemlist); ereport(LOG, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("invalid list syntax in parameter \"pg_partman_bgw.dbname\" in postgresql.conf"))); return; } dbcounter = 0; foreach(l, elemlist) { char *dbname = (char *) lfirst(l); elog(DEBUG1, "Dynamic bgw launch begun for %s (%d)", dbname, dbcounter); worker.bgw_flags = BGWORKER_SHMEM_ACCESS | BGWORKER_BACKEND_DATABASE_CONNECTION; worker.bgw_start_time = BgWorkerStart_RecoveryFinished; worker.bgw_restart_time = BGW_NEVER_RESTART; #if (PG_VERSION_NUM < 100000) worker.bgw_main = NULL; #endif sprintf(worker.bgw_library_name, "pg_partman_bgw"); sprintf(worker.bgw_function_name, "pg_partman_bgw_run_maint"); full_string_length = snprintf(worker.bgw_name, sizeof(worker.bgw_name), "pg_partman dynamic background worker (dbname=%s)", dbname); if (full_string_length >= sizeof(worker.bgw_name)) { /* dbname was truncated, add an ellipsis to denote it */ const char truncated_mark[] = "...)"; memcpy(worker.bgw_name + sizeof(worker.bgw_name) - sizeof(truncated_mark), truncated_mark, sizeof(truncated_mark)); } worker.bgw_main_arg = Int32GetDatum(dbcounter); worker.bgw_notify_pid = MyProcPid; dbcounter++; elog(DEBUG1, "Registering dynamic background worker..."); if (!RegisterDynamicBackgroundWorker(&worker, &handle)) { elog(ERROR, "Unable to register dynamic background worker for pg_partman. Consider increasing max_worker_processes if you see this frequently. Main background worker process will try restarting in 10 minutes."); } elog(DEBUG1, "Waiting for BGW startup..."); status = WaitForBackgroundWorkerStartup(handle, &pid); elog(DEBUG1, "BGW startup status: %d", status); if (status == BGWH_STOPPED) { ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_RESOURCES), errmsg("Could not start dynamic pg_partman background process"), errhint("More details may be available in the server log."))); } if (status == BGWH_POSTMASTER_DIED) { ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_RESOURCES), errmsg("Cannot start dynamic pg_partman background processes without postmaster"), errhint("Kill all remaining database processes and restart the database."))); } Assert(status == BGWH_STARTED); #if (PG_VERSION_NUM >= 90500) // Shutdown wait function introduced in 9.5. The latch problems this wait fixes are only encountered in // 9.6 and later. So this shouldn't be a problem for 9.4. elog(DEBUG1, "Waiting for BGW shutdown..."); status = WaitForBackgroundWorkerShutdown(handle); elog(DEBUG1, "BGW shutdown status: %d", status); Assert(status == BGWH_STOPPED); #endif } pfree(rawstring); list_free(elemlist); } else { // pg_partman_bgw_dbname if null elog(DEBUG1, "pg_partman_bgw.dbname GUC is NULL. Nothing to do in main loop."); } elog(DEBUG1, "Latch status just before waitlatch call: %d", MyProc->procLatch.is_set); #if (PG_VERSION_NUM >= 100000) rc = WaitLatch(&MyProc->procLatch, WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH, pg_partman_bgw_interval * 1000L, PG_WAIT_EXTENSION); #endif #if (PG_VERSION_NUM < 100000) rc = WaitLatch(&MyProc->procLatch, WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH, pg_partman_bgw_interval * 1000L); #endif /* emergency bailout if postmaster has died */ if (rc & WL_POSTMASTER_DEATH) { proc_exit(1); } elog(DEBUG1, "Latch status after waitlatch call: %d", MyProc->procLatch.is_set); } // end sigterm while } // end main /* * Unable to pass the database name as a string argument (not sure why yet) * Instead, the GUC is parsed both in the main function and below and a counter integer * is passed to determine which database the BGW will run in. */ void pg_partman_bgw_run_maint(Datum arg) { char *analyze; char *dbname = "template1"; char *jobmon; char *partman_schema; char *rawstring; int db_main_counter = DatumGetInt32(arg); List *elemlist; int ret; StringInfoData buf; /* Establish signal handlers before unblocking signals. */ pqsignal(SIGHUP, pg_partman_bgw_sighup); pqsignal(SIGTERM, pg_partman_bgw_sigterm); /* We're now ready to receive signals */ BackgroundWorkerUnblockSignals(); elog(DEBUG1, "Before parsing dbname GUC in dynamic main func: %s", pg_partman_bgw_dbname); rawstring = pstrdup(pg_partman_bgw_dbname); elog(DEBUG1, "GUC rawstring copy: %s", rawstring); // Parse string into list of identifiers if (!(*split_function_ptr)(rawstring, ',', &elemlist)) { // syntax error in list pfree(rawstring); list_free(elemlist); ereport(LOG, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("invalid list syntax in parameter \"pg_partman_bgw.dbname\" in postgresql.conf"))); return; } dbname = list_nth(elemlist, db_main_counter); elog(DEBUG1, "Parsing dbname list: %s (%d)", dbname, db_main_counter); if (strcmp(dbname, "template1") == 0) { elog(DEBUG1, "Default database name found in dbname local variable (\"template1\")."); } elog(DEBUG1, "Before run_maint initialize connection for db %s", dbname); #if (PG_VERSION_NUM < 110000) BackgroundWorkerInitializeConnection(dbname, pg_partman_bgw_role); #endif #if (PG_VERSION_NUM >= 110000) BackgroundWorkerInitializeConnection(dbname, pg_partman_bgw_role, 0); #endif elog(DEBUG1, "After run_maint initialize connection for db %s", dbname); initStringInfo(&buf); SetCurrentStatementStartTimestamp(); StartTransactionCommand(); SPI_connect(); PushActiveSnapshot(GetTransactionSnapshot()); pgstat_report_appname("pg_partman dynamic background worker"); // First determine if pg_partman is even installed in this database appendStringInfo(&buf, "SELECT extname FROM pg_catalog.pg_extension WHERE extname = 'pg_partman'"); pgstat_report_activity(STATE_RUNNING, buf.data); elog(DEBUG1, "Checking if pg_partman extension is installed in database: %s" , dbname); ret = SPI_execute(buf.data, true, 1); if (ret != SPI_OK_SELECT) { elog(FATAL, "Cannot determine if pg_partman is installed in database %s: error code %d", dbname, ret); } if (SPI_processed <= 0) { elog(DEBUG1, "pg_partman not installed in database %s. Nothing to do so dynamic worker exiting gracefully.", dbname); // Nothing left to do. Return end the run of BGW function. SPI_finish(); PopActiveSnapshot(); CommitTransactionCommand(); pgstat_report_activity(STATE_IDLE, NULL); pfree(rawstring); list_free(elemlist); return; } // If so then actually log that it's started for that database. elog(LOG, "%s dynamic background worker initialized with role %s on database %s" , MyBgworkerEntry->bgw_name , pg_partman_bgw_role , dbname); resetStringInfo(&buf); appendStringInfo(&buf, "SELECT n.nspname FROM pg_catalog.pg_extension e JOIN pg_catalog.pg_namespace n ON e.extnamespace = n.oid WHERE extname = 'pg_partman'"); pgstat_report_activity(STATE_RUNNING, buf.data); ret = SPI_execute(buf.data, true, 1); if (ret != SPI_OK_SELECT) { elog(FATAL, "Cannot determine which schema pg_partman has been installed to: error code %d", ret); } if (SPI_processed > 0) { bool isnull; partman_schema = DatumGetCString(SPI_getbinval(SPI_tuptable->vals[0] , SPI_tuptable->tupdesc , 1 , &isnull)); if (isnull) elog(FATAL, "Query to determine pg_partman schema returned NULL."); } else { elog(FATAL, "Query to determine pg_partman schema returned zero rows."); } resetStringInfo(&buf); if (strcmp(pg_partman_bgw_analyze, "on") == 0) { analyze = "true"; } else { analyze = "false"; } if (strcmp(pg_partman_bgw_jobmon, "on") == 0) { jobmon = "true"; } else { jobmon = "false"; } appendStringInfo(&buf, "SELECT \"%s\".run_maintenance(p_analyze := %s, p_jobmon := %s)", partman_schema, analyze, jobmon); pgstat_report_activity(STATE_RUNNING, buf.data); ret = SPI_execute(buf.data, false, 0); if (ret != SPI_OK_SELECT) elog(FATAL, "Cannot call pg_partman run_maintenance() function: error code %d", ret); elog(LOG, "%s: %s called by role %s on database %s" , MyBgworkerEntry->bgw_name , buf.data , pg_partman_bgw_role , dbname); SPI_finish(); PopActiveSnapshot(); CommitTransactionCommand(); ProcessCompletedNotifies(); pgstat_report_activity(STATE_IDLE, NULL); elog(DEBUG1, "pg_partman dynamic BGW shutting down gracefully for database %s.", dbname); pfree(rawstring); list_free(elemlist); return; } pg_partman-4.6.0/test/000077500000000000000000000000001412761706100146355ustar00rootroot00000000000000pg_partman-4.6.0/test/README_test.md000066400000000000000000000063751412761706100171660ustar00rootroot00000000000000pg_partman Test Suite ===================== The pgTAP testing suite is used to provide an extensive and easily maintainable set of unit tests. Please see the pgTAP home page for more details on its installation and use. http://pgTAP.org/ A minimum version of pgtap 0.99.1 is required for pg_partman's test suite. Tests assume that the required extensions have been installed in the following schemas: pg_partman: partman pgTAP: public If you've installed any of the above extensions in a different schema and would like to run the test suite, simply change the configuration option found at the top of each testing file to match your setup. If you've also installed pg_jobmon, be aware that the logging of the tests cannot be rolled back and any failures will be picked up by the monitoring in the jobmon extension. SELECT set_config('search_path','partman, public',false); Once that's done, it's best to use the **pg_prove** script that pgTAP comes with to run all the tests. I like using the -o -f -v options to get more useful feedback. pg_prove -ovf /path/to/partman/test/*.sql For most tests, they must be run by a superuser since roles & schemas are created & dropped as part of the test. There is a separate test script for each of the partitioning types. The tests are not required to run pg_partman, so if you don't feel safe doing this you don't need to run the tests. But if you are running into problems and report any issues without a clear explanation of what is wrong, I will ask that you run the test suite so you can try and narrow down where the problem may be. You are free to look through to tests to see exactly what they're doing. All tests in the top level of the test folder are run inside a transaction that is rolled back, so they should not change anything (except jobmon logging as mentioned). Tests for the time-custom partition type are in their own folder. The 30second test frequently errors out if run in certain 30 second blocks. Waiting for the next 30 second block and running it again should allow it to pass. Same goes for the 30 second test in the native folder. Tests for the reindexing script can be found in the test/test_reindex folder. These tests cannot just be run all at once and are not run within rolled back transactions. They must be run in order, one at a time, and there are explicit instructions at the end of each test for what to do next. Tests for the Background Worker can be found in the test/test_bgw folder. Please read the header comments at the top of each test for the postgresql.conf settings required for that test. Tests for procedures are in their own folders. Like the reindexing tests, these must be run in stages with manual commands between them since pgtap cannot handle distinct commits within a single test run. As of version 4.4.0, SECURITY DEFINER is no longer in use. The following tests can be used for non-native partitioning to ensure this is working by passing the -U option to pg_prove with a role that has the necessary privileges: pg_prove -ovf -U partman -d mydatabase test/test-time-nonsuperuser.sql pg_prove -ovf -U partman -d mydatabase test/test-id-nonsuperuser.sql For native partitioning, use the test/test_native/test_nonsuperuser tests and read the instructions contained in them pg_partman-4.6.0/test/test-id-id-id-subpart.sql000066400000000000000000020045731412761706100214040ustar00rootroot00000000000000-- ########## ID PARENT / ID SUBPARENT / ID SUB-SUB-PARENT ########## -- May have to increase max_locks_per_transaction above the default 64 for this test to run properly -- Additional tests: Cluster index \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(3096); CREATE SCHEMA partman_test; CREATE TABLE partman_test.id_taptest_table (col1 int primary key, col2 text default 'stuff', col3 timestamptz NOT NULL DEFAULT now()); ALTER TABLE partman_test.id_taptest_table CLUSTER ON id_taptest_table_pkey; INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(1,100000)); -- larger insert for testing partition/undo scripts --INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(100001,2000000)); SELECT create_parent('partman_test.id_taptest_table', 'col1', 'partman', '10000', p_jobmon := false, p_premake := 2); SELECT has_table('partman_test', 'id_taptest_table_p100000', 'Check id_taptest_table_p100000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p110000', 'Check id_taptest_table_p110000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p120000', 'Check id_taptest_table_p120000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p90000', 'Check id_taptest_table_p90000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p80000', 'Check id_taptest_table_p80000 exists'); SELECT results_eq('SELECT i.indisclustered::text FROM pg_class c JOIN pg_index i ON c.oid = i.indexrelid WHERE c.relname = ''id_taptest_table_p100000_pkey''', ARRAY['true'], 'Check that id_taptest_table_p100000_pkey is clustered'); SELECT results_eq('SELECT i.indisclustered::text FROM pg_class c JOIN pg_index i ON c.oid = i.indexrelid WHERE c.relname = ''id_taptest_table_p110000_pkey''', ARRAY['true'], 'Check that id_taptest_table_p110000_pkey is clustered'); SELECT results_eq('SELECT i.indisclustered::text FROM pg_class c JOIN pg_index i ON c.oid = i.indexrelid WHERE c.relname = ''id_taptest_table_p120000_pkey''', ARRAY['true'], 'Check that id_taptest_table_p120000_pkey is clustered'); SELECT results_eq('SELECT i.indisclustered::text FROM pg_class c JOIN pg_index i ON c.oid = i.indexrelid WHERE c.relname = ''id_taptest_table_p90000_pkey''', ARRAY['true'], 'Check that id_taptest_table_p90000_pkey is clustered'); SELECT results_eq('SELECT i.indisclustered::text FROM pg_class c JOIN pg_index i ON c.oid = i.indexrelid WHERE c.relname = ''id_taptest_table_p80000_pkey''', ARRAY['true'], 'Check that id_taptest_table_p80000_pkey is clustered'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table'', p_batch_count := 20)::int', ARRAY[100000], 'Check that partitioning function returns correct count of rows moved'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[100000], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0', ARRAY[9999], 'Check count from id_taptest_table_p0'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000', ARRAY[10000], 'Check count from id_taptest_table_p10000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000', ARRAY[10000], 'Check count from id_taptest_table_p20000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000', ARRAY[10000], 'Check count from id_taptest_table_p30000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000', ARRAY[10000], 'Check count from id_taptest_table_p40000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000', ARRAY[10000], 'Check count from id_taptest_table_p50000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000', ARRAY[10000], 'Check count from id_taptest_table_p60000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000', ARRAY[10000], 'Check count from id_taptest_table_p70000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000', ARRAY[10000], 'Check count from id_taptest_table_p80000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000', ARRAY[10000], 'Check count from id_taptest_table_p90000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000', ARRAY[1], 'Check count from id_taptest_table_p100000'); SELECT create_sub_parent('partman_test.id_taptest_table', 'col1', 'partman', '1000', p_jobmon := false, p_premake := 2); SELECT results_eq('SELECT sub_parent FROM part_config_sub ORDER BY sub_parent', ARRAY['partman_test.id_taptest_table'], 'Check that part_config_sub has all tables configured as needed'); -- Test for normal partitions that should be made based on current max value of 100,000 SELECT has_table('partman_test', 'id_taptest_table_p90000_p98000', 'Check id_taptest_table_p90000_p98000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p90000_p99000', 'Check id_taptest_table_p90000_p99000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p100000_p100000', 'Check id_taptest_table_p100000_p100000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p100000_p101000', 'Check id_taptest_table_p100000_p101000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p100000_p102000', 'Check id_taptest_table_p100000_p102000 exists'); -- This should be enough cluster testing. If it worked above and here, should work for remaining subpartitioning SELECT results_eq('SELECT i.indisclustered::text FROM pg_class c JOIN pg_index i ON c.oid = i.indexrelid WHERE c.relname = ''id_taptest_table_p90000_p98000_pkey''', ARRAY['true'], 'Check that id_taptest_table_p90000_p98000_pkey is clustered'); SELECT results_eq('SELECT i.indisclustered::text FROM pg_class c JOIN pg_index i ON c.oid = i.indexrelid WHERE c.relname = ''id_taptest_table_p90000_p99000_pkey''', ARRAY['true'], 'Check that id_taptest_table_p90000_p99000_pkey is clustered'); SELECT results_eq('SELECT i.indisclustered::text FROM pg_class c JOIN pg_index i ON c.oid = i.indexrelid WHERE c.relname = ''id_taptest_table_p100000_p100000_pkey''', ARRAY['true'], 'Check that id_taptest_table_p100000_p100000_pkey is clustered'); SELECT results_eq('SELECT i.indisclustered::text FROM pg_class c JOIN pg_index i ON c.oid = i.indexrelid WHERE c.relname = ''id_taptest_table_p100000_p101000_pkey''', ARRAY['true'], 'Check that id_taptest_table_p100000_p101000_pkey is clustered'); SELECT results_eq('SELECT i.indisclustered::text FROM pg_class c JOIN pg_index i ON c.oid = i.indexrelid WHERE c.relname = ''id_taptest_table_p100000_p102000_pkey''', ARRAY['true'], 'Check that id_taptest_table_p100000_p102000_pkey is clustered'); -- Tests that ensure minimal partition was made in all sets SELECT has_table('partman_test', 'id_taptest_table_p0_p0', 'Check id_taptest_table_p0_p0 exists'); SELECT has_table('partman_test', 'id_taptest_table_p10000_p10000', 'Check id_taptest_table_p10000_p10000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p20000_p20000', 'Check id_taptest_table_p20000_p20000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p30000_p30000', 'Check id_taptest_table_p30000_p30000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p40000_p40000', 'Check id_taptest_table_p40000_p40000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p50000_p50000', 'Check id_taptest_table_p50000_p50000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p60000_p60000', 'Check id_taptest_table_p60000_p60000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p70000_p70000', 'Check id_taptest_table_p70000_p70000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p80000_p80000', 'Check id_taptest_table_p80000_p80000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p110000_p110000', 'Check id_taptest_table_p110000_p110000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p120000_p120000', 'Check id_taptest_table_p120000_p120000 exists'); -- Partition all data again SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p0'', p_batch_count := 20)::int', ARRAY[9999], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p0'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p10000'', p_batch_count := 20)::int', ARRAY[10000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p10000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p20000'', p_batch_count := 20)::int', ARRAY[10000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p20000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p30000'', p_batch_count := 20)::int', ARRAY[10000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p30000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p40000'', p_batch_count := 20)::int', ARRAY[10000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p40000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p50000'', p_batch_count := 20)::int', ARRAY[10000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p50000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p60000'', p_batch_count := 20)::int', ARRAY[10000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p60000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p70000'', p_batch_count := 20)::int', ARRAY[10000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p70000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p80000'', p_batch_count := 20)::int', ARRAY[10000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p80000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p90000'', p_batch_count := 20)::int', ARRAY[10000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p90000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p100000'', p_batch_count := 20)::int', ARRAY[1], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p100000'); -- Test that all partitions have their data/exist SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[100000], 'Check count from parent table'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p0', 'Check that parent table id_taptest_table_p0 has had data moved to partition '); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p0', ARRAY[999], 'Check count from id_taptest_table_p0_p0'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p1000', ARRAY[1000], 'Check count from id_taptest_table_p0_p1000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p2000', ARRAY[1000], 'Check count from id_taptest_table_p0_p2000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p3000', ARRAY[1000], 'Check count from id_taptest_table_p0_p3000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p4000', ARRAY[1000], 'Check count from id_taptest_table_p0_p4000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p5000', ARRAY[1000], 'Check count from id_taptest_table_p0_p5000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p6000', ARRAY[1000], 'Check count from id_taptest_table_p0_p6000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p7000', ARRAY[1000], 'Check count from id_taptest_table_p0_p7000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p8000', ARRAY[1000], 'Check count from id_taptest_table_p0_p8000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p9000', ARRAY[1000], 'Check count from id_taptest_table_p0_p9000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10000', 'Check that parent table id_taptest_table_p10000 has had data moved to partition '); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p10000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p10000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p11000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p11000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p12000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p12000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p13000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p13000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p14000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p14000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p15000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p15000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p16000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p16000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p17000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p17000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p18000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p18000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p19000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p19000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p20000', 'Check that parent table id_taptest_table_p20000 has had data moved to partition '); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p20000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p20000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p21000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p21000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p22000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p22000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p23000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p23000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p24000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p24000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p25000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p25000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p26000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p26000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p27000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p27000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p28000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p28000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p29000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p29000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p30000', 'Check that parent table id_taptest_table_p30000 has had data moved to partition '); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p30000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p30000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p31000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p31000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p32000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p32000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p33000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p33000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p34000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p34000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p35000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p35000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p36000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p36000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p37000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p37000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p38000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p38000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p39000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p39000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p40000', 'Check that parent table id_taptest_table_p40000 has had data moved to partition '); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p40000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p40000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p41000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p41000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p42000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p42000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p43000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p43000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p44000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p44000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p45000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p45000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p46000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p46000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p47000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p47000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p48000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p48000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p49000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p49000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p50000', 'Check that parent table id_taptest_table_p50000 has had data moved to partition '); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p50000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p50000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p51000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p51000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p52000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p52000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p53000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p53000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p54000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p54000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p55000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p55000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p56000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p56000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p57000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p57000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p58000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p58000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p59000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p59000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p60000', 'Check that parent table id_taptest_table_p60000 has had data moved to partition '); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p60000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p60000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p61000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p61000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p62000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p62000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p63000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p63000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p64000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p64000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p65000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p65000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p66000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p66000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p67000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p67000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p68000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p68000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p69000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p69000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p70000', 'Check that parent table id_taptest_table_p70000 has had data moved to partition '); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p70000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p70000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p71000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p71000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p72000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p72000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p73000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p73000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p74000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p74000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p75000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p75000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p76000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p76000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p77000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p77000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p78000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p78000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p79000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p79000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p80000', 'Check that parent table id_taptest_table_p80000 has had data moved to partition '); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p80000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p80000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p81000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p81000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p82000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p82000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p83000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p83000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p84000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p84000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p85000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p85000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p86000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p86000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p87000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p87000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p88000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p88000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p89000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p89000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p90000', 'Check that parent table id_taptest_table_p90000 has had data moved to partition '); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p90000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p90000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p91000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p91000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p92000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p92000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p93000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p93000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p94000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p94000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p95000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p95000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p96000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p96000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p97000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p97000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p98000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p98000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p99000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p99000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p100000', 'Check that parent table id_taptest_table_p100000 has had data moved to partition '); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p100000', ARRAY[1], 'Check count from id_taptest_table_p100000_p100000'); -- Create next level of subpartitions SELECT create_sub_parent('partman_test.id_taptest_table_p0', 'col1', 'partman', '100', p_jobmon := false, p_premake := 2); SELECT create_sub_parent('partman_test.id_taptest_table_p10000', 'col1', 'partman', '100', p_jobmon := false, p_premake := 2); SELECT create_sub_parent('partman_test.id_taptest_table_p20000', 'col1', 'partman', '100', p_jobmon := false, p_premake := 2); SELECT create_sub_parent('partman_test.id_taptest_table_p30000', 'col1', 'partman', '100', p_jobmon := false, p_premake := 2); SELECT create_sub_parent('partman_test.id_taptest_table_p40000', 'col1', 'partman', '100', p_jobmon := false, p_premake := 2); SELECT create_sub_parent('partman_test.id_taptest_table_p50000', 'col1', 'partman', '100', p_jobmon := false, p_premake := 2); SELECT create_sub_parent('partman_test.id_taptest_table_p60000', 'col1', 'partman', '100', p_jobmon := false, p_premake := 2); SELECT create_sub_parent('partman_test.id_taptest_table_p70000', 'col1', 'partman', '100', p_jobmon := false, p_premake := 2); SELECT create_sub_parent('partman_test.id_taptest_table_p80000', 'col1', 'partman', '100', p_jobmon := false, p_premake := 2); SELECT create_sub_parent('partman_test.id_taptest_table_p90000', 'col1', 'partman', '100', p_jobmon := false, p_premake := 2); SELECT create_sub_parent('partman_test.id_taptest_table_p100000', 'col1', 'partman', '100', p_jobmon := false, p_premake := 2); SELECT create_sub_parent('partman_test.id_taptest_table_p110000', 'col1', 'partman', '100', p_jobmon := false, p_premake := 2); SELECT create_sub_parent('partman_test.id_taptest_table_p120000', 'col1', 'partman', '100', p_jobmon := false, p_premake := 2); SELECT results_eq('SELECT sub_parent FROM part_config_sub ORDER BY sub_parent', ARRAY['partman_test.id_taptest_table', 'partman_test.id_taptest_table_p0', 'partman_test.id_taptest_table_p10000', 'partman_test.id_taptest_table_p100000', 'partman_test.id_taptest_table_p110000', 'partman_test.id_taptest_table_p120000', 'partman_test.id_taptest_table_p20000', 'partman_test.id_taptest_table_p30000', 'partman_test.id_taptest_table_p40000', 'partman_test.id_taptest_table_p50000', 'partman_test.id_taptest_table_p60000', 'partman_test.id_taptest_table_p70000', 'partman_test.id_taptest_table_p80000', 'partman_test.id_taptest_table_p90000' ], 'Check that part_config_sub has all tables configured as needed'); -- Test for normal partitions that should be made based on current max value of 100,000 SELECT has_table('partman_test', 'id_taptest_table_p90000_p99000_p99800', 'Check id_taptest_table_p90000_p99000_p99800 exists'); SELECT has_table('partman_test', 'id_taptest_table_p90000_p99000_p99900', 'Check id_taptest_table_p90000_p99000_p99800 exists'); SELECT has_table('partman_test', 'id_taptest_table_p100000_p100000_p100000', 'Check id_taptest_table_p100000_p100000_p100000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p100000_p100000_p100100', 'Check id_taptest_table_p100000_p100000_p100100 exists'); SELECT has_table('partman_test', 'id_taptest_table_p100000_p100000_p100200', 'Check id_taptest_table_p100000_p100000_p100200 exists'); -- Tests that ensure minimal partition was made in all sets SELECT has_table('partman_test', 'id_taptest_table_p0_p0_p0', 'Check id_taptest_table_p0_p0_p0 exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p1000_p1000', 'Check id_taptest_table_p0_p1000_p1000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p2000_p2000', 'Check id_taptest_table_p0_p2000_p2000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p3000_p3000', 'Check id_taptest_table_p0_p3000_p3000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p4000_p4000', 'Check id_taptest_table_p0_p4000_p4000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p5000_p5000', 'Check id_taptest_table_p0_p5000_p5000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p6000_p6000', 'Check id_taptest_table_p0_p6000_p6000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p7000_p7000', 'Check id_taptest_table_p0_p7000_p7000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p8000_p8000', 'Check id_taptest_table_p0_p8000_p8000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p9000_p9000', 'Check id_taptest_table_p0_p9000_p1000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p10000_p10000_p10000', 'Check id_taptest_table_p10000_p10000_p10000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p10000_p11000_p11000', 'Check id_taptest_table_p10000_p11000_p11000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p10000_p12000_p12000', 'Check id_taptest_table_p10000_p12000_p12000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p10000_p13000_p13000', 'Check id_taptest_table_p10000_p13000_p13000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p10000_p14000_p14000', 'Check id_taptest_table_p10000_p14000_p14000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p10000_p15000_p15000', 'Check id_taptest_table_p10000_p15000_p15000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p10000_p16000_p16000', 'Check id_taptest_table_p10000_p16000_p16000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p10000_p17000_p17000', 'Check id_taptest_table_p10000_p17000_p17000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p10000_p18000_p18000', 'Check id_taptest_table_p10000_p18000_p18000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p10000_p19000_p19000', 'Check id_taptest_table_p10000_p19000_p19000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p20000_p20000_p20000', 'Check id_taptest_table_p20000_p20000_p20000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p20000_p21000_p21000', 'Check id_taptest_table_p20000_p21000_p21000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p20000_p22000_p22000', 'Check id_taptest_table_p20000_p22000_p22000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p20000_p23000_p23000', 'Check id_taptest_table_p20000_p23000_p23000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p20000_p24000_p24000', 'Check id_taptest_table_p10000_p24000_p24000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p20000_p25000_p25000', 'Check id_taptest_table_p20000_p25000_p25000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p20000_p26000_p26000', 'Check id_taptest_table_p20000_p26000_p26000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p20000_p27000_p27000', 'Check id_taptest_table_p20000_p27000_p27000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p20000_p28000_p28000', 'Check id_taptest_table_p20000_p28000_p28000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p20000_p29000_p29000', 'Check id_taptest_table_p20000_p29000_p29000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p30000_p30000_p30000', 'Check id_taptest_table_p30000_p30000_p30000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p30000_p31000_p31000', 'Check id_taptest_table_p30000_p31000_p31000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p30000_p32000_p32000', 'Check id_taptest_table_p30000_p32000_p32000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p30000_p33000_p33000', 'Check id_taptest_table_p30000_p33000_p33000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p30000_p34000_p34000', 'Check id_taptest_table_p30000_p34000_p34000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p30000_p35000_p35000', 'Check id_taptest_table_p30000_p35000_p35000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p30000_p36000_p36000', 'Check id_taptest_table_p30000_p36000_p36000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p30000_p37000_p37000', 'Check id_taptest_table_p30000_p37000_p37000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p30000_p38000_p38000', 'Check id_taptest_table_p30000_p38000_p38000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p30000_p39000_p39000', 'Check id_taptest_table_p30000_p39000_p39000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p40000_p40000_p40000', 'Check id_taptest_table_p40000_p40000_p40000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p40000_p41000_p41000', 'Check id_taptest_table_p40000_p41000_p41000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p40000_p42000_p42000', 'Check id_taptest_table_p40000_p42000_p42000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p40000_p43000_p43000', 'Check id_taptest_table_p40000_p43000_p43000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p40000_p44000_p44000', 'Check id_taptest_table_p40000_p44000_p44000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p40000_p45000_p45000', 'Check id_taptest_table_p40000_p45000_p45000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p40000_p46000_p46000', 'Check id_taptest_table_p40000_p46000_p46000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p40000_p47000_p47000', 'Check id_taptest_table_p40000_p47000_p47000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p40000_p48000_p48000', 'Check id_taptest_table_p40000_p48000_p48000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p40000_p49000_p49000', 'Check id_taptest_table_p40000_p49000_p49000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p50000_p50000_p50000', 'Check id_taptest_table_p50000_p50000_p50000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p50000_p51000_p51000', 'Check id_taptest_table_p50000_p51000_p51000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p50000_p52000_p52000', 'Check id_taptest_table_p50000_p52000_p52000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p50000_p53000_p53000', 'Check id_taptest_table_p50000_p53000_p53000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p50000_p54000_p54000', 'Check id_taptest_table_p50000_p54000_p54000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p50000_p55000_p55000', 'Check id_taptest_table_p50000_p55000_p55000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p50000_p56000_p56000', 'Check id_taptest_table_p50000_p56000_p56000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p50000_p57000_p57000', 'Check id_taptest_table_p50000_p57000_p57000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p50000_p58000_p58000', 'Check id_taptest_table_p50000_p58000_p58000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p50000_p59000_p59000', 'Check id_taptest_table_p50000_p59000_p59000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p60000_p60000_p60000', 'Check id_taptest_table_p60000_p60000_p60000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p60000_p61000_p61000', 'Check id_taptest_table_p60000_p61000_p61000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p60000_p62000_p62000', 'Check id_taptest_table_p60000_p62000_p62000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p60000_p63000_p63000', 'Check id_taptest_table_p60000_p63000_p63000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p60000_p64000_p64000', 'Check id_taptest_table_p60000_p64000_p64000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p60000_p65000_p65000', 'Check id_taptest_table_p60000_p65000_p65000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p60000_p66000_p66000', 'Check id_taptest_table_p60000_p66000_p66000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p60000_p67000_p67000', 'Check id_taptest_table_p60000_p67000_p67000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p60000_p68000_p68000', 'Check id_taptest_table_p60000_p68000_p68000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p60000_p69000_p69000', 'Check id_taptest_table_p60000_p69000_p69000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p70000_p70000_p70000', 'Check id_taptest_table_p70000_p70000_p70000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p70000_p71000_p71000', 'Check id_taptest_table_p70000_p71000_p71000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p70000_p72000_p72000', 'Check id_taptest_table_p70000_p72000_p72000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p70000_p73000_p73000', 'Check id_taptest_table_p70000_p73000_p73000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p70000_p74000_p74000', 'Check id_taptest_table_p70000_p74000_p74000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p70000_p75000_p75000', 'Check id_taptest_table_p70000_p75000_p75000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p70000_p76000_p76000', 'Check id_taptest_table_p70000_p76000_p76000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p70000_p77000_p77000', 'Check id_taptest_table_p70000_p77000_p77000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p70000_p78000_p78000', 'Check id_taptest_table_p70000_p78000_p78000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p70000_p79000_p79000', 'Check id_taptest_table_p70000_p79000_p79000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p80000_p80000_p80000', 'Check id_taptest_table_p80000_p80000_p80000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p80000_p81000_p81000', 'Check id_taptest_table_p80000_p81000_p80000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p80000_p82000_p82000', 'Check id_taptest_table_p80000_p82000_p80000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p80000_p83000_p83000', 'Check id_taptest_table_p80000_p83000_p80000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p80000_p84000_p84000', 'Check id_taptest_table_p80000_p84000_p80000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p80000_p85000_p85000', 'Check id_taptest_table_p80000_p85000_p80000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p80000_p86000_p86000', 'Check id_taptest_table_p80000_p86000_p80000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p80000_p87000_p87000', 'Check id_taptest_table_p80000_p87000_p80000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p80000_p88000_p88000', 'Check id_taptest_table_p80000_p88000_p80000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p80000_p89000_p89000', 'Check id_taptest_table_p80000_p89000_p80000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p90000_p90000_p90000', 'Check id_taptest_table_p90000_p90000_p90000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p90000_p91000_p91000', 'Check id_taptest_table_p90000_p91000_p91000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p90000_p92000_p92000', 'Check id_taptest_table_p90000_p92000_p92000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p90000_p93000_p93000', 'Check id_taptest_table_p90000_p93000_p93000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p90000_p94000_p94000', 'Check id_taptest_table_p90000_p94000_p94000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p90000_p95000_p95000', 'Check id_taptest_table_p90000_p95000_p95000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p90000_p96000_p96000', 'Check id_taptest_table_p90000_p96000_p96000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p90000_p97000_p97000', 'Check id_taptest_table_p90000_p97000_p97000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p90000_p98000_p98000', 'Check id_taptest_table_p90000_p98000_p98000 exists'); -- All the extra partitions above exist because there is data in that range and it was partitioned out -- Do a hasnt check just for sanity SELECT has_table('partman_test', 'id_taptest_table_p100000_p100000_p100000', 'Check id_taptest_table_p100000_p100000_p100000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p100000_p101000_p101000', 'Check id_taptest_table_p100000_p101000_p101000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p100000_p102000_p102000', 'Check id_taptest_table_p100000_p102000_p102000 exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p100000_p103000_p103000', 'Check id_taptest_table_p100000_p103000_p103000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p110000_p110000_p110000', 'Check id_taptest_table_p110000_p110000_p110000 exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p110000_p111000_p111000', 'Check id_taptest_table_p110000_p111000_p111000 does not exist'); SELECT has_table('partman_test', 'id_taptest_table_p120000_p120000_p120000', 'Check id_taptest_table_p120000_p120000_p120000 exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p120000_p121000_p121000', 'Check id_taptest_table_p120000_p121000_p121000 does not exist'); -- Partition all data yet again SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p0_p0'', p_batch_count := 20)::int', ARRAY[999], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p0_p0'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p0_p1000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p0_p1000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p0_p2000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p0_p2000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p0_p3000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p0_p3000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p0_p4000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p0_p4000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p0_p5000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p0_p5000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p0_p6000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p0_p6000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p0_p7000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p0_p7000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p0_p8000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p0_p8000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p0_p9000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p0_p9000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p10000_p10000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p10000_p10000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p10000_p11000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p10000_p11000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p10000_p12000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p10000_p12000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p10000_p13000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p10000_p13000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p10000_p14000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p10000_p14000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p10000_p15000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p10000_p15000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p10000_p16000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p10000_p16000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p10000_p17000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p10000_p17000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p10000_p18000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p10000_p18000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p10000_p19000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p10000_p19000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p20000_p20000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p20000_p20000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p20000_p21000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p20000_p21000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p20000_p22000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p20000_p22000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p20000_p23000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p20000_p23000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p20000_p24000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p20000_p24000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p20000_p25000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p20000_p25000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p20000_p26000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p20000_p26000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p20000_p27000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p20000_p27000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p20000_p28000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p20000_p28000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p20000_p29000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p20000_p29000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p30000_p30000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p30000_p30000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p30000_p31000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p30000_p31000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p30000_p32000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p30000_p32000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p30000_p33000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p30000_p33000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p30000_p34000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p30000_p34000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p30000_p35000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p30000_p35000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p30000_p36000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p30000_p36000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p30000_p37000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p30000_p37000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p30000_p38000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p30000_p38000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p30000_p39000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p30000_p39000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p40000_p40000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p40000_p40000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p40000_p41000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p40000_p41000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p40000_p42000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p40000_p42000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p40000_p43000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p40000_p43000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p40000_p44000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p40000_p44000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p40000_p45000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p40000_p45000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p40000_p46000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p40000_p46000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p40000_p47000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p40000_p47000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p40000_p48000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p40000_p48000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p40000_p49000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p40000_p49000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p50000_p50000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p50000_p50000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p50000_p51000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p50000_p51000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p50000_p52000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p50000_p52000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p50000_p53000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p50000_p53000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p50000_p54000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p50000_p54000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p50000_p55000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p50000_p55000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p50000_p56000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p50000_p56000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p50000_p57000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p50000_p57000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p50000_p58000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p50000_p58000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p50000_p59000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p50000_p59000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p60000_p60000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p60000_p60000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p60000_p61000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p60000_p61000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p60000_p62000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p60000_p62000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p60000_p63000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p60000_p63000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p60000_p64000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p60000_p64000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p60000_p65000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p60000_p65000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p60000_p66000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p60000_p66000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p60000_p67000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p60000_p67000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p60000_p68000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p60000_p68000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p60000_p69000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p60000_p69000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p70000_p70000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p70000_p70000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p70000_p71000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p70000_p71000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p70000_p72000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p70000_p72000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p70000_p73000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p70000_p73000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p70000_p74000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p70000_p74000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p70000_p75000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p70000_p75000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p70000_p76000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p70000_p76000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p70000_p77000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p70000_p77000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p70000_p78000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p70000_p78000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p70000_p79000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p70000_p79000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p80000_p80000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p80000_p80000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p80000_p81000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p80000_p81000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p80000_p82000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p80000_p82000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p80000_p83000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p80000_p83000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p80000_p84000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p80000_p84000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p80000_p85000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p80000_p85000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p80000_p86000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p80000_p86000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p80000_p87000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p80000_p87000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p80000_p88000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p80000_p88000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p80000_p89000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p80000_p89000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p90000_p90000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p90000_p90000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p90000_p91000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p90000_p91000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p90000_p92000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p90000_p92000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p90000_p93000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p90000_p93000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p90000_p94000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p90000_p94000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p90000_p95000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p90000_p95000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p90000_p96000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p90000_p96000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p90000_p97000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p90000_p97000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p90000_p98000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p90000_p98000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p90000_p99000'', p_batch_count := 20)::int', ARRAY[1000], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p90000_p99000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p100000_p100000'', p_batch_count := 20)::int', ARRAY[1], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p100000_p100000'); -- Test that all parent tables partitions have no data SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[100000], 'Check count from parent table'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p0', 'Check that parent table id_taptest_table_p0 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10000', 'Check that parent table id_taptest_table_p10000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p20000', 'Check that parent table id_taptest_table_p20000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p30000', 'Check that parent table id_taptest_table_p30000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p40000', 'Check that parent table id_taptest_table_p40000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p50000', 'Check that parent table id_taptest_table_p50000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p60000', 'Check that parent table id_taptest_table_p60000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p70000', 'Check that parent table id_taptest_table_p70000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p80000', 'Check that parent table id_taptest_table_p80000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p90000', 'Check that parent table id_taptest_table_p90000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p100000', 'Check that parent table id_taptest_table_p100000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p0_p0', 'Check that parent table id_taptest_table_p0_p0 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p0_p1000', 'Check that parent table id_taptest_table_p0_p1000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p0_p2000', 'Check that parent table id_taptest_table_p0_p2000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p0_p3000', 'Check that parent table id_taptest_table_p0_p3000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p0_p4000', 'Check that parent table id_taptest_table_p0_p4000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p0_p5000', 'Check that parent table id_taptest_table_p0_p5000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p0_p6000', 'Check that parent table id_taptest_table_p0_p6000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p0_p7000', 'Check that parent table id_taptest_table_p0_p7000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p0_p8000', 'Check that parent table id_taptest_table_p0_p8000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p0_p9000', 'Check that parent table id_taptest_table_p0_p9000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10000_p10000', 'Check that parent table id_taptest_table_p10000_p10000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10000_p11000', 'Check that parent table id_taptest_table_p10000_p11000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10000_p12000', 'Check that parent table id_taptest_table_p10000_p12000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10000_p13000', 'Check that parent table id_taptest_table_p10000_p13000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10000_p14000', 'Check that parent table id_taptest_table_p10000_p14000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10000_p15000', 'Check that parent table id_taptest_table_p10000_p15000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10000_p16000', 'Check that parent table id_taptest_table_p10000_p16000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10000_p17000', 'Check that parent table id_taptest_table_p10000_p17000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10000_p18000', 'Check that parent table id_taptest_table_p10000_p18000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10000_p19000', 'Check that parent table id_taptest_table_p10000_p19000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p20000_p20000', 'Check that parent table id_taptest_table_p20000_p20000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p20000_p22000', 'Check that parent table id_taptest_table_p20000_p22000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p20000_p23000', 'Check that parent table id_taptest_table_p20000_p23000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p20000_p24000', 'Check that parent table id_taptest_table_p20000_p24000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p20000_p25000', 'Check that parent table id_taptest_table_p20000_p25000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p20000_p26000', 'Check that parent table id_taptest_table_p20000_p26000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p20000_p27000', 'Check that parent table id_taptest_table_p20000_p27000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p20000_p28000', 'Check that parent table id_taptest_table_p20000_p28000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p20000_p29000', 'Check that parent table id_taptest_table_p20000_p29000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p30000_p30000', 'Check that parent table id_taptest_table_p30000_p30000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p30000_p31000', 'Check that parent table id_taptest_table_p30000_p31000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p30000_p32000', 'Check that parent table id_taptest_table_p30000_p32000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p30000_p33000', 'Check that parent table id_taptest_table_p30000_p33000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p30000_p34000', 'Check that parent table id_taptest_table_p30000_p34000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p30000_p35000', 'Check that parent table id_taptest_table_p30000_p35000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p30000_p36000', 'Check that parent table id_taptest_table_p30000_p36000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p30000_p37000', 'Check that parent table id_taptest_table_p30000_p37000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p30000_p38000', 'Check that parent table id_taptest_table_p30000_p38000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p30000_p39000', 'Check that parent table id_taptest_table_p30000_p39000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p40000_p40000', 'Check that parent table id_taptest_table_p40000_p40000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p40000_p41000', 'Check that parent table id_taptest_table_p40000_p41000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p40000_p42000', 'Check that parent table id_taptest_table_p40000_p42000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p40000_p43000', 'Check that parent table id_taptest_table_p40000_p43000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p40000_p44000', 'Check that parent table id_taptest_table_p40000_p44000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p40000_p45000', 'Check that parent table id_taptest_table_p40000_p45000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p40000_p46000', 'Check that parent table id_taptest_table_p40000_p46000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p40000_p47000', 'Check that parent table id_taptest_table_p40000_p47000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p40000_p48000', 'Check that parent table id_taptest_table_p40000_p48000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p40000_p49000', 'Check that parent table id_taptest_table_p40000_p49000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p50000_p50000', 'Check that parent table id_taptest_table_p50000_p50000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p50000_p51000', 'Check that parent table id_taptest_table_p50000_p51000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p50000_p52000', 'Check that parent table id_taptest_table_p50000_p52000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p50000_p53000', 'Check that parent table id_taptest_table_p50000_p53000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p50000_p54000', 'Check that parent table id_taptest_table_p50000_p54000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p50000_p55000', 'Check that parent table id_taptest_table_p50000_p55000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p50000_p56000', 'Check that parent table id_taptest_table_p50000_p56000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p50000_p57000', 'Check that parent table id_taptest_table_p50000_p57000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p50000_p58000', 'Check that parent table id_taptest_table_p50000_p58000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p50000_p59000', 'Check that parent table id_taptest_table_p50000_p59000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p60000_p60000', 'Check that parent table id_taptest_table_p60000_p60000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p60000_p61000', 'Check that parent table id_taptest_table_p60000_p61000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p60000_p62000', 'Check that parent table id_taptest_table_p60000_p62000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p60000_p63000', 'Check that parent table id_taptest_table_p60000_p63000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p60000_p64000', 'Check that parent table id_taptest_table_p60000_p64000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p60000_p65000', 'Check that parent table id_taptest_table_p60000_p65000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p60000_p66000', 'Check that parent table id_taptest_table_p60000_p66000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p60000_p67000', 'Check that parent table id_taptest_table_p60000_p67000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p60000_p68000', 'Check that parent table id_taptest_table_p60000_p68000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p60000_p69000', 'Check that parent table id_taptest_table_p60000_p69000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p70000_p70000', 'Check that parent table id_taptest_table_p70000_p70000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p70000_p71000', 'Check that parent table id_taptest_table_p70000_p71000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p70000_p72000', 'Check that parent table id_taptest_table_p70000_p72000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p70000_p73000', 'Check that parent table id_taptest_table_p70000_p73000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p70000_p74000', 'Check that parent table id_taptest_table_p70000_p74000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p70000_p75000', 'Check that parent table id_taptest_table_p70000_p75000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p70000_p76000', 'Check that parent table id_taptest_table_p70000_p76000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p70000_p77000', 'Check that parent table id_taptest_table_p70000_p77000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p70000_p78000', 'Check that parent table id_taptest_table_p70000_p78000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p70000_p79000', 'Check that parent table id_taptest_table_p70000_p79000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p80000_p80000', 'Check that parent table id_taptest_table_p80000_p80000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p80000_p81000', 'Check that parent table id_taptest_table_p80000_p81000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p80000_p82000', 'Check that parent table id_taptest_table_p80000_p82000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p80000_p83000', 'Check that parent table id_taptest_table_p80000_p83000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p80000_p84000', 'Check that parent table id_taptest_table_p80000_p84000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p80000_p85000', 'Check that parent table id_taptest_table_p80000_p85000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p80000_p86000', 'Check that parent table id_taptest_table_p80000_p86000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p80000_p87000', 'Check that parent table id_taptest_table_p80000_p87000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p80000_p88000', 'Check that parent table id_taptest_table_p80000_p88000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p80000_p89000', 'Check that parent table id_taptest_table_p80000_p89000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p90000_p90000', 'Check that parent table id_taptest_table_p90000_p90000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p90000_p91000', 'Check that parent table id_taptest_table_p90000_p91000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p90000_p92000', 'Check that parent table id_taptest_table_p90000_p92000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p90000_p93000', 'Check that parent table id_taptest_table_p90000_p93000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p90000_p94000', 'Check that parent table id_taptest_table_p90000_p94000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p90000_p95000', 'Check that parent table id_taptest_table_p90000_p95000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p90000_p96000', 'Check that parent table id_taptest_table_p90000_p96000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p90000_p97000', 'Check that parent table id_taptest_table_p90000_p97000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p90000_p98000', 'Check that parent table id_taptest_table_p90000_p98000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p90000_p99000', 'Check that parent table id_taptest_table_p90000_p99000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p100000_p100000', 'Check that parent table id_taptest_table_p100000_p100000 has had data moved to partition '); -- Test that all child partitions have their data/exist SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p0_p0', ARRAY[99], 'Check count from id_taptest_table_p0_p0_p0'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p0_p100', ARRAY[100], 'Check count from id_taptest_table_p0_p0_p100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p0_p200', ARRAY[100], 'Check count from id_taptest_table_p0_p0_p200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p0_p300', ARRAY[100], 'Check count from id_taptest_table_p0_p0_p300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p0_p400', ARRAY[100], 'Check count from id_taptest_table_p0_p0_p400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p0_p500', ARRAY[100], 'Check count from id_taptest_table_p0_p0_p500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p0_p600', ARRAY[100], 'Check count from id_taptest_table_p0_p0_p600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p0_p700', ARRAY[100], 'Check count from id_taptest_table_p0_p0_p700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p0_p800', ARRAY[100], 'Check count from id_taptest_table_p0_p0_p800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p0_p900', ARRAY[100], 'Check count from id_taptest_table_p0_p0_p900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p1000_p1000', ARRAY[100], 'Check count from id_taptest_table_p0_p1000_p1000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p1000_p1100', ARRAY[100], 'Check count from id_taptest_table_p0_p1000_p1100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p1000_p1200', ARRAY[100], 'Check count from id_taptest_table_p0_p1000_p1200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p1000_p1300', ARRAY[100], 'Check count from id_taptest_table_p0_p1000_p1300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p1000_p1400', ARRAY[100], 'Check count from id_taptest_table_p0_p1000_p1400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p1000_p1500', ARRAY[100], 'Check count from id_taptest_table_p0_p1000_p1500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p1000_p1600', ARRAY[100], 'Check count from id_taptest_table_p0_p1000_p1600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p1000_p1700', ARRAY[100], 'Check count from id_taptest_table_p0_p1000_p1700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p1000_p1800', ARRAY[100], 'Check count from id_taptest_table_p0_p1000_p1800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p1000_p1900', ARRAY[100], 'Check count from id_taptest_table_p0_p1000_p1900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p2000_p2000', ARRAY[100], 'Check count from id_taptest_table_p0_p2000_p2000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p2000_p2100', ARRAY[100], 'Check count from id_taptest_table_p0_p2000_p2100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p2000_p2200', ARRAY[100], 'Check count from id_taptest_table_p0_p2000_p2200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p2000_p2300', ARRAY[100], 'Check count from id_taptest_table_p0_p2000_p2300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p2000_p2400', ARRAY[100], 'Check count from id_taptest_table_p0_p2000_p2400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p2000_p2500', ARRAY[100], 'Check count from id_taptest_table_p0_p2000_p2500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p2000_p2600', ARRAY[100], 'Check count from id_taptest_table_p0_p2000_p2600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p2000_p2700', ARRAY[100], 'Check count from id_taptest_table_p0_p2000_p2700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p2000_p2800', ARRAY[100], 'Check count from id_taptest_table_p0_p2000_p2800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p2000_p2900', ARRAY[100], 'Check count from id_taptest_table_p0_p2000_p2900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p3000_p3000', ARRAY[100], 'Check count from id_taptest_table_p0_p3000_p3000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p3000_p3100', ARRAY[100], 'Check count from id_taptest_table_p0_p3000_p3100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p3000_p3200', ARRAY[100], 'Check count from id_taptest_table_p0_p3000_p3200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p3000_p3300', ARRAY[100], 'Check count from id_taptest_table_p0_p3000_p3300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p3000_p3400', ARRAY[100], 'Check count from id_taptest_table_p0_p3000_p3400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p3000_p3500', ARRAY[100], 'Check count from id_taptest_table_p0_p3000_p3500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p3000_p3600', ARRAY[100], 'Check count from id_taptest_table_p0_p3000_p3600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p3000_p3700', ARRAY[100], 'Check count from id_taptest_table_p0_p3000_p3700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p3000_p3800', ARRAY[100], 'Check count from id_taptest_table_p0_p3000_p3800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p3000_p3900', ARRAY[100], 'Check count from id_taptest_table_p0_p3000_p3900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p4000_p4000', ARRAY[100], 'Check count from id_taptest_table_p0_p4000_p4000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p4000_p4100', ARRAY[100], 'Check count from id_taptest_table_p0_p4000_p4100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p4000_p4200', ARRAY[100], 'Check count from id_taptest_table_p0_p4000_p4200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p4000_p4300', ARRAY[100], 'Check count from id_taptest_table_p0_p4000_p4300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p4000_p4400', ARRAY[100], 'Check count from id_taptest_table_p0_p4000_p4400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p4000_p4500', ARRAY[100], 'Check count from id_taptest_table_p0_p4000_p4500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p4000_p4600', ARRAY[100], 'Check count from id_taptest_table_p0_p4000_p4600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p4000_p4700', ARRAY[100], 'Check count from id_taptest_table_p0_p4000_p4700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p4000_p4800', ARRAY[100], 'Check count from id_taptest_table_p0_p4000_p4800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p4000_p4900', ARRAY[100], 'Check count from id_taptest_table_p0_p4000_p4900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p5000_p5000', ARRAY[100], 'Check count from id_taptest_table_p0_p5000_p5000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p5000_p5100', ARRAY[100], 'Check count from id_taptest_table_p0_p5000_p5100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p5000_p5200', ARRAY[100], 'Check count from id_taptest_table_p0_p5000_p5200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p5000_p5300', ARRAY[100], 'Check count from id_taptest_table_p0_p5000_p5300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p5000_p5400', ARRAY[100], 'Check count from id_taptest_table_p0_p5000_p5400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p5000_p5500', ARRAY[100], 'Check count from id_taptest_table_p0_p5000_p5500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p5000_p5600', ARRAY[100], 'Check count from id_taptest_table_p0_p5000_p5600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p5000_p5700', ARRAY[100], 'Check count from id_taptest_table_p0_p5000_p5700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p5000_p5800', ARRAY[100], 'Check count from id_taptest_table_p0_p5000_p5800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p5000_p5900', ARRAY[100], 'Check count from id_taptest_table_p0_p5000_p5900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p6000_p6000', ARRAY[100], 'Check count from id_taptest_table_p0_p6000_p6000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p6000_p6100', ARRAY[100], 'Check count from id_taptest_table_p0_p6000_p6100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p6000_p6200', ARRAY[100], 'Check count from id_taptest_table_p0_p6000_p6200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p6000_p6300', ARRAY[100], 'Check count from id_taptest_table_p0_p6000_p6300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p6000_p6400', ARRAY[100], 'Check count from id_taptest_table_p0_p6000_p6400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p6000_p6500', ARRAY[100], 'Check count from id_taptest_table_p0_p6000_p6500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p6000_p6600', ARRAY[100], 'Check count from id_taptest_table_p0_p6000_p6600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p6000_p6700', ARRAY[100], 'Check count from id_taptest_table_p0_p6000_p6700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p6000_p6800', ARRAY[100], 'Check count from id_taptest_table_p0_p6000_p6800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p6000_p6900', ARRAY[100], 'Check count from id_taptest_table_p0_p6000_p6900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p7000_p7000', ARRAY[100], 'Check count from id_taptest_table_p0_p7000_p7000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p7000_p7100', ARRAY[100], 'Check count from id_taptest_table_p0_p7000_p7100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p7000_p7200', ARRAY[100], 'Check count from id_taptest_table_p0_p7000_p7200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p7000_p7300', ARRAY[100], 'Check count from id_taptest_table_p0_p7000_p7300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p7000_p7400', ARRAY[100], 'Check count from id_taptest_table_p0_p7000_p7400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p7000_p7500', ARRAY[100], 'Check count from id_taptest_table_p0_p7000_p7500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p7000_p7600', ARRAY[100], 'Check count from id_taptest_table_p0_p7000_p7600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p7000_p7700', ARRAY[100], 'Check count from id_taptest_table_p0_p7000_p7700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p7000_p7800', ARRAY[100], 'Check count from id_taptest_table_p0_p7000_p7800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p7000_p7900', ARRAY[100], 'Check count from id_taptest_table_p0_p7000_p7900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p8000_p8000', ARRAY[100], 'Check count from id_taptest_table_p0_p8000_p8000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p8000_p8100', ARRAY[100], 'Check count from id_taptest_table_p0_p8000_p8100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p8000_p8200', ARRAY[100], 'Check count from id_taptest_table_p0_p8000_p8200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p8000_p8300', ARRAY[100], 'Check count from id_taptest_table_p0_p8000_p8300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p8000_p8400', ARRAY[100], 'Check count from id_taptest_table_p0_p8000_p8400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p8000_p8500', ARRAY[100], 'Check count from id_taptest_table_p0_p8000_p8500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p8000_p8600', ARRAY[100], 'Check count from id_taptest_table_p0_p8000_p8600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p8000_p8700', ARRAY[100], 'Check count from id_taptest_table_p0_p8000_p8700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p8000_p8800', ARRAY[100], 'Check count from id_taptest_table_p0_p8000_p8800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p8000_p8900', ARRAY[100], 'Check count from id_taptest_table_p0_p8000_p8900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p9000_p9000', ARRAY[100], 'Check count from id_taptest_table_p0_p9000_p9000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p9000_p9100', ARRAY[100], 'Check count from id_taptest_table_p0_p9000_p9100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p9000_p9200', ARRAY[100], 'Check count from id_taptest_table_p0_p9000_p9200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p9000_p9300', ARRAY[100], 'Check count from id_taptest_table_p0_p9000_p9300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p9000_p9400', ARRAY[100], 'Check count from id_taptest_table_p0_p9000_p9400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p9000_p9500', ARRAY[100], 'Check count from id_taptest_table_p0_p9000_p9500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p9000_p9600', ARRAY[100], 'Check count from id_taptest_table_p0_p9000_p9600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p9000_p9700', ARRAY[100], 'Check count from id_taptest_table_p0_p9000_p9700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p9000_p9800', ARRAY[100], 'Check count from id_taptest_table_p0_p9000_p9800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p9000_p9900', ARRAY[100], 'Check count from id_taptest_table_p0_p9000_p9900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p10000_p10000', ARRAY[100], 'Check count from id_taptest_table_p10000_p10000_p10000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p10000_p10100', ARRAY[100], 'Check count from id_taptest_table_p10000_p10000_p10100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p10000_p10200', ARRAY[100], 'Check count from id_taptest_table_p10000_p10000_p10200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p10000_p10300', ARRAY[100], 'Check count from id_taptest_table_p10000_p10000_p10300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p10000_p10400', ARRAY[100], 'Check count from id_taptest_table_p10000_p10000_p10400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p10000_p10500', ARRAY[100], 'Check count from id_taptest_table_p10000_p10000_p10500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p10000_p10600', ARRAY[100], 'Check count from id_taptest_table_p10000_p10000_p10600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p10000_p10700', ARRAY[100], 'Check count from id_taptest_table_p10000_p10000_p10700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p10000_p10800', ARRAY[100], 'Check count from id_taptest_table_p10000_p10000_p10800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p10000_p10900', ARRAY[100], 'Check count from id_taptest_table_p10000_p10000_p10900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p11000_p11000', ARRAY[100], 'Check count from id_taptest_table_p10000_p11000_p11000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p11000_p11100', ARRAY[100], 'Check count from id_taptest_table_p10000_p11000_p11100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p11000_p11200', ARRAY[100], 'Check count from id_taptest_table_p10000_p11000_p11200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p11000_p11300', ARRAY[100], 'Check count from id_taptest_table_p10000_p11000_p11300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p11000_p11400', ARRAY[100], 'Check count from id_taptest_table_p10000_p11000_p11400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p11000_p11500', ARRAY[100], 'Check count from id_taptest_table_p10000_p11000_p11500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p11000_p11600', ARRAY[100], 'Check count from id_taptest_table_p10000_p11000_p11600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p11000_p11700', ARRAY[100], 'Check count from id_taptest_table_p10000_p11000_p11700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p11000_p11800', ARRAY[100], 'Check count from id_taptest_table_p10000_p11000_p11800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p11000_p11900', ARRAY[100], 'Check count from id_taptest_table_p10000_p11000_p11900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p12000_p12000', ARRAY[100], 'Check count from id_taptest_table_p10000_p12000_p12000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p12000_p12100', ARRAY[100], 'Check count from id_taptest_table_p10000_p12000_p12100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p12000_p12200', ARRAY[100], 'Check count from id_taptest_table_p10000_p12000_p12200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p12000_p12300', ARRAY[100], 'Check count from id_taptest_table_p10000_p12000_p12300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p12000_p12400', ARRAY[100], 'Check count from id_taptest_table_p10000_p12000_p12400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p12000_p12500', ARRAY[100], 'Check count from id_taptest_table_p10000_p12000_p12500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p12000_p12600', ARRAY[100], 'Check count from id_taptest_table_p10000_p12000_p12600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p12000_p12700', ARRAY[100], 'Check count from id_taptest_table_p10000_p12000_p12700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p12000_p12800', ARRAY[100], 'Check count from id_taptest_table_p10000_p12000_p12800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p12000_p12900', ARRAY[100], 'Check count from id_taptest_table_p10000_p12000_p12900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p13000_p13000', ARRAY[100], 'Check count from id_taptest_table_p10000_p13000_p13000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p13000_p13100', ARRAY[100], 'Check count from id_taptest_table_p10000_p13000_p13100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p13000_p13200', ARRAY[100], 'Check count from id_taptest_table_p10000_p13000_p13200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p13000_p13300', ARRAY[100], 'Check count from id_taptest_table_p10000_p13000_p13300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p13000_p13400', ARRAY[100], 'Check count from id_taptest_table_p10000_p13000_p13400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p13000_p13500', ARRAY[100], 'Check count from id_taptest_table_p10000_p13000_p13500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p13000_p13600', ARRAY[100], 'Check count from id_taptest_table_p10000_p13000_p13600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p13000_p13700', ARRAY[100], 'Check count from id_taptest_table_p10000_p13000_p13700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p13000_p13800', ARRAY[100], 'Check count from id_taptest_table_p10000_p13000_p13800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p13000_p13900', ARRAY[100], 'Check count from id_taptest_table_p10000_p13000_p13900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p14000_p14000', ARRAY[100], 'Check count from id_taptest_table_p10000_p14000_p14000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p14000_p14100', ARRAY[100], 'Check count from id_taptest_table_p10000_p14000_p14100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p14000_p14200', ARRAY[100], 'Check count from id_taptest_table_p10000_p14000_p14200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p14000_p14300', ARRAY[100], 'Check count from id_taptest_table_p10000_p14000_p14300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p14000_p14400', ARRAY[100], 'Check count from id_taptest_table_p10000_p14000_p14400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p14000_p14500', ARRAY[100], 'Check count from id_taptest_table_p10000_p14000_p14500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p14000_p14600', ARRAY[100], 'Check count from id_taptest_table_p10000_p14000_p14600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p14000_p14700', ARRAY[100], 'Check count from id_taptest_table_p10000_p14000_p14700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p14000_p14800', ARRAY[100], 'Check count from id_taptest_table_p10000_p14000_p14800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p14000_p14900', ARRAY[100], 'Check count from id_taptest_table_p10000_p14000_p14900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p15000_p15000', ARRAY[100], 'Check count from id_taptest_table_p10000_p15000_p15000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p15000_p15100', ARRAY[100], 'Check count from id_taptest_table_p10000_p15000_p15100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p15000_p15200', ARRAY[100], 'Check count from id_taptest_table_p10000_p15000_p15200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p15000_p15300', ARRAY[100], 'Check count from id_taptest_table_p10000_p15000_p15300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p15000_p15400', ARRAY[100], 'Check count from id_taptest_table_p10000_p15000_p15400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p15000_p15500', ARRAY[100], 'Check count from id_taptest_table_p10000_p15000_p15500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p15000_p15600', ARRAY[100], 'Check count from id_taptest_table_p10000_p15000_p15600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p15000_p15700', ARRAY[100], 'Check count from id_taptest_table_p10000_p15000_p15700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p15000_p15800', ARRAY[100], 'Check count from id_taptest_table_p10000_p15000_p15800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p15000_p15900', ARRAY[100], 'Check count from id_taptest_table_p10000_p15000_p15900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p16000_p16000', ARRAY[100], 'Check count from id_taptest_table_p10000_p16000_p16000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p16000_p16100', ARRAY[100], 'Check count from id_taptest_table_p10000_p16000_p16100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p16000_p16200', ARRAY[100], 'Check count from id_taptest_table_p10000_p16000_p16200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p16000_p16300', ARRAY[100], 'Check count from id_taptest_table_p10000_p16000_p16300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p16000_p16400', ARRAY[100], 'Check count from id_taptest_table_p10000_p16000_p16400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p16000_p16500', ARRAY[100], 'Check count from id_taptest_table_p10000_p16000_p16500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p16000_p16600', ARRAY[100], 'Check count from id_taptest_table_p10000_p16000_p16600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p16000_p16700', ARRAY[100], 'Check count from id_taptest_table_p10000_p16000_p16700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p16000_p16800', ARRAY[100], 'Check count from id_taptest_table_p10000_p16000_p16800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p16000_p16900', ARRAY[100], 'Check count from id_taptest_table_p10000_p16000_p16900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p17000_p17000', ARRAY[100], 'Check count from id_taptest_table_p10000_p17000_p17000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p17000_p17100', ARRAY[100], 'Check count from id_taptest_table_p10000_p17000_p17100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p17000_p17200', ARRAY[100], 'Check count from id_taptest_table_p10000_p17000_p17200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p17000_p17300', ARRAY[100], 'Check count from id_taptest_table_p10000_p17000_p17300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p17000_p17400', ARRAY[100], 'Check count from id_taptest_table_p10000_p17000_p17400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p17000_p17500', ARRAY[100], 'Check count from id_taptest_table_p10000_p17000_p17500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p17000_p17600', ARRAY[100], 'Check count from id_taptest_table_p10000_p17000_p17600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p17000_p17700', ARRAY[100], 'Check count from id_taptest_table_p10000_p17000_p17700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p17000_p17800', ARRAY[100], 'Check count from id_taptest_table_p10000_p17000_p17800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p17000_p17900', ARRAY[100], 'Check count from id_taptest_table_p10000_p17000_p17900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p18000_p18000', ARRAY[100], 'Check count from id_taptest_table_p10000_p18000_p18000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p18000_p18100', ARRAY[100], 'Check count from id_taptest_table_p10000_p18000_p18100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p18000_p18200', ARRAY[100], 'Check count from id_taptest_table_p10000_p18000_p18200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p18000_p18300', ARRAY[100], 'Check count from id_taptest_table_p10000_p18000_p18300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p18000_p18400', ARRAY[100], 'Check count from id_taptest_table_p10000_p18000_p18400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p18000_p18500', ARRAY[100], 'Check count from id_taptest_table_p10000_p18000_p18500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p18000_p18600', ARRAY[100], 'Check count from id_taptest_table_p10000_p18000_p18600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p18000_p18700', ARRAY[100], 'Check count from id_taptest_table_p10000_p18000_p18700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p18000_p18800', ARRAY[100], 'Check count from id_taptest_table_p10000_p18000_p18800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p18000_p18900', ARRAY[100], 'Check count from id_taptest_table_p10000_p18000_p18900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p19000_p19000', ARRAY[100], 'Check count from id_taptest_table_p10000_p19000_p19000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p19000_p19100', ARRAY[100], 'Check count from id_taptest_table_p10000_p19000_p19100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p19000_p19200', ARRAY[100], 'Check count from id_taptest_table_p10000_p19000_p19200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p19000_p19300', ARRAY[100], 'Check count from id_taptest_table_p10000_p19000_p19300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p19000_p19400', ARRAY[100], 'Check count from id_taptest_table_p10000_p19000_p19400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p19000_p19500', ARRAY[100], 'Check count from id_taptest_table_p10000_p19000_p19500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p19000_p19600', ARRAY[100], 'Check count from id_taptest_table_p10000_p19000_p19600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p19000_p19700', ARRAY[100], 'Check count from id_taptest_table_p10000_p19000_p19700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p19000_p19800', ARRAY[100], 'Check count from id_taptest_table_p10000_p19000_p19800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p19000_p19900', ARRAY[100], 'Check count from id_taptest_table_p10000_p19000_p19900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p20000_p20000', ARRAY[100], 'Check count from id_taptest_table_p20000_p20000_p20000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p20000_p20100', ARRAY[100], 'Check count from id_taptest_table_p20000_p20000_p20100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p20000_p20200', ARRAY[100], 'Check count from id_taptest_table_p20000_p20000_p20200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p20000_p20300', ARRAY[100], 'Check count from id_taptest_table_p20000_p20000_p20300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p20000_p20400', ARRAY[100], 'Check count from id_taptest_table_p20000_p20000_p20400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p20000_p20500', ARRAY[100], 'Check count from id_taptest_table_p20000_p20000_p20500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p20000_p20600', ARRAY[100], 'Check count from id_taptest_table_p20000_p20000_p20600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p20000_p20700', ARRAY[100], 'Check count from id_taptest_table_p20000_p20000_p20700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p20000_p20800', ARRAY[100], 'Check count from id_taptest_table_p20000_p20000_p20800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p20000_p20900', ARRAY[100], 'Check count from id_taptest_table_p20000_p20000_p20900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p21000_p21000', ARRAY[100], 'Check count from id_taptest_table_p20000_p21000_p21000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p21000_p21100', ARRAY[100], 'Check count from id_taptest_table_p20000_p21000_p21100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p21000_p21200', ARRAY[100], 'Check count from id_taptest_table_p20000_p21000_p21200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p21000_p21300', ARRAY[100], 'Check count from id_taptest_table_p20000_p21000_p21300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p21000_p21400', ARRAY[100], 'Check count from id_taptest_table_p20000_p21000_p21400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p21000_p21500', ARRAY[100], 'Check count from id_taptest_table_p20000_p21000_p21500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p21000_p21600', ARRAY[100], 'Check count from id_taptest_table_p20000_p21000_p21600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p21000_p21700', ARRAY[100], 'Check count from id_taptest_table_p20000_p21000_p21700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p21000_p21800', ARRAY[100], 'Check count from id_taptest_table_p20000_p21000_p21800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p21000_p21900', ARRAY[100], 'Check count from id_taptest_table_p20000_p21000_p21900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p22000_p22000', ARRAY[100], 'Check count from id_taptest_table_p20000_p22000_p22000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p22000_p22100', ARRAY[100], 'Check count from id_taptest_table_p20000_p22000_p22100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p22000_p22200', ARRAY[100], 'Check count from id_taptest_table_p20000_p22000_p22200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p22000_p22300', ARRAY[100], 'Check count from id_taptest_table_p20000_p22000_p22300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p22000_p22400', ARRAY[100], 'Check count from id_taptest_table_p20000_p22000_p22400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p22000_p22500', ARRAY[100], 'Check count from id_taptest_table_p20000_p22000_p22500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p22000_p22600', ARRAY[100], 'Check count from id_taptest_table_p20000_p22000_p22600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p22000_p22700', ARRAY[100], 'Check count from id_taptest_table_p20000_p22000_p22700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p22000_p22800', ARRAY[100], 'Check count from id_taptest_table_p20000_p22000_p22800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p22000_p22900', ARRAY[100], 'Check count from id_taptest_table_p20000_p22000_p22900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p23000_p23000', ARRAY[100], 'Check count from id_taptest_table_p20000_p23000_p23000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p23000_p23100', ARRAY[100], 'Check count from id_taptest_table_p20000_p23000_p23100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p23000_p23200', ARRAY[100], 'Check count from id_taptest_table_p20000_p23000_p23200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p23000_p23300', ARRAY[100], 'Check count from id_taptest_table_p20000_p23000_p23300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p23000_p23400', ARRAY[100], 'Check count from id_taptest_table_p20000_p23000_p23400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p23000_p23500', ARRAY[100], 'Check count from id_taptest_table_p20000_p23000_p23500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p23000_p23600', ARRAY[100], 'Check count from id_taptest_table_p20000_p23000_p23600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p23000_p23700', ARRAY[100], 'Check count from id_taptest_table_p20000_p23000_p23700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p23000_p23800', ARRAY[100], 'Check count from id_taptest_table_p20000_p23000_p23800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p23000_p23900', ARRAY[100], 'Check count from id_taptest_table_p20000_p23000_p23900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p24000_p24000', ARRAY[100], 'Check count from id_taptest_table_p20000_p24000_p24000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p24000_p24100', ARRAY[100], 'Check count from id_taptest_table_p20000_p24000_p24100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p24000_p24200', ARRAY[100], 'Check count from id_taptest_table_p20000_p24000_p24200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p24000_p24300', ARRAY[100], 'Check count from id_taptest_table_p20000_p24000_p24300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p24000_p24400', ARRAY[100], 'Check count from id_taptest_table_p20000_p24000_p24400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p24000_p24500', ARRAY[100], 'Check count from id_taptest_table_p20000_p24000_p24500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p24000_p24600', ARRAY[100], 'Check count from id_taptest_table_p20000_p24000_p24600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p24000_p24700', ARRAY[100], 'Check count from id_taptest_table_p20000_p24000_p24700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p24000_p24800', ARRAY[100], 'Check count from id_taptest_table_p20000_p24000_p24800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p24000_p24900', ARRAY[100], 'Check count from id_taptest_table_p20000_p24000_p24900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p25000_p25000', ARRAY[100], 'Check count from id_taptest_table_p20000_p25000_p25000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p25000_p25100', ARRAY[100], 'Check count from id_taptest_table_p20000_p25000_p25100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p25000_p25200', ARRAY[100], 'Check count from id_taptest_table_p20000_p25000_p25200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p25000_p25300', ARRAY[100], 'Check count from id_taptest_table_p20000_p25000_p25300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p25000_p25400', ARRAY[100], 'Check count from id_taptest_table_p20000_p25000_p25400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p25000_p25500', ARRAY[100], 'Check count from id_taptest_table_p20000_p25000_p25500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p25000_p25600', ARRAY[100], 'Check count from id_taptest_table_p20000_p25000_p25600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p25000_p25700', ARRAY[100], 'Check count from id_taptest_table_p20000_p25000_p25700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p25000_p25800', ARRAY[100], 'Check count from id_taptest_table_p20000_p25000_p25800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p25000_p25900', ARRAY[100], 'Check count from id_taptest_table_p20000_p25000_p25900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p26000_p26000', ARRAY[100], 'Check count from id_taptest_table_p20000_p26000_p26000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p26000_p26100', ARRAY[100], 'Check count from id_taptest_table_p20000_p26000_p26100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p26000_p26200', ARRAY[100], 'Check count from id_taptest_table_p20000_p26000_p26200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p26000_p26300', ARRAY[100], 'Check count from id_taptest_table_p20000_p26000_p26300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p26000_p26400', ARRAY[100], 'Check count from id_taptest_table_p20000_p26000_p26400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p26000_p26500', ARRAY[100], 'Check count from id_taptest_table_p20000_p26000_p26500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p26000_p26600', ARRAY[100], 'Check count from id_taptest_table_p20000_p26000_p26600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p26000_p26700', ARRAY[100], 'Check count from id_taptest_table_p20000_p26000_p26700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p26000_p26800', ARRAY[100], 'Check count from id_taptest_table_p20000_p26000_p26800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p26000_p26900', ARRAY[100], 'Check count from id_taptest_table_p20000_p26000_p26900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p27000_p27000', ARRAY[100], 'Check count from id_taptest_table_p20000_p27000_p27000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p27000_p27100', ARRAY[100], 'Check count from id_taptest_table_p20000_p27000_p27100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p27000_p27200', ARRAY[100], 'Check count from id_taptest_table_p20000_p27000_p27200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p27000_p27300', ARRAY[100], 'Check count from id_taptest_table_p20000_p27000_p27300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p27000_p27400', ARRAY[100], 'Check count from id_taptest_table_p20000_p27000_p27400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p27000_p27500', ARRAY[100], 'Check count from id_taptest_table_p20000_p27000_p27500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p27000_p27600', ARRAY[100], 'Check count from id_taptest_table_p20000_p27000_p27600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p27000_p27700', ARRAY[100], 'Check count from id_taptest_table_p20000_p27000_p27700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p27000_p27800', ARRAY[100], 'Check count from id_taptest_table_p20000_p27000_p27800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p27000_p27900', ARRAY[100], 'Check count from id_taptest_table_p20000_p27000_p27900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p28000_p28000', ARRAY[100], 'Check count from id_taptest_table_p20000_p28000_p28000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p28000_p28100', ARRAY[100], 'Check count from id_taptest_table_p20000_p28000_p28100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p28000_p28200', ARRAY[100], 'Check count from id_taptest_table_p20000_p28000_p28200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p28000_p28300', ARRAY[100], 'Check count from id_taptest_table_p20000_p28000_p28300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p28000_p28400', ARRAY[100], 'Check count from id_taptest_table_p20000_p28000_p28400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p28000_p28500', ARRAY[100], 'Check count from id_taptest_table_p20000_p28000_p28500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p28000_p28600', ARRAY[100], 'Check count from id_taptest_table_p20000_p28000_p28600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p28000_p28700', ARRAY[100], 'Check count from id_taptest_table_p20000_p28000_p28700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p28000_p28800', ARRAY[100], 'Check count from id_taptest_table_p20000_p28000_p28800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p28000_p28900', ARRAY[100], 'Check count from id_taptest_table_p20000_p28000_p28900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p29000_p29000', ARRAY[100], 'Check count from id_taptest_table_p20000_p29000_p29000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p29000_p29100', ARRAY[100], 'Check count from id_taptest_table_p20000_p29000_p29100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p29000_p29200', ARRAY[100], 'Check count from id_taptest_table_p20000_p29000_p29200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p29000_p29300', ARRAY[100], 'Check count from id_taptest_table_p20000_p29000_p29300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p29000_p29400', ARRAY[100], 'Check count from id_taptest_table_p20000_p29000_p29400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p29000_p29500', ARRAY[100], 'Check count from id_taptest_table_p20000_p29000_p29500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p29000_p29600', ARRAY[100], 'Check count from id_taptest_table_p20000_p29000_p29600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p29000_p29700', ARRAY[100], 'Check count from id_taptest_table_p20000_p29000_p29700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p29000_p29800', ARRAY[100], 'Check count from id_taptest_table_p20000_p29000_p29800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p29000_p29900', ARRAY[100], 'Check count from id_taptest_table_p20000_p29000_p29900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p30000_p30000', ARRAY[100], 'Check count from id_taptest_table_p30000_p30000_p30000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p30000_p30100', ARRAY[100], 'Check count from id_taptest_table_p30000_p30000_p30100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p30000_p30200', ARRAY[100], 'Check count from id_taptest_table_p30000_p30000_p30200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p30000_p30300', ARRAY[100], 'Check count from id_taptest_table_p30000_p30000_p30300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p30000_p30400', ARRAY[100], 'Check count from id_taptest_table_p30000_p30000_p30400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p30000_p30500', ARRAY[100], 'Check count from id_taptest_table_p30000_p30000_p30500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p30000_p30600', ARRAY[100], 'Check count from id_taptest_table_p30000_p30000_p30600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p30000_p30700', ARRAY[100], 'Check count from id_taptest_table_p30000_p30000_p30700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p30000_p30800', ARRAY[100], 'Check count from id_taptest_table_p30000_p30000_p30800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p30000_p30900', ARRAY[100], 'Check count from id_taptest_table_p30000_p30000_p30900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p31000_p31000', ARRAY[100], 'Check count from id_taptest_table_p30000_p31000_p31000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p31000_p31100', ARRAY[100], 'Check count from id_taptest_table_p30000_p31000_p31100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p31000_p31200', ARRAY[100], 'Check count from id_taptest_table_p30000_p31000_p31200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p31000_p31300', ARRAY[100], 'Check count from id_taptest_table_p30000_p31000_p31300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p31000_p31400', ARRAY[100], 'Check count from id_taptest_table_p30000_p31000_p31400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p31000_p31500', ARRAY[100], 'Check count from id_taptest_table_p30000_p31000_p31500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p31000_p31600', ARRAY[100], 'Check count from id_taptest_table_p30000_p31000_p31600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p31000_p31700', ARRAY[100], 'Check count from id_taptest_table_p30000_p31000_p31700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p31000_p31800', ARRAY[100], 'Check count from id_taptest_table_p30000_p31000_p31800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p31000_p31900', ARRAY[100], 'Check count from id_taptest_table_p30000_p31000_p31900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p32000_p32000', ARRAY[100], 'Check count from id_taptest_table_p30000_p32000_p32000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p32000_p32100', ARRAY[100], 'Check count from id_taptest_table_p30000_p32000_p32100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p32000_p32200', ARRAY[100], 'Check count from id_taptest_table_p30000_p32000_p32200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p32000_p32300', ARRAY[100], 'Check count from id_taptest_table_p30000_p32000_p32300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p32000_p32400', ARRAY[100], 'Check count from id_taptest_table_p30000_p32000_p32400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p32000_p32500', ARRAY[100], 'Check count from id_taptest_table_p30000_p32000_p32500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p32000_p32600', ARRAY[100], 'Check count from id_taptest_table_p30000_p32000_p32600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p32000_p32700', ARRAY[100], 'Check count from id_taptest_table_p30000_p32000_p32700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p32000_p32800', ARRAY[100], 'Check count from id_taptest_table_p30000_p32000_p32800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p32000_p32900', ARRAY[100], 'Check count from id_taptest_table_p30000_p32000_p32900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p33000_p33000', ARRAY[100], 'Check count from id_taptest_table_p30000_p33000_p33000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p33000_p33100', ARRAY[100], 'Check count from id_taptest_table_p30000_p33000_p33100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p33000_p33200', ARRAY[100], 'Check count from id_taptest_table_p30000_p33000_p33200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p33000_p33300', ARRAY[100], 'Check count from id_taptest_table_p30000_p33000_p33300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p33000_p33400', ARRAY[100], 'Check count from id_taptest_table_p30000_p33000_p33400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p33000_p33500', ARRAY[100], 'Check count from id_taptest_table_p30000_p33000_p33500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p33000_p33600', ARRAY[100], 'Check count from id_taptest_table_p30000_p33000_p33600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p33000_p33700', ARRAY[100], 'Check count from id_taptest_table_p30000_p33000_p33700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p33000_p33800', ARRAY[100], 'Check count from id_taptest_table_p30000_p33000_p33800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p33000_p33900', ARRAY[100], 'Check count from id_taptest_table_p30000_p33000_p33900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p34000_p34000', ARRAY[100], 'Check count from id_taptest_table_p30000_p34000_p34000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p34000_p34100', ARRAY[100], 'Check count from id_taptest_table_p30000_p34000_p34100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p34000_p34200', ARRAY[100], 'Check count from id_taptest_table_p30000_p34000_p34200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p34000_p34300', ARRAY[100], 'Check count from id_taptest_table_p30000_p34000_p34300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p34000_p34400', ARRAY[100], 'Check count from id_taptest_table_p30000_p34000_p34400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p34000_p34500', ARRAY[100], 'Check count from id_taptest_table_p30000_p34000_p34500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p34000_p34600', ARRAY[100], 'Check count from id_taptest_table_p30000_p34000_p34600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p34000_p34700', ARRAY[100], 'Check count from id_taptest_table_p30000_p34000_p34700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p34000_p34800', ARRAY[100], 'Check count from id_taptest_table_p30000_p34000_p34800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p34000_p34900', ARRAY[100], 'Check count from id_taptest_table_p30000_p34000_p34900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p35000_p35000', ARRAY[100], 'Check count from id_taptest_table_p30000_p35000_p35000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p35000_p35100', ARRAY[100], 'Check count from id_taptest_table_p30000_p35000_p35100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p35000_p35200', ARRAY[100], 'Check count from id_taptest_table_p30000_p35000_p35200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p35000_p35300', ARRAY[100], 'Check count from id_taptest_table_p30000_p35000_p35300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p35000_p35400', ARRAY[100], 'Check count from id_taptest_table_p30000_p35000_p35400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p35000_p35500', ARRAY[100], 'Check count from id_taptest_table_p30000_p35000_p35500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p35000_p35600', ARRAY[100], 'Check count from id_taptest_table_p30000_p35000_p35600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p35000_p35700', ARRAY[100], 'Check count from id_taptest_table_p30000_p35000_p35700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p35000_p35800', ARRAY[100], 'Check count from id_taptest_table_p30000_p35000_p35800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p35000_p35900', ARRAY[100], 'Check count from id_taptest_table_p30000_p35000_p35900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p36000_p36000', ARRAY[100], 'Check count from id_taptest_table_p30000_p36000_p36000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p36000_p36100', ARRAY[100], 'Check count from id_taptest_table_p30000_p36000_p36100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p36000_p36200', ARRAY[100], 'Check count from id_taptest_table_p30000_p36000_p36200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p36000_p36300', ARRAY[100], 'Check count from id_taptest_table_p30000_p36000_p36300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p36000_p36400', ARRAY[100], 'Check count from id_taptest_table_p30000_p36000_p36400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p36000_p36500', ARRAY[100], 'Check count from id_taptest_table_p30000_p36000_p36500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p36000_p36600', ARRAY[100], 'Check count from id_taptest_table_p30000_p36000_p36600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p36000_p36700', ARRAY[100], 'Check count from id_taptest_table_p30000_p36000_p36700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p36000_p36800', ARRAY[100], 'Check count from id_taptest_table_p30000_p36000_p36800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p36000_p36900', ARRAY[100], 'Check count from id_taptest_table_p30000_p36000_p36900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p37000_p37000', ARRAY[100], 'Check count from id_taptest_table_p30000_p37000_p37000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p37000_p37100', ARRAY[100], 'Check count from id_taptest_table_p30000_p37000_p37100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p37000_p37200', ARRAY[100], 'Check count from id_taptest_table_p30000_p37000_p37200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p37000_p37300', ARRAY[100], 'Check count from id_taptest_table_p30000_p37000_p37300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p37000_p37400', ARRAY[100], 'Check count from id_taptest_table_p30000_p37000_p37400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p37000_p37500', ARRAY[100], 'Check count from id_taptest_table_p30000_p37000_p37500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p37000_p37600', ARRAY[100], 'Check count from id_taptest_table_p30000_p37000_p37600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p37000_p37700', ARRAY[100], 'Check count from id_taptest_table_p30000_p37000_p37700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p37000_p37800', ARRAY[100], 'Check count from id_taptest_table_p30000_p37000_p37800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p37000_p37900', ARRAY[100], 'Check count from id_taptest_table_p30000_p37000_p37900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p38000_p38000', ARRAY[100], 'Check count from id_taptest_table_p30000_p38000_p38000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p38000_p38100', ARRAY[100], 'Check count from id_taptest_table_p30000_p38000_p38100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p38000_p38200', ARRAY[100], 'Check count from id_taptest_table_p30000_p38000_p38200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p38000_p38300', ARRAY[100], 'Check count from id_taptest_table_p30000_p38000_p38300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p38000_p38400', ARRAY[100], 'Check count from id_taptest_table_p30000_p38000_p38400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p38000_p38500', ARRAY[100], 'Check count from id_taptest_table_p30000_p38000_p38500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p38000_p38600', ARRAY[100], 'Check count from id_taptest_table_p30000_p38000_p38600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p38000_p38700', ARRAY[100], 'Check count from id_taptest_table_p30000_p38000_p38700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p38000_p38800', ARRAY[100], 'Check count from id_taptest_table_p30000_p38000_p38800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p38000_p38900', ARRAY[100], 'Check count from id_taptest_table_p30000_p38000_p38900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p39000_p39000', ARRAY[100], 'Check count from id_taptest_table_p30000_p39000_p39000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p39000_p39100', ARRAY[100], 'Check count from id_taptest_table_p30000_p39000_p39100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p39000_p39200', ARRAY[100], 'Check count from id_taptest_table_p30000_p39000_p39200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p39000_p39300', ARRAY[100], 'Check count from id_taptest_table_p30000_p39000_p39300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p39000_p39400', ARRAY[100], 'Check count from id_taptest_table_p30000_p39000_p39400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p39000_p39500', ARRAY[100], 'Check count from id_taptest_table_p30000_p39000_p39500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p39000_p39600', ARRAY[100], 'Check count from id_taptest_table_p30000_p39000_p39600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p39000_p39700', ARRAY[100], 'Check count from id_taptest_table_p30000_p39000_p39700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p39000_p39800', ARRAY[100], 'Check count from id_taptest_table_p30000_p39000_p39800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p39000_p39900', ARRAY[100], 'Check count from id_taptest_table_p30000_p39000_p39900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p40000_p40000', ARRAY[100], 'Check count from id_taptest_table_p40000_p40000_p40000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p40000_p40100', ARRAY[100], 'Check count from id_taptest_table_p40000_p40000_p40100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p40000_p40200', ARRAY[100], 'Check count from id_taptest_table_p40000_p40000_p40200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p40000_p40300', ARRAY[100], 'Check count from id_taptest_table_p40000_p40000_p40300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p40000_p40400', ARRAY[100], 'Check count from id_taptest_table_p40000_p40000_p40400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p40000_p40500', ARRAY[100], 'Check count from id_taptest_table_p40000_p40000_p40500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p40000_p40600', ARRAY[100], 'Check count from id_taptest_table_p40000_p40000_p40600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p40000_p40700', ARRAY[100], 'Check count from id_taptest_table_p40000_p40000_p40700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p40000_p40800', ARRAY[100], 'Check count from id_taptest_table_p40000_p40000_p40800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p40000_p40900', ARRAY[100], 'Check count from id_taptest_table_p40000_p40000_p40900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p41000_p41000', ARRAY[100], 'Check count from id_taptest_table_p40000_p41000_p41000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p41000_p41100', ARRAY[100], 'Check count from id_taptest_table_p40000_p41000_p41100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p41000_p41200', ARRAY[100], 'Check count from id_taptest_table_p40000_p41000_p41200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p41000_p41300', ARRAY[100], 'Check count from id_taptest_table_p40000_p41000_p41300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p41000_p41400', ARRAY[100], 'Check count from id_taptest_table_p40000_p41000_p41400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p41000_p41500', ARRAY[100], 'Check count from id_taptest_table_p40000_p41000_p41500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p41000_p41600', ARRAY[100], 'Check count from id_taptest_table_p40000_p41000_p41600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p41000_p41700', ARRAY[100], 'Check count from id_taptest_table_p40000_p41000_p41700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p41000_p41800', ARRAY[100], 'Check count from id_taptest_table_p40000_p41000_p41800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p41000_p41900', ARRAY[100], 'Check count from id_taptest_table_p40000_p41000_p41900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p42000_p42000', ARRAY[100], 'Check count from id_taptest_table_p40000_p42000_p42000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p42000_p42100', ARRAY[100], 'Check count from id_taptest_table_p40000_p42000_p42100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p42000_p42200', ARRAY[100], 'Check count from id_taptest_table_p40000_p42000_p42200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p42000_p42300', ARRAY[100], 'Check count from id_taptest_table_p40000_p42000_p42300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p42000_p42400', ARRAY[100], 'Check count from id_taptest_table_p40000_p42000_p42400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p42000_p42500', ARRAY[100], 'Check count from id_taptest_table_p40000_p42000_p42500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p42000_p42600', ARRAY[100], 'Check count from id_taptest_table_p40000_p42000_p42600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p42000_p42700', ARRAY[100], 'Check count from id_taptest_table_p40000_p42000_p42700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p42000_p42800', ARRAY[100], 'Check count from id_taptest_table_p40000_p42000_p42800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p42000_p42900', ARRAY[100], 'Check count from id_taptest_table_p40000_p42000_p42900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p43000_p43000', ARRAY[100], 'Check count from id_taptest_table_p40000_p43000_p43000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p43000_p43100', ARRAY[100], 'Check count from id_taptest_table_p40000_p43000_p43100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p43000_p43200', ARRAY[100], 'Check count from id_taptest_table_p40000_p43000_p43200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p43000_p43300', ARRAY[100], 'Check count from id_taptest_table_p40000_p43000_p43300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p43000_p43400', ARRAY[100], 'Check count from id_taptest_table_p40000_p43000_p43400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p43000_p43500', ARRAY[100], 'Check count from id_taptest_table_p40000_p43000_p43500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p43000_p43600', ARRAY[100], 'Check count from id_taptest_table_p40000_p43000_p43600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p43000_p43700', ARRAY[100], 'Check count from id_taptest_table_p40000_p43000_p43700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p43000_p43800', ARRAY[100], 'Check count from id_taptest_table_p40000_p43000_p43800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p43000_p43900', ARRAY[100], 'Check count from id_taptest_table_p40000_p43000_p43900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p44000_p44000', ARRAY[100], 'Check count from id_taptest_table_p40000_p44000_p44000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p44000_p44100', ARRAY[100], 'Check count from id_taptest_table_p40000_p44000_p44100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p44000_p44200', ARRAY[100], 'Check count from id_taptest_table_p40000_p44000_p44200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p44000_p44300', ARRAY[100], 'Check count from id_taptest_table_p40000_p44000_p44300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p44000_p44400', ARRAY[100], 'Check count from id_taptest_table_p40000_p44000_p44400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p44000_p44500', ARRAY[100], 'Check count from id_taptest_table_p40000_p44000_p44500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p44000_p44600', ARRAY[100], 'Check count from id_taptest_table_p40000_p44000_p44600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p44000_p44700', ARRAY[100], 'Check count from id_taptest_table_p40000_p44000_p44700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p44000_p44800', ARRAY[100], 'Check count from id_taptest_table_p40000_p44000_p44800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p44000_p44900', ARRAY[100], 'Check count from id_taptest_table_p40000_p44000_p44900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p45000_p45000', ARRAY[100], 'Check count from id_taptest_table_p40000_p45000_p45000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p45000_p45100', ARRAY[100], 'Check count from id_taptest_table_p40000_p45000_p45100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p45000_p45200', ARRAY[100], 'Check count from id_taptest_table_p40000_p45000_p45200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p45000_p45300', ARRAY[100], 'Check count from id_taptest_table_p40000_p45000_p45300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p45000_p45400', ARRAY[100], 'Check count from id_taptest_table_p40000_p45000_p45400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p45000_p45500', ARRAY[100], 'Check count from id_taptest_table_p40000_p45000_p45500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p45000_p45600', ARRAY[100], 'Check count from id_taptest_table_p40000_p45000_p45600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p45000_p45700', ARRAY[100], 'Check count from id_taptest_table_p40000_p45000_p45700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p45000_p45800', ARRAY[100], 'Check count from id_taptest_table_p40000_p45000_p45800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p45000_p45900', ARRAY[100], 'Check count from id_taptest_table_p40000_p45000_p45900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p46000_p46000', ARRAY[100], 'Check count from id_taptest_table_p40000_p46000_p46000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p46000_p46100', ARRAY[100], 'Check count from id_taptest_table_p40000_p46000_p46100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p46000_p46200', ARRAY[100], 'Check count from id_taptest_table_p40000_p46000_p46200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p46000_p46300', ARRAY[100], 'Check count from id_taptest_table_p40000_p46000_p46300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p46000_p46400', ARRAY[100], 'Check count from id_taptest_table_p40000_p46000_p46400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p46000_p46500', ARRAY[100], 'Check count from id_taptest_table_p40000_p46000_p46500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p46000_p46600', ARRAY[100], 'Check count from id_taptest_table_p40000_p46000_p46600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p46000_p46700', ARRAY[100], 'Check count from id_taptest_table_p40000_p46000_p46700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p46000_p46800', ARRAY[100], 'Check count from id_taptest_table_p40000_p46000_p46800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p46000_p46900', ARRAY[100], 'Check count from id_taptest_table_p40000_p46000_p46900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p47000_p47000', ARRAY[100], 'Check count from id_taptest_table_p40000_p47000_p47000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p47000_p47100', ARRAY[100], 'Check count from id_taptest_table_p40000_p47000_p47100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p47000_p47200', ARRAY[100], 'Check count from id_taptest_table_p40000_p47000_p47200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p47000_p47300', ARRAY[100], 'Check count from id_taptest_table_p40000_p47000_p47300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p47000_p47400', ARRAY[100], 'Check count from id_taptest_table_p40000_p47000_p47400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p47000_p47500', ARRAY[100], 'Check count from id_taptest_table_p40000_p47000_p47500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p47000_p47600', ARRAY[100], 'Check count from id_taptest_table_p40000_p47000_p47600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p47000_p47700', ARRAY[100], 'Check count from id_taptest_table_p40000_p47000_p47700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p47000_p47800', ARRAY[100], 'Check count from id_taptest_table_p40000_p47000_p47800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p47000_p47900', ARRAY[100], 'Check count from id_taptest_table_p40000_p47000_p47900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p48000_p48000', ARRAY[100], 'Check count from id_taptest_table_p40000_p48000_p48000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p48000_p48100', ARRAY[100], 'Check count from id_taptest_table_p40000_p48000_p48100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p48000_p48200', ARRAY[100], 'Check count from id_taptest_table_p40000_p48000_p48200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p48000_p48300', ARRAY[100], 'Check count from id_taptest_table_p40000_p48000_p48300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p48000_p48400', ARRAY[100], 'Check count from id_taptest_table_p40000_p48000_p48400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p48000_p48500', ARRAY[100], 'Check count from id_taptest_table_p40000_p48000_p48500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p48000_p48600', ARRAY[100], 'Check count from id_taptest_table_p40000_p48000_p48600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p48000_p48700', ARRAY[100], 'Check count from id_taptest_table_p40000_p48000_p48700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p48000_p48800', ARRAY[100], 'Check count from id_taptest_table_p40000_p48000_p48800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p48000_p48900', ARRAY[100], 'Check count from id_taptest_table_p40000_p48000_p48900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p49000_p49000', ARRAY[100], 'Check count from id_taptest_table_p40000_p49000_p49000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p49000_p49100', ARRAY[100], 'Check count from id_taptest_table_p40000_p49000_p49100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p49000_p49200', ARRAY[100], 'Check count from id_taptest_table_p40000_p49000_p49200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p49000_p49300', ARRAY[100], 'Check count from id_taptest_table_p40000_p49000_p49300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p49000_p49400', ARRAY[100], 'Check count from id_taptest_table_p40000_p49000_p49400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p49000_p49500', ARRAY[100], 'Check count from id_taptest_table_p40000_p49000_p49500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p49000_p49600', ARRAY[100], 'Check count from id_taptest_table_p40000_p49000_p49600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p49000_p49700', ARRAY[100], 'Check count from id_taptest_table_p40000_p49000_p49700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p49000_p49800', ARRAY[100], 'Check count from id_taptest_table_p40000_p49000_p49800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p49000_p49900', ARRAY[100], 'Check count from id_taptest_table_p40000_p49000_p49900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p50000_p50000', ARRAY[100], 'Check count from id_taptest_table_p50000_p50000_p50000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p50000_p50100', ARRAY[100], 'Check count from id_taptest_table_p50000_p50000_p50100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p50000_p50200', ARRAY[100], 'Check count from id_taptest_table_p50000_p50000_p50200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p50000_p50300', ARRAY[100], 'Check count from id_taptest_table_p50000_p50000_p50300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p50000_p50400', ARRAY[100], 'Check count from id_taptest_table_p50000_p50000_p50400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p50000_p50500', ARRAY[100], 'Check count from id_taptest_table_p50000_p50000_p50500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p50000_p50600', ARRAY[100], 'Check count from id_taptest_table_p50000_p50000_p50600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p50000_p50700', ARRAY[100], 'Check count from id_taptest_table_p50000_p50000_p50700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p50000_p50800', ARRAY[100], 'Check count from id_taptest_table_p50000_p50000_p50800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p50000_p50900', ARRAY[100], 'Check count from id_taptest_table_p50000_p50000_p50900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p51000_p51000', ARRAY[100], 'Check count from id_taptest_table_p50000_p51000_p51000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p51000_p51100', ARRAY[100], 'Check count from id_taptest_table_p50000_p51000_p51100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p51000_p51200', ARRAY[100], 'Check count from id_taptest_table_p50000_p51000_p51200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p51000_p51300', ARRAY[100], 'Check count from id_taptest_table_p50000_p51000_p51300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p51000_p51400', ARRAY[100], 'Check count from id_taptest_table_p50000_p51000_p51400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p51000_p51500', ARRAY[100], 'Check count from id_taptest_table_p50000_p51000_p51500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p51000_p51600', ARRAY[100], 'Check count from id_taptest_table_p50000_p51000_p51600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p51000_p51700', ARRAY[100], 'Check count from id_taptest_table_p50000_p51000_p51700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p51000_p51800', ARRAY[100], 'Check count from id_taptest_table_p50000_p51000_p51800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p51000_p51900', ARRAY[100], 'Check count from id_taptest_table_p50000_p51000_p51900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p52000_p52000', ARRAY[100], 'Check count from id_taptest_table_p50000_p52000_p52000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p52000_p52100', ARRAY[100], 'Check count from id_taptest_table_p50000_p52000_p52100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p52000_p52200', ARRAY[100], 'Check count from id_taptest_table_p50000_p52000_p52200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p52000_p52300', ARRAY[100], 'Check count from id_taptest_table_p50000_p52000_p52300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p52000_p52400', ARRAY[100], 'Check count from id_taptest_table_p50000_p52000_p52400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p52000_p52500', ARRAY[100], 'Check count from id_taptest_table_p50000_p52000_p52500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p52000_p52600', ARRAY[100], 'Check count from id_taptest_table_p50000_p52000_p52600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p52000_p52700', ARRAY[100], 'Check count from id_taptest_table_p50000_p52000_p52700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p52000_p52800', ARRAY[100], 'Check count from id_taptest_table_p50000_p52000_p52800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p52000_p52900', ARRAY[100], 'Check count from id_taptest_table_p50000_p52000_p52900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p53000_p53000', ARRAY[100], 'Check count from id_taptest_table_p50000_p53000_p53000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p53000_p53100', ARRAY[100], 'Check count from id_taptest_table_p50000_p53000_p53100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p53000_p53200', ARRAY[100], 'Check count from id_taptest_table_p50000_p53000_p53200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p53000_p53300', ARRAY[100], 'Check count from id_taptest_table_p50000_p53000_p53300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p53000_p53400', ARRAY[100], 'Check count from id_taptest_table_p50000_p53000_p53400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p53000_p53500', ARRAY[100], 'Check count from id_taptest_table_p50000_p53000_p53500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p53000_p53600', ARRAY[100], 'Check count from id_taptest_table_p50000_p53000_p53600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p53000_p53700', ARRAY[100], 'Check count from id_taptest_table_p50000_p53000_p53700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p53000_p53800', ARRAY[100], 'Check count from id_taptest_table_p50000_p53000_p53800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p53000_p53900', ARRAY[100], 'Check count from id_taptest_table_p50000_p53000_p53900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p54000_p54000', ARRAY[100], 'Check count from id_taptest_table_p50000_p54000_p54000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p54000_p54100', ARRAY[100], 'Check count from id_taptest_table_p50000_p54000_p54100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p54000_p54200', ARRAY[100], 'Check count from id_taptest_table_p50000_p54000_p54200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p54000_p54300', ARRAY[100], 'Check count from id_taptest_table_p50000_p54000_p54300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p54000_p54400', ARRAY[100], 'Check count from id_taptest_table_p50000_p54000_p54400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p54000_p54500', ARRAY[100], 'Check count from id_taptest_table_p50000_p54000_p54500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p54000_p54600', ARRAY[100], 'Check count from id_taptest_table_p50000_p54000_p54600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p54000_p54700', ARRAY[100], 'Check count from id_taptest_table_p50000_p54000_p54700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p54000_p54800', ARRAY[100], 'Check count from id_taptest_table_p50000_p54000_p54800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p54000_p54900', ARRAY[100], 'Check count from id_taptest_table_p50000_p54000_p54900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p55000_p55000', ARRAY[100], 'Check count from id_taptest_table_p50000_p55000_p55000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p55000_p55100', ARRAY[100], 'Check count from id_taptest_table_p50000_p55000_p55100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p55000_p55200', ARRAY[100], 'Check count from id_taptest_table_p50000_p55000_p55200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p55000_p55300', ARRAY[100], 'Check count from id_taptest_table_p50000_p55000_p55300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p55000_p55400', ARRAY[100], 'Check count from id_taptest_table_p50000_p55000_p55400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p55000_p55500', ARRAY[100], 'Check count from id_taptest_table_p50000_p55000_p55500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p55000_p55600', ARRAY[100], 'Check count from id_taptest_table_p50000_p55000_p55600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p55000_p55700', ARRAY[100], 'Check count from id_taptest_table_p50000_p55000_p55700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p55000_p55800', ARRAY[100], 'Check count from id_taptest_table_p50000_p55000_p55800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p55000_p55900', ARRAY[100], 'Check count from id_taptest_table_p50000_p55000_p55900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p56000_p56000', ARRAY[100], 'Check count from id_taptest_table_p50000_p56000_p56000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p56000_p56100', ARRAY[100], 'Check count from id_taptest_table_p50000_p56000_p56100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p56000_p56200', ARRAY[100], 'Check count from id_taptest_table_p50000_p56000_p56200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p56000_p56300', ARRAY[100], 'Check count from id_taptest_table_p50000_p56000_p56300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p56000_p56400', ARRAY[100], 'Check count from id_taptest_table_p50000_p56000_p56400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p56000_p56500', ARRAY[100], 'Check count from id_taptest_table_p50000_p56000_p56500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p56000_p56600', ARRAY[100], 'Check count from id_taptest_table_p50000_p56000_p56600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p56000_p56700', ARRAY[100], 'Check count from id_taptest_table_p50000_p56000_p56700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p56000_p56800', ARRAY[100], 'Check count from id_taptest_table_p50000_p56000_p56800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p56000_p56900', ARRAY[100], 'Check count from id_taptest_table_p50000_p56000_p56900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p57000_p57000', ARRAY[100], 'Check count from id_taptest_table_p50000_p57000_p57000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p57000_p57100', ARRAY[100], 'Check count from id_taptest_table_p50000_p57000_p57100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p57000_p57200', ARRAY[100], 'Check count from id_taptest_table_p50000_p57000_p57200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p57000_p57300', ARRAY[100], 'Check count from id_taptest_table_p50000_p57000_p57300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p57000_p57400', ARRAY[100], 'Check count from id_taptest_table_p50000_p57000_p57400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p57000_p57500', ARRAY[100], 'Check count from id_taptest_table_p50000_p57000_p57500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p57000_p57600', ARRAY[100], 'Check count from id_taptest_table_p50000_p57000_p57600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p57000_p57700', ARRAY[100], 'Check count from id_taptest_table_p50000_p57000_p57700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p57000_p57800', ARRAY[100], 'Check count from id_taptest_table_p50000_p57000_p57800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p57000_p57900', ARRAY[100], 'Check count from id_taptest_table_p50000_p57000_p57900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p58000_p58000', ARRAY[100], 'Check count from id_taptest_table_p50000_p58000_p58000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p58000_p58100', ARRAY[100], 'Check count from id_taptest_table_p50000_p58000_p58100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p58000_p58200', ARRAY[100], 'Check count from id_taptest_table_p50000_p58000_p58200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p58000_p58300', ARRAY[100], 'Check count from id_taptest_table_p50000_p58000_p58300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p58000_p58400', ARRAY[100], 'Check count from id_taptest_table_p50000_p58000_p58400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p58000_p58500', ARRAY[100], 'Check count from id_taptest_table_p50000_p58000_p58500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p58000_p58600', ARRAY[100], 'Check count from id_taptest_table_p50000_p58000_p58600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p58000_p58700', ARRAY[100], 'Check count from id_taptest_table_p50000_p58000_p58700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p58000_p58800', ARRAY[100], 'Check count from id_taptest_table_p50000_p58000_p58800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p58000_p58900', ARRAY[100], 'Check count from id_taptest_table_p50000_p58000_p58900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p59000_p59000', ARRAY[100], 'Check count from id_taptest_table_p50000_p59000_p59000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p59000_p59100', ARRAY[100], 'Check count from id_taptest_table_p50000_p59000_p59100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p59000_p59200', ARRAY[100], 'Check count from id_taptest_table_p50000_p59000_p59200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p59000_p59300', ARRAY[100], 'Check count from id_taptest_table_p50000_p59000_p59300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p59000_p59400', ARRAY[100], 'Check count from id_taptest_table_p50000_p59000_p59400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p59000_p59500', ARRAY[100], 'Check count from id_taptest_table_p50000_p59000_p59500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p59000_p59600', ARRAY[100], 'Check count from id_taptest_table_p50000_p59000_p59600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p59000_p59700', ARRAY[100], 'Check count from id_taptest_table_p50000_p59000_p59700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p59000_p59800', ARRAY[100], 'Check count from id_taptest_table_p50000_p59000_p59800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p59000_p59900', ARRAY[100], 'Check count from id_taptest_table_p50000_p59000_p59900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p60000_p60000', ARRAY[100], 'Check count from id_taptest_table_p60000_p60000_p60000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p60000_p60100', ARRAY[100], 'Check count from id_taptest_table_p60000_p60000_p60100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p60000_p60200', ARRAY[100], 'Check count from id_taptest_table_p60000_p60000_p60200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p60000_p60300', ARRAY[100], 'Check count from id_taptest_table_p60000_p60000_p60300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p60000_p60400', ARRAY[100], 'Check count from id_taptest_table_p60000_p60000_p60400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p60000_p60500', ARRAY[100], 'Check count from id_taptest_table_p60000_p60000_p60500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p60000_p60600', ARRAY[100], 'Check count from id_taptest_table_p60000_p60000_p60600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p60000_p60700', ARRAY[100], 'Check count from id_taptest_table_p60000_p60000_p60700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p60000_p60800', ARRAY[100], 'Check count from id_taptest_table_p60000_p60000_p60800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p60000_p60900', ARRAY[100], 'Check count from id_taptest_table_p60000_p60000_p60900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p61000_p61000', ARRAY[100], 'Check count from id_taptest_table_p60000_p61000_p61000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p61000_p61100', ARRAY[100], 'Check count from id_taptest_table_p60000_p61000_p61100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p61000_p61200', ARRAY[100], 'Check count from id_taptest_table_p60000_p61000_p61200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p61000_p61300', ARRAY[100], 'Check count from id_taptest_table_p60000_p61000_p61300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p61000_p61400', ARRAY[100], 'Check count from id_taptest_table_p60000_p61000_p61400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p61000_p61500', ARRAY[100], 'Check count from id_taptest_table_p60000_p61000_p61500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p61000_p61600', ARRAY[100], 'Check count from id_taptest_table_p60000_p61000_p61600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p61000_p61700', ARRAY[100], 'Check count from id_taptest_table_p60000_p61000_p61700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p61000_p61800', ARRAY[100], 'Check count from id_taptest_table_p60000_p61000_p61800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p61000_p61900', ARRAY[100], 'Check count from id_taptest_table_p60000_p61000_p61900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p62000_p62000', ARRAY[100], 'Check count from id_taptest_table_p60000_p62000_p62000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p62000_p62100', ARRAY[100], 'Check count from id_taptest_table_p60000_p62000_p62100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p62000_p62200', ARRAY[100], 'Check count from id_taptest_table_p60000_p62000_p62200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p62000_p62300', ARRAY[100], 'Check count from id_taptest_table_p60000_p62000_p62300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p62000_p62400', ARRAY[100], 'Check count from id_taptest_table_p60000_p62000_p62400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p62000_p62500', ARRAY[100], 'Check count from id_taptest_table_p60000_p62000_p62500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p62000_p62600', ARRAY[100], 'Check count from id_taptest_table_p60000_p62000_p62600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p62000_p62700', ARRAY[100], 'Check count from id_taptest_table_p60000_p62000_p62700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p62000_p62800', ARRAY[100], 'Check count from id_taptest_table_p60000_p62000_p62800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p62000_p62900', ARRAY[100], 'Check count from id_taptest_table_p60000_p62000_p62900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p63000_p63000', ARRAY[100], 'Check count from id_taptest_table_p60000_p63000_p63000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p63000_p63100', ARRAY[100], 'Check count from id_taptest_table_p60000_p63000_p63100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p63000_p63200', ARRAY[100], 'Check count from id_taptest_table_p60000_p63000_p63200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p63000_p63300', ARRAY[100], 'Check count from id_taptest_table_p60000_p63000_p63300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p63000_p63400', ARRAY[100], 'Check count from id_taptest_table_p60000_p63000_p63400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p63000_p63500', ARRAY[100], 'Check count from id_taptest_table_p60000_p63000_p63500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p63000_p63600', ARRAY[100], 'Check count from id_taptest_table_p60000_p63000_p63600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p63000_p63700', ARRAY[100], 'Check count from id_taptest_table_p60000_p63000_p63700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p63000_p63800', ARRAY[100], 'Check count from id_taptest_table_p60000_p63000_p63800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p63000_p63900', ARRAY[100], 'Check count from id_taptest_table_p60000_p63000_p63900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p64000_p64000', ARRAY[100], 'Check count from id_taptest_table_p60000_p64000_p64000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p64000_p64100', ARRAY[100], 'Check count from id_taptest_table_p60000_p64000_p64100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p64000_p64200', ARRAY[100], 'Check count from id_taptest_table_p60000_p64000_p64200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p64000_p64300', ARRAY[100], 'Check count from id_taptest_table_p60000_p64000_p64300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p64000_p64400', ARRAY[100], 'Check count from id_taptest_table_p60000_p64000_p64400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p64000_p64500', ARRAY[100], 'Check count from id_taptest_table_p60000_p64000_p64500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p64000_p64600', ARRAY[100], 'Check count from id_taptest_table_p60000_p64000_p64600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p64000_p64700', ARRAY[100], 'Check count from id_taptest_table_p60000_p64000_p64700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p64000_p64800', ARRAY[100], 'Check count from id_taptest_table_p60000_p64000_p64800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p64000_p64900', ARRAY[100], 'Check count from id_taptest_table_p60000_p64000_p64900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p65000_p65000', ARRAY[100], 'Check count from id_taptest_table_p60000_p65000_p65000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p65000_p65100', ARRAY[100], 'Check count from id_taptest_table_p60000_p65000_p65100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p65000_p65200', ARRAY[100], 'Check count from id_taptest_table_p60000_p65000_p65200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p65000_p65300', ARRAY[100], 'Check count from id_taptest_table_p60000_p65000_p65300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p65000_p65400', ARRAY[100], 'Check count from id_taptest_table_p60000_p65000_p65400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p65000_p65500', ARRAY[100], 'Check count from id_taptest_table_p60000_p65000_p65500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p65000_p65600', ARRAY[100], 'Check count from id_taptest_table_p60000_p65000_p65600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p65000_p65700', ARRAY[100], 'Check count from id_taptest_table_p60000_p65000_p65700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p65000_p65800', ARRAY[100], 'Check count from id_taptest_table_p60000_p65000_p65800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p65000_p65900', ARRAY[100], 'Check count from id_taptest_table_p60000_p65000_p65900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p66000_p66000', ARRAY[100], 'Check count from id_taptest_table_p60000_p66000_p66000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p66000_p66100', ARRAY[100], 'Check count from id_taptest_table_p60000_p66000_p66100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p66000_p66200', ARRAY[100], 'Check count from id_taptest_table_p60000_p66000_p66200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p66000_p66300', ARRAY[100], 'Check count from id_taptest_table_p60000_p66000_p66300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p66000_p66400', ARRAY[100], 'Check count from id_taptest_table_p60000_p66000_p66400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p66000_p66500', ARRAY[100], 'Check count from id_taptest_table_p60000_p66000_p66500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p66000_p66600', ARRAY[100], 'Check count from id_taptest_table_p60000_p66000_p66600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p66000_p66700', ARRAY[100], 'Check count from id_taptest_table_p60000_p66000_p66700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p66000_p66800', ARRAY[100], 'Check count from id_taptest_table_p60000_p66000_p66800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p66000_p66900', ARRAY[100], 'Check count from id_taptest_table_p60000_p66000_p66900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p67000_p67000', ARRAY[100], 'Check count from id_taptest_table_p60000_p67000_p67000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p67000_p67100', ARRAY[100], 'Check count from id_taptest_table_p60000_p67000_p67100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p67000_p67200', ARRAY[100], 'Check count from id_taptest_table_p60000_p67000_p67200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p67000_p67300', ARRAY[100], 'Check count from id_taptest_table_p60000_p67000_p67300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p67000_p67400', ARRAY[100], 'Check count from id_taptest_table_p60000_p67000_p67400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p67000_p67500', ARRAY[100], 'Check count from id_taptest_table_p60000_p67000_p67500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p67000_p67600', ARRAY[100], 'Check count from id_taptest_table_p60000_p67000_p67600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p67000_p67700', ARRAY[100], 'Check count from id_taptest_table_p60000_p67000_p67700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p67000_p67800', ARRAY[100], 'Check count from id_taptest_table_p60000_p67000_p67800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p67000_p67900', ARRAY[100], 'Check count from id_taptest_table_p60000_p67000_p67900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p68000_p68000', ARRAY[100], 'Check count from id_taptest_table_p60000_p68000_p68000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p68000_p68100', ARRAY[100], 'Check count from id_taptest_table_p60000_p68000_p68100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p68000_p68200', ARRAY[100], 'Check count from id_taptest_table_p60000_p68000_p68200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p68000_p68300', ARRAY[100], 'Check count from id_taptest_table_p60000_p68000_p68300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p68000_p68400', ARRAY[100], 'Check count from id_taptest_table_p60000_p68000_p68400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p68000_p68500', ARRAY[100], 'Check count from id_taptest_table_p60000_p68000_p68500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p68000_p68600', ARRAY[100], 'Check count from id_taptest_table_p60000_p68000_p68600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p68000_p68700', ARRAY[100], 'Check count from id_taptest_table_p60000_p68000_p68700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p68000_p68800', ARRAY[100], 'Check count from id_taptest_table_p60000_p68000_p68800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p68000_p68900', ARRAY[100], 'Check count from id_taptest_table_p60000_p68000_p68900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p69000_p69000', ARRAY[100], 'Check count from id_taptest_table_p60000_p69000_p69000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p69000_p69100', ARRAY[100], 'Check count from id_taptest_table_p60000_p69000_p69100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p69000_p69200', ARRAY[100], 'Check count from id_taptest_table_p60000_p69000_p69200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p69000_p69300', ARRAY[100], 'Check count from id_taptest_table_p60000_p69000_p69300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p69000_p69400', ARRAY[100], 'Check count from id_taptest_table_p60000_p69000_p69400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p69000_p69500', ARRAY[100], 'Check count from id_taptest_table_p60000_p69000_p69500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p69000_p69600', ARRAY[100], 'Check count from id_taptest_table_p60000_p69000_p69600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p69000_p69700', ARRAY[100], 'Check count from id_taptest_table_p60000_p69000_p69700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p69000_p69800', ARRAY[100], 'Check count from id_taptest_table_p60000_p69000_p69800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p69000_p69900', ARRAY[100], 'Check count from id_taptest_table_p60000_p69000_p69900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p70000_p70000', ARRAY[100], 'Check count from id_taptest_table_p70000_p70000_p70000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p70000_p70100', ARRAY[100], 'Check count from id_taptest_table_p70000_p70000_p70100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p70000_p70200', ARRAY[100], 'Check count from id_taptest_table_p70000_p70000_p70200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p70000_p70300', ARRAY[100], 'Check count from id_taptest_table_p70000_p70000_p70300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p70000_p70400', ARRAY[100], 'Check count from id_taptest_table_p70000_p70000_p70400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p70000_p70500', ARRAY[100], 'Check count from id_taptest_table_p70000_p70000_p70500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p70000_p70600', ARRAY[100], 'Check count from id_taptest_table_p70000_p70000_p70600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p70000_p70700', ARRAY[100], 'Check count from id_taptest_table_p70000_p70000_p70700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p70000_p70800', ARRAY[100], 'Check count from id_taptest_table_p70000_p70000_p70800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p70000_p70900', ARRAY[100], 'Check count from id_taptest_table_p70000_p70000_p70900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p71000_p71000', ARRAY[100], 'Check count from id_taptest_table_p70000_p71000_p71000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p71000_p71100', ARRAY[100], 'Check count from id_taptest_table_p70000_p71000_p71100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p71000_p71200', ARRAY[100], 'Check count from id_taptest_table_p70000_p71000_p71200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p71000_p71300', ARRAY[100], 'Check count from id_taptest_table_p70000_p71000_p71300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p71000_p71400', ARRAY[100], 'Check count from id_taptest_table_p70000_p71000_p71400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p71000_p71500', ARRAY[100], 'Check count from id_taptest_table_p70000_p71000_p71500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p71000_p71600', ARRAY[100], 'Check count from id_taptest_table_p70000_p71000_p71600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p71000_p71700', ARRAY[100], 'Check count from id_taptest_table_p70000_p71000_p71700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p71000_p71800', ARRAY[100], 'Check count from id_taptest_table_p70000_p71000_p71800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p71000_p71900', ARRAY[100], 'Check count from id_taptest_table_p70000_p71000_p71900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p72000_p72000', ARRAY[100], 'Check count from id_taptest_table_p70000_p72000_p72000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p72000_p72100', ARRAY[100], 'Check count from id_taptest_table_p70000_p72000_p72100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p72000_p72200', ARRAY[100], 'Check count from id_taptest_table_p70000_p72000_p72200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p72000_p72300', ARRAY[100], 'Check count from id_taptest_table_p70000_p72000_p72300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p72000_p72400', ARRAY[100], 'Check count from id_taptest_table_p70000_p72000_p72400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p72000_p72500', ARRAY[100], 'Check count from id_taptest_table_p70000_p72000_p72500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p72000_p72600', ARRAY[100], 'Check count from id_taptest_table_p70000_p72000_p72600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p72000_p72700', ARRAY[100], 'Check count from id_taptest_table_p70000_p72000_p72700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p72000_p72800', ARRAY[100], 'Check count from id_taptest_table_p70000_p72000_p72800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p72000_p72900', ARRAY[100], 'Check count from id_taptest_table_p70000_p72000_p72900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p73000_p73000', ARRAY[100], 'Check count from id_taptest_table_p70000_p73000_p73000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p73000_p73100', ARRAY[100], 'Check count from id_taptest_table_p70000_p73000_p73100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p73000_p73200', ARRAY[100], 'Check count from id_taptest_table_p70000_p73000_p73200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p73000_p73300', ARRAY[100], 'Check count from id_taptest_table_p70000_p73000_p73300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p73000_p73400', ARRAY[100], 'Check count from id_taptest_table_p70000_p73000_p73400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p73000_p73500', ARRAY[100], 'Check count from id_taptest_table_p70000_p73000_p73500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p73000_p73600', ARRAY[100], 'Check count from id_taptest_table_p70000_p73000_p73600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p73000_p73700', ARRAY[100], 'Check count from id_taptest_table_p70000_p73000_p73700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p73000_p73800', ARRAY[100], 'Check count from id_taptest_table_p70000_p73000_p73800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p73000_p73900', ARRAY[100], 'Check count from id_taptest_table_p70000_p73000_p73900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p74000_p74000', ARRAY[100], 'Check count from id_taptest_table_p70000_p74000_p74000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p74000_p74100', ARRAY[100], 'Check count from id_taptest_table_p70000_p74000_p74100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p74000_p74200', ARRAY[100], 'Check count from id_taptest_table_p70000_p74000_p74200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p74000_p74300', ARRAY[100], 'Check count from id_taptest_table_p70000_p74000_p74300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p74000_p74400', ARRAY[100], 'Check count from id_taptest_table_p70000_p74000_p74400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p74000_p74500', ARRAY[100], 'Check count from id_taptest_table_p70000_p74000_p74500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p74000_p74600', ARRAY[100], 'Check count from id_taptest_table_p70000_p74000_p74600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p74000_p74700', ARRAY[100], 'Check count from id_taptest_table_p70000_p74000_p74700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p74000_p74800', ARRAY[100], 'Check count from id_taptest_table_p70000_p74000_p74800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p74000_p74900', ARRAY[100], 'Check count from id_taptest_table_p70000_p74000_p74900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p75000_p75000', ARRAY[100], 'Check count from id_taptest_table_p70000_p75000_p75000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p75000_p75100', ARRAY[100], 'Check count from id_taptest_table_p70000_p75000_p75100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p75000_p75200', ARRAY[100], 'Check count from id_taptest_table_p70000_p75000_p75200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p75000_p75300', ARRAY[100], 'Check count from id_taptest_table_p70000_p75000_p75300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p75000_p75400', ARRAY[100], 'Check count from id_taptest_table_p70000_p75000_p75400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p75000_p75500', ARRAY[100], 'Check count from id_taptest_table_p70000_p75000_p75500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p75000_p75600', ARRAY[100], 'Check count from id_taptest_table_p70000_p75000_p75600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p75000_p75700', ARRAY[100], 'Check count from id_taptest_table_p70000_p75000_p75700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p75000_p75800', ARRAY[100], 'Check count from id_taptest_table_p70000_p75000_p75800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p75000_p75900', ARRAY[100], 'Check count from id_taptest_table_p70000_p75000_p75900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p76000_p76000', ARRAY[100], 'Check count from id_taptest_table_p70000_p76000_p76000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p76000_p76100', ARRAY[100], 'Check count from id_taptest_table_p70000_p76000_p76100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p76000_p76200', ARRAY[100], 'Check count from id_taptest_table_p70000_p76000_p76200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p76000_p76300', ARRAY[100], 'Check count from id_taptest_table_p70000_p76000_p76300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p76000_p76400', ARRAY[100], 'Check count from id_taptest_table_p70000_p76000_p76400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p76000_p76500', ARRAY[100], 'Check count from id_taptest_table_p70000_p76000_p76500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p76000_p76600', ARRAY[100], 'Check count from id_taptest_table_p70000_p76000_p76600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p76000_p76700', ARRAY[100], 'Check count from id_taptest_table_p70000_p76000_p76700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p76000_p76800', ARRAY[100], 'Check count from id_taptest_table_p70000_p76000_p76800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p76000_p76900', ARRAY[100], 'Check count from id_taptest_table_p70000_p76000_p76900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p77000_p77000', ARRAY[100], 'Check count from id_taptest_table_p70000_p77000_p77000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p77000_p77100', ARRAY[100], 'Check count from id_taptest_table_p70000_p77000_p77100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p77000_p77200', ARRAY[100], 'Check count from id_taptest_table_p70000_p77000_p77200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p77000_p77300', ARRAY[100], 'Check count from id_taptest_table_p70000_p77000_p77300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p77000_p77400', ARRAY[100], 'Check count from id_taptest_table_p70000_p77000_p77400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p77000_p77500', ARRAY[100], 'Check count from id_taptest_table_p70000_p77000_p77500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p77000_p77600', ARRAY[100], 'Check count from id_taptest_table_p70000_p77000_p77600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p77000_p77700', ARRAY[100], 'Check count from id_taptest_table_p70000_p77000_p77700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p77000_p77800', ARRAY[100], 'Check count from id_taptest_table_p70000_p77000_p77800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p77000_p77900', ARRAY[100], 'Check count from id_taptest_table_p70000_p77000_p77900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p78000_p78000', ARRAY[100], 'Check count from id_taptest_table_p70000_p78000_p78000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p78000_p78100', ARRAY[100], 'Check count from id_taptest_table_p70000_p78000_p78100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p78000_p78200', ARRAY[100], 'Check count from id_taptest_table_p70000_p78000_p78200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p78000_p78300', ARRAY[100], 'Check count from id_taptest_table_p70000_p78000_p78300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p78000_p78400', ARRAY[100], 'Check count from id_taptest_table_p70000_p78000_p78400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p78000_p78500', ARRAY[100], 'Check count from id_taptest_table_p70000_p78000_p78500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p78000_p78600', ARRAY[100], 'Check count from id_taptest_table_p70000_p78000_p78600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p78000_p78700', ARRAY[100], 'Check count from id_taptest_table_p70000_p78000_p78700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p78000_p78800', ARRAY[100], 'Check count from id_taptest_table_p70000_p78000_p78800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p78000_p78900', ARRAY[100], 'Check count from id_taptest_table_p70000_p78000_p78900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p79000_p79000', ARRAY[100], 'Check count from id_taptest_table_p70000_p79000_p79000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p79000_p79100', ARRAY[100], 'Check count from id_taptest_table_p70000_p79000_p79100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p79000_p79200', ARRAY[100], 'Check count from id_taptest_table_p70000_p79000_p79200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p79000_p79300', ARRAY[100], 'Check count from id_taptest_table_p70000_p79000_p79300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p79000_p79400', ARRAY[100], 'Check count from id_taptest_table_p70000_p79000_p79400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p79000_p79500', ARRAY[100], 'Check count from id_taptest_table_p70000_p79000_p79500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p79000_p79600', ARRAY[100], 'Check count from id_taptest_table_p70000_p79000_p79600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p79000_p79700', ARRAY[100], 'Check count from id_taptest_table_p70000_p79000_p79700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p79000_p79800', ARRAY[100], 'Check count from id_taptest_table_p70000_p79000_p79800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p79000_p79900', ARRAY[100], 'Check count from id_taptest_table_p70000_p79000_p79900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p80000_p80000', ARRAY[100], 'Check count from id_taptest_table_p80000_p80000_p80000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p80000_p80100', ARRAY[100], 'Check count from id_taptest_table_p80000_p80000_p80100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p80000_p80200', ARRAY[100], 'Check count from id_taptest_table_p80000_p80000_p80200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p80000_p80300', ARRAY[100], 'Check count from id_taptest_table_p80000_p80000_p80300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p80000_p80400', ARRAY[100], 'Check count from id_taptest_table_p80000_p80000_p80400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p80000_p80500', ARRAY[100], 'Check count from id_taptest_table_p80000_p80000_p80500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p80000_p80600', ARRAY[100], 'Check count from id_taptest_table_p80000_p80000_p80600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p80000_p80700', ARRAY[100], 'Check count from id_taptest_table_p80000_p80000_p80700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p80000_p80800', ARRAY[100], 'Check count from id_taptest_table_p80000_p80000_p80800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p80000_p80900', ARRAY[100], 'Check count from id_taptest_table_p80000_p80000_p80900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p81000_p81000', ARRAY[100], 'Check count from id_taptest_table_p80000_p81000_p81000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p81000_p81100', ARRAY[100], 'Check count from id_taptest_table_p80000_p81000_p81100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p81000_p81200', ARRAY[100], 'Check count from id_taptest_table_p80000_p81000_p81200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p81000_p81300', ARRAY[100], 'Check count from id_taptest_table_p80000_p81000_p81300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p81000_p81400', ARRAY[100], 'Check count from id_taptest_table_p80000_p81000_p81400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p81000_p81500', ARRAY[100], 'Check count from id_taptest_table_p80000_p81000_p81500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p81000_p81600', ARRAY[100], 'Check count from id_taptest_table_p80000_p81000_p81600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p81000_p81700', ARRAY[100], 'Check count from id_taptest_table_p80000_p81000_p81700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p81000_p81800', ARRAY[100], 'Check count from id_taptest_table_p80000_p81000_p81800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p81000_p81900', ARRAY[100], 'Check count from id_taptest_table_p80000_p81000_p81900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p82000_p82000', ARRAY[100], 'Check count from id_taptest_table_p80000_p82000_p82000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p82000_p82100', ARRAY[100], 'Check count from id_taptest_table_p80000_p82000_p82100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p82000_p82200', ARRAY[100], 'Check count from id_taptest_table_p80000_p82000_p82200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p82000_p82300', ARRAY[100], 'Check count from id_taptest_table_p80000_p82000_p82300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p82000_p82400', ARRAY[100], 'Check count from id_taptest_table_p80000_p82000_p82400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p82000_p82500', ARRAY[100], 'Check count from id_taptest_table_p80000_p82000_p82500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p82000_p82600', ARRAY[100], 'Check count from id_taptest_table_p80000_p82000_p82600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p82000_p82700', ARRAY[100], 'Check count from id_taptest_table_p80000_p82000_p82700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p82000_p82800', ARRAY[100], 'Check count from id_taptest_table_p80000_p82000_p82800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p82000_p82900', ARRAY[100], 'Check count from id_taptest_table_p80000_p82000_p82900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p83000_p83000', ARRAY[100], 'Check count from id_taptest_table_p80000_p83000_p83000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p83000_p83100', ARRAY[100], 'Check count from id_taptest_table_p80000_p83000_p83100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p83000_p83200', ARRAY[100], 'Check count from id_taptest_table_p80000_p83000_p83200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p83000_p83300', ARRAY[100], 'Check count from id_taptest_table_p80000_p83000_p83300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p83000_p83400', ARRAY[100], 'Check count from id_taptest_table_p80000_p83000_p83400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p83000_p83500', ARRAY[100], 'Check count from id_taptest_table_p80000_p83000_p83500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p83000_p83600', ARRAY[100], 'Check count from id_taptest_table_p80000_p83000_p83600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p83000_p83700', ARRAY[100], 'Check count from id_taptest_table_p80000_p83000_p83700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p83000_p83800', ARRAY[100], 'Check count from id_taptest_table_p80000_p83000_p83800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p83000_p83900', ARRAY[100], 'Check count from id_taptest_table_p80000_p83000_p83900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p84000_p84000', ARRAY[100], 'Check count from id_taptest_table_p80000_p84000_p84000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p84000_p84100', ARRAY[100], 'Check count from id_taptest_table_p80000_p84000_p84100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p84000_p84200', ARRAY[100], 'Check count from id_taptest_table_p80000_p84000_p84200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p84000_p84300', ARRAY[100], 'Check count from id_taptest_table_p80000_p84000_p84300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p84000_p84400', ARRAY[100], 'Check count from id_taptest_table_p80000_p84000_p84400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p84000_p84500', ARRAY[100], 'Check count from id_taptest_table_p80000_p84000_p84500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p84000_p84600', ARRAY[100], 'Check count from id_taptest_table_p80000_p84000_p84600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p84000_p84700', ARRAY[100], 'Check count from id_taptest_table_p80000_p84000_p84700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p84000_p84800', ARRAY[100], 'Check count from id_taptest_table_p80000_p84000_p84800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p84000_p84900', ARRAY[100], 'Check count from id_taptest_table_p80000_p84000_p84900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p85000_p85000', ARRAY[100], 'Check count from id_taptest_table_p80000_p85000_p85000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p85000_p85100', ARRAY[100], 'Check count from id_taptest_table_p80000_p85000_p85100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p85000_p85200', ARRAY[100], 'Check count from id_taptest_table_p80000_p85000_p85200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p85000_p85300', ARRAY[100], 'Check count from id_taptest_table_p80000_p85000_p85300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p85000_p85400', ARRAY[100], 'Check count from id_taptest_table_p80000_p85000_p85400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p85000_p85500', ARRAY[100], 'Check count from id_taptest_table_p80000_p85000_p85500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p85000_p85600', ARRAY[100], 'Check count from id_taptest_table_p80000_p85000_p85600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p85000_p85700', ARRAY[100], 'Check count from id_taptest_table_p80000_p85000_p85700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p85000_p85800', ARRAY[100], 'Check count from id_taptest_table_p80000_p85000_p85800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p85000_p85900', ARRAY[100], 'Check count from id_taptest_table_p80000_p85000_p85900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p86000_p86000', ARRAY[100], 'Check count from id_taptest_table_p80000_p86000_p86000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p86000_p86100', ARRAY[100], 'Check count from id_taptest_table_p80000_p86000_p86100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p86000_p86200', ARRAY[100], 'Check count from id_taptest_table_p80000_p86000_p86200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p86000_p86300', ARRAY[100], 'Check count from id_taptest_table_p80000_p86000_p86300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p86000_p86400', ARRAY[100], 'Check count from id_taptest_table_p80000_p86000_p86400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p86000_p86500', ARRAY[100], 'Check count from id_taptest_table_p80000_p86000_p86500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p86000_p86600', ARRAY[100], 'Check count from id_taptest_table_p80000_p86000_p86600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p86000_p86700', ARRAY[100], 'Check count from id_taptest_table_p80000_p86000_p86700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p86000_p86800', ARRAY[100], 'Check count from id_taptest_table_p80000_p86000_p86800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p86000_p86900', ARRAY[100], 'Check count from id_taptest_table_p80000_p86000_p86900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p87000_p87000', ARRAY[100], 'Check count from id_taptest_table_p80000_p87000_p87000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p87000_p87100', ARRAY[100], 'Check count from id_taptest_table_p80000_p87000_p87100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p87000_p87200', ARRAY[100], 'Check count from id_taptest_table_p80000_p87000_p87200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p87000_p87300', ARRAY[100], 'Check count from id_taptest_table_p80000_p87000_p87300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p87000_p87400', ARRAY[100], 'Check count from id_taptest_table_p80000_p87000_p87400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p87000_p87500', ARRAY[100], 'Check count from id_taptest_table_p80000_p87000_p87500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p87000_p87600', ARRAY[100], 'Check count from id_taptest_table_p80000_p87000_p87600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p87000_p87700', ARRAY[100], 'Check count from id_taptest_table_p80000_p87000_p87700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p87000_p87800', ARRAY[100], 'Check count from id_taptest_table_p80000_p87000_p87800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p87000_p87900', ARRAY[100], 'Check count from id_taptest_table_p80000_p87000_p87900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p88000_p88000', ARRAY[100], 'Check count from id_taptest_table_p80000_p88000_p88000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p88000_p88100', ARRAY[100], 'Check count from id_taptest_table_p80000_p88000_p88100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p88000_p88200', ARRAY[100], 'Check count from id_taptest_table_p80000_p88000_p88200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p88000_p88300', ARRAY[100], 'Check count from id_taptest_table_p80000_p88000_p88300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p88000_p88400', ARRAY[100], 'Check count from id_taptest_table_p80000_p88000_p88400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p88000_p88500', ARRAY[100], 'Check count from id_taptest_table_p80000_p88000_p88500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p88000_p88600', ARRAY[100], 'Check count from id_taptest_table_p80000_p88000_p88600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p88000_p88700', ARRAY[100], 'Check count from id_taptest_table_p80000_p88000_p88700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p88000_p88800', ARRAY[100], 'Check count from id_taptest_table_p80000_p88000_p88800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p88000_p88900', ARRAY[100], 'Check count from id_taptest_table_p80000_p88000_p88900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p89000_p89000', ARRAY[100], 'Check count from id_taptest_table_p80000_p89000_p89000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p89000_p89100', ARRAY[100], 'Check count from id_taptest_table_p80000_p89000_p89100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p89000_p89200', ARRAY[100], 'Check count from id_taptest_table_p80000_p89000_p89200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p89000_p89300', ARRAY[100], 'Check count from id_taptest_table_p80000_p89000_p89300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p89000_p89400', ARRAY[100], 'Check count from id_taptest_table_p80000_p89000_p89400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p89000_p89500', ARRAY[100], 'Check count from id_taptest_table_p80000_p89000_p89500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p89000_p89600', ARRAY[100], 'Check count from id_taptest_table_p80000_p89000_p89600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p89000_p89700', ARRAY[100], 'Check count from id_taptest_table_p80000_p89000_p89700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p89000_p89800', ARRAY[100], 'Check count from id_taptest_table_p80000_p89000_p89800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p89000_p89900', ARRAY[100], 'Check count from id_taptest_table_p80000_p89000_p89900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p90000_p90000', ARRAY[100], 'Check count from id_taptest_table_p90000_p90000_p90000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p90000_p90100', ARRAY[100], 'Check count from id_taptest_table_p90000_p90000_p90100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p90000_p90200', ARRAY[100], 'Check count from id_taptest_table_p90000_p90000_p90200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p90000_p90300', ARRAY[100], 'Check count from id_taptest_table_p90000_p90000_p90300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p90000_p90400', ARRAY[100], 'Check count from id_taptest_table_p90000_p90000_p90400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p90000_p90500', ARRAY[100], 'Check count from id_taptest_table_p90000_p90000_p90500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p90000_p90600', ARRAY[100], 'Check count from id_taptest_table_p90000_p90000_p90600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p90000_p90700', ARRAY[100], 'Check count from id_taptest_table_p90000_p90000_p90700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p90000_p90800', ARRAY[100], 'Check count from id_taptest_table_p90000_p90000_p90800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p90000_p90900', ARRAY[100], 'Check count from id_taptest_table_p90000_p90000_p90900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p91000_p91000', ARRAY[100], 'Check count from id_taptest_table_p90000_p91000_p91000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p91000_p91100', ARRAY[100], 'Check count from id_taptest_table_p90000_p91000_p91100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p91000_p91200', ARRAY[100], 'Check count from id_taptest_table_p90000_p91000_p91200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p91000_p91300', ARRAY[100], 'Check count from id_taptest_table_p90000_p91000_p91300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p91000_p91400', ARRAY[100], 'Check count from id_taptest_table_p90000_p91000_p91400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p91000_p91500', ARRAY[100], 'Check count from id_taptest_table_p90000_p91000_p91500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p91000_p91600', ARRAY[100], 'Check count from id_taptest_table_p90000_p91000_p91600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p91000_p91700', ARRAY[100], 'Check count from id_taptest_table_p90000_p91000_p91700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p91000_p91800', ARRAY[100], 'Check count from id_taptest_table_p90000_p91000_p91800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p91000_p91900', ARRAY[100], 'Check count from id_taptest_table_p90000_p91000_p91900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p92000_p92000', ARRAY[100], 'Check count from id_taptest_table_p90000_p92000_p92000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p92000_p92100', ARRAY[100], 'Check count from id_taptest_table_p90000_p92000_p92100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p92000_p92200', ARRAY[100], 'Check count from id_taptest_table_p90000_p92000_p92200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p92000_p92300', ARRAY[100], 'Check count from id_taptest_table_p90000_p92000_p92300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p92000_p92400', ARRAY[100], 'Check count from id_taptest_table_p90000_p92000_p92400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p92000_p92500', ARRAY[100], 'Check count from id_taptest_table_p90000_p92000_p92500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p92000_p92600', ARRAY[100], 'Check count from id_taptest_table_p90000_p92000_p92600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p92000_p92700', ARRAY[100], 'Check count from id_taptest_table_p90000_p92000_p92700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p92000_p92800', ARRAY[100], 'Check count from id_taptest_table_p90000_p92000_p92800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p92000_p92900', ARRAY[100], 'Check count from id_taptest_table_p90000_p92000_p92900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p93000_p93000', ARRAY[100], 'Check count from id_taptest_table_p90000_p93000_p93000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p93000_p93100', ARRAY[100], 'Check count from id_taptest_table_p90000_p93000_p93100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p93000_p93200', ARRAY[100], 'Check count from id_taptest_table_p90000_p93000_p93200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p93000_p93300', ARRAY[100], 'Check count from id_taptest_table_p90000_p93000_p93300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p93000_p93400', ARRAY[100], 'Check count from id_taptest_table_p90000_p93000_p93400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p93000_p93500', ARRAY[100], 'Check count from id_taptest_table_p90000_p93000_p93500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p93000_p93600', ARRAY[100], 'Check count from id_taptest_table_p90000_p93000_p93600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p93000_p93700', ARRAY[100], 'Check count from id_taptest_table_p90000_p93000_p93700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p93000_p93800', ARRAY[100], 'Check count from id_taptest_table_p90000_p93000_p93800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p93000_p93900', ARRAY[100], 'Check count from id_taptest_table_p90000_p93000_p93900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p94000_p94000', ARRAY[100], 'Check count from id_taptest_table_p90000_p94000_p94000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p94000_p94100', ARRAY[100], 'Check count from id_taptest_table_p90000_p94000_p94100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p94000_p94200', ARRAY[100], 'Check count from id_taptest_table_p90000_p94000_p94200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p94000_p94300', ARRAY[100], 'Check count from id_taptest_table_p90000_p94000_p94300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p94000_p94400', ARRAY[100], 'Check count from id_taptest_table_p90000_p94000_p94400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p94000_p94500', ARRAY[100], 'Check count from id_taptest_table_p90000_p94000_p94500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p94000_p94600', ARRAY[100], 'Check count from id_taptest_table_p90000_p94000_p94600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p94000_p94700', ARRAY[100], 'Check count from id_taptest_table_p90000_p94000_p94700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p94000_p94800', ARRAY[100], 'Check count from id_taptest_table_p90000_p94000_p94800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p94000_p94900', ARRAY[100], 'Check count from id_taptest_table_p90000_p94000_p94900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p95000_p95000', ARRAY[100], 'Check count from id_taptest_table_p90000_p95000_p95000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p95000_p95100', ARRAY[100], 'Check count from id_taptest_table_p90000_p95000_p95100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p95000_p95200', ARRAY[100], 'Check count from id_taptest_table_p90000_p95000_p95200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p95000_p95300', ARRAY[100], 'Check count from id_taptest_table_p90000_p95000_p95300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p95000_p95400', ARRAY[100], 'Check count from id_taptest_table_p90000_p95000_p95400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p95000_p95500', ARRAY[100], 'Check count from id_taptest_table_p90000_p95000_p95500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p95000_p95600', ARRAY[100], 'Check count from id_taptest_table_p90000_p95000_p95600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p95000_p95700', ARRAY[100], 'Check count from id_taptest_table_p90000_p95000_p95700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p95000_p95800', ARRAY[100], 'Check count from id_taptest_table_p90000_p95000_p95800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p95000_p95900', ARRAY[100], 'Check count from id_taptest_table_p90000_p95000_p95900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p96000_p96000', ARRAY[100], 'Check count from id_taptest_table_p90000_p96000_p96000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p96000_p96100', ARRAY[100], 'Check count from id_taptest_table_p90000_p96000_p96100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p96000_p96200', ARRAY[100], 'Check count from id_taptest_table_p90000_p96000_p96200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p96000_p96300', ARRAY[100], 'Check count from id_taptest_table_p90000_p96000_p96300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p96000_p96400', ARRAY[100], 'Check count from id_taptest_table_p90000_p96000_p96400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p96000_p96500', ARRAY[100], 'Check count from id_taptest_table_p90000_p96000_p96500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p96000_p96600', ARRAY[100], 'Check count from id_taptest_table_p90000_p96000_p96600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p96000_p96700', ARRAY[100], 'Check count from id_taptest_table_p90000_p96000_p96700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p96000_p96800', ARRAY[100], 'Check count from id_taptest_table_p90000_p96000_p96800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p96000_p96900', ARRAY[100], 'Check count from id_taptest_table_p90000_p96000_p96900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p97000_p97000', ARRAY[100], 'Check count from id_taptest_table_p90000_p97000_p97000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p97000_p97100', ARRAY[100], 'Check count from id_taptest_table_p90000_p97000_p97100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p97000_p97200', ARRAY[100], 'Check count from id_taptest_table_p90000_p97000_p97200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p97000_p97300', ARRAY[100], 'Check count from id_taptest_table_p90000_p97000_p97300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p97000_p97400', ARRAY[100], 'Check count from id_taptest_table_p90000_p97000_p97400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p97000_p97500', ARRAY[100], 'Check count from id_taptest_table_p90000_p97000_p97500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p97000_p97600', ARRAY[100], 'Check count from id_taptest_table_p90000_p97000_p97600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p97000_p97700', ARRAY[100], 'Check count from id_taptest_table_p90000_p97000_p97700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p97000_p97800', ARRAY[100], 'Check count from id_taptest_table_p90000_p97000_p97800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p97000_p97900', ARRAY[100], 'Check count from id_taptest_table_p90000_p97000_p97900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p98000_p98000', ARRAY[100], 'Check count from id_taptest_table_p90000_p98000_p98000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p98000_p98100', ARRAY[100], 'Check count from id_taptest_table_p90000_p98000_p98100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p98000_p98200', ARRAY[100], 'Check count from id_taptest_table_p90000_p98000_p98200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p98000_p98300', ARRAY[100], 'Check count from id_taptest_table_p90000_p98000_p98300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p98000_p98400', ARRAY[100], 'Check count from id_taptest_table_p90000_p98000_p98400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p98000_p98500', ARRAY[100], 'Check count from id_taptest_table_p90000_p98000_p98500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p98000_p98600', ARRAY[100], 'Check count from id_taptest_table_p90000_p98000_p98600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p98000_p98700', ARRAY[100], 'Check count from id_taptest_table_p90000_p98000_p98700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p98000_p98800', ARRAY[100], 'Check count from id_taptest_table_p90000_p98000_p98800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p98000_p98900', ARRAY[100], 'Check count from id_taptest_table_p90000_p98000_p98900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p99000_p99000', ARRAY[100], 'Check count from id_taptest_table_p90000_p99000_p99000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p99000_p99100', ARRAY[100], 'Check count from id_taptest_table_p90000_p99000_p99100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p99000_p99200', ARRAY[100], 'Check count from id_taptest_table_p90000_p99000_p99200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p99000_p99300', ARRAY[100], 'Check count from id_taptest_table_p90000_p99000_p99300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p99000_p99400', ARRAY[100], 'Check count from id_taptest_table_p90000_p99000_p99400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p99000_p99500', ARRAY[100], 'Check count from id_taptest_table_p90000_p99000_p99500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p99000_p99600', ARRAY[100], 'Check count from id_taptest_table_p90000_p99000_p99600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p99000_p99700', ARRAY[100], 'Check count from id_taptest_table_p90000_p99000_p99700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p99000_p99800', ARRAY[100], 'Check count from id_taptest_table_p90000_p99000_p99800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p99000_p99900', ARRAY[100], 'Check count from id_taptest_table_p90000_p99000_p99900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p100000_p100000', ARRAY[1], 'Check count from id_taptest_table_p100000_p100000_p100000'); -- INSERT batch 2 INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(100001, 100100)); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table partman_test.id_taptest_table is empty'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[100100], 'Check count from parent table partman_test.id_taptest_table (175000)'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p100000', 'Check that parent table partman_test.id_taptest_table_p100000 is empty'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p100000_p100000', 'Check that parent table partman_test.id_taptest_table_p100000_p100000 is empty'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p100000_p101000', 'Check that parent table partman_test.id_taptest_table_p100000_p101000 is empty'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p100000_p100000', ARRAY[100], 'Check count from id_taptest_table_p100000_p100000_p100000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p100000_p100100', ARRAY[1], 'Check count from id_taptest_table_p100000_p100000_p100100'); SELECT run_maintenance(); SELECT has_table('partman_test', 'id_taptest_table_p100000_p100000_p100300', 'Check id_taptest_table_p100000_p100000_p100300 exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p100000_p100000_p100400', 'Check id_taptest_table_p100000_p100000_p100400 does not exists'); UPDATE part_config SET premake = 11 WHERE parent_table LIKE 'partman_test.id_taptest_table%' AND partition_type = 'partman'; SELECT run_maintenance(); INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(100101,107500)); -- Stuff likely went into some parent tables. It's diffcult to test a constant stream of input data to regularly run run_maintenence() against. SELECT results_eq('SELECT default_table, count::int FROM check_default() ORDER BY default_table', $$VALUES ('partman_test.id_taptest_table_p100000_p101000', 900), ('partman_test.id_taptest_table_p100000_p102000', 900), ('partman_test.id_taptest_table_p100000_p103000', 900), ('partman_test.id_taptest_table_p100000_p104000', 900), ('partman_test.id_taptest_table_p100000_p105000', 900), ('partman_test.id_taptest_table_p100000_p106000', 900), ('partman_test.id_taptest_table_p100000_p107000', 401)$$, 'check_default() run to see if stuff was put into parent tables'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p100000_p101000'', p_batch_count := 20)::int', ARRAY[900], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p100000_p101000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p100000_p102000'', p_batch_count := 20)::int', ARRAY[900], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p100000_p102000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p100000_p103000'', p_batch_count := 20)::int', ARRAY[900], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p100000_p103000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p100000_p104000'', p_batch_count := 20)::int', ARRAY[900], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p100000_p104000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p100000_p105000'', p_batch_count := 20)::int', ARRAY[900], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p100000_p105000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p100000_p106000'', p_batch_count := 20)::int', ARRAY[900], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p100000_p106000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p100000_p107000'', p_batch_count := 20)::int', ARRAY[401], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p100000_p107000'); SELECT is_empty('SELECT * from check_default()', 'check_default() should return nothing now.'); -- Check for the rest of batch 2 tables now SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p100000_p100000', ARRAY[100], 'Check count fromid_taptest_table_p100000_p100000_p100000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p100000_p100100', ARRAY[100], 'Check count fromid_taptest_table_p100000_p100000_p100100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p100000_p100200', ARRAY[100], 'Check count fromid_taptest_table_p100000_p100000_p100200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p100000_p100300', ARRAY[100], 'Check count fromid_taptest_table_p100000_p100000_p100300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p100000_p100400', ARRAY[100], 'Check count fromid_taptest_table_p100000_p100000_p100400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p100000_p100500', ARRAY[100], 'Check count fromid_taptest_table_p100000_p100000_p100500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p100000_p100600', ARRAY[100], 'Check count fromid_taptest_table_p100000_p100000_p100600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p100000_p100700', ARRAY[100], 'Check count fromid_taptest_table_p100000_p100000_p100700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p100000_p100800', ARRAY[100], 'Check count fromid_taptest_table_p100000_p100000_p100800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p100000_p100900', ARRAY[100], 'Check count fromid_taptest_table_p100000_p100000_p100900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p101000_p101000', ARRAY[100], 'Check count fromid_taptest_table_p100000_p101000_p101000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p101000_p101100', ARRAY[100], 'Check count fromid_taptest_table_p100000_p101000_p101100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p101000_p101200', ARRAY[100], 'Check count fromid_taptest_table_p100000_p101000_p101200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p101000_p101300', ARRAY[100], 'Check count fromid_taptest_table_p100000_p101000_p101300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p101000_p101400', ARRAY[100], 'Check count fromid_taptest_table_p100000_p101000_p101400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p101000_p101500', ARRAY[100], 'Check count fromid_taptest_table_p100000_p101000_p101500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p101000_p101600', ARRAY[100], 'Check count fromid_taptest_table_p100000_p101000_p101600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p101000_p101700', ARRAY[100], 'Check count fromid_taptest_table_p100000_p101000_p101700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p101000_p101800', ARRAY[100], 'Check count fromid_taptest_table_p100000_p101000_p101800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p101000_p101900', ARRAY[100], 'Check count fromid_taptest_table_p100000_p101000_p101900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p102000_p102000', ARRAY[100], 'Check count fromid_taptest_table_p100000_p102000_p102000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p102000_p102100', ARRAY[100], 'Check count fromid_taptest_table_p100000_p102000_p102100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p102000_p102200', ARRAY[100], 'Check count fromid_taptest_table_p100000_p102000_p102200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p102000_p102300', ARRAY[100], 'Check count fromid_taptest_table_p100000_p102000_p102300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p102000_p102400', ARRAY[100], 'Check count fromid_taptest_table_p100000_p102000_p102400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p102000_p102500', ARRAY[100], 'Check count fromid_taptest_table_p100000_p102000_p102500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p102000_p102600', ARRAY[100], 'Check count fromid_taptest_table_p100000_p102000_p102600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p102000_p102700', ARRAY[100], 'Check count fromid_taptest_table_p100000_p102000_p102700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p102000_p102800', ARRAY[100], 'Check count fromid_taptest_table_p100000_p102000_p102800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p102000_p102900', ARRAY[100], 'Check count fromid_taptest_table_p100000_p102000_p102900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p103000_p103000', ARRAY[100], 'Check count fromid_taptest_table_p100000_p103000_p103000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p103000_p103100', ARRAY[100], 'Check count fromid_taptest_table_p100000_p103000_p103100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p103000_p103200', ARRAY[100], 'Check count fromid_taptest_table_p100000_p103000_p103200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p103000_p103300', ARRAY[100], 'Check count fromid_taptest_table_p100000_p103000_p103300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p103000_p103400', ARRAY[100], 'Check count fromid_taptest_table_p100000_p103000_p103400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p103000_p103500', ARRAY[100], 'Check count fromid_taptest_table_p100000_p103000_p103500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p103000_p103600', ARRAY[100], 'Check count fromid_taptest_table_p100000_p103000_p103600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p103000_p103700', ARRAY[100], 'Check count fromid_taptest_table_p100000_p103000_p103700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p103000_p103800', ARRAY[100], 'Check count fromid_taptest_table_p100000_p103000_p103800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p103000_p103900', ARRAY[100], 'Check count fromid_taptest_table_p100000_p103000_p103900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p104000_p104000', ARRAY[100], 'Check count fromid_taptest_table_p100000_p104000_p104000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p104000_p104100', ARRAY[100], 'Check count fromid_taptest_table_p100000_p104000_p104100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p104000_p104200', ARRAY[100], 'Check count fromid_taptest_table_p100000_p104000_p104200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p104000_p104300', ARRAY[100], 'Check count fromid_taptest_table_p100000_p104000_p104300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p104000_p104400', ARRAY[100], 'Check count fromid_taptest_table_p100000_p104000_p104400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p104000_p104500', ARRAY[100], 'Check count fromid_taptest_table_p100000_p104000_p104500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p104000_p104600', ARRAY[100], 'Check count fromid_taptest_table_p100000_p104000_p104600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p104000_p104700', ARRAY[100], 'Check count fromid_taptest_table_p100000_p104000_p104700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p104000_p104800', ARRAY[100], 'Check count fromid_taptest_table_p100000_p104000_p104800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p104000_p104900', ARRAY[100], 'Check count fromid_taptest_table_p100000_p104000_p104900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p105000_p105000', ARRAY[100], 'Check count fromid_taptest_table_p100000_p105000_p105000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p105000_p105100', ARRAY[100], 'Check count fromid_taptest_table_p100000_p105000_p105100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p105000_p105200', ARRAY[100], 'Check count fromid_taptest_table_p100000_p105000_p105200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p105000_p105300', ARRAY[100], 'Check count fromid_taptest_table_p100000_p105000_p105300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p105000_p105400', ARRAY[100], 'Check count fromid_taptest_table_p100000_p105000_p105400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p105000_p105500', ARRAY[100], 'Check count fromid_taptest_table_p100000_p105000_p105500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p105000_p105600', ARRAY[100], 'Check count fromid_taptest_table_p100000_p105000_p105600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p105000_p105700', ARRAY[100], 'Check count fromid_taptest_table_p100000_p105000_p105700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p105000_p105800', ARRAY[100], 'Check count fromid_taptest_table_p100000_p105000_p105800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p105000_p105900', ARRAY[100], 'Check count fromid_taptest_table_p100000_p105000_p105900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p106000_p106000', ARRAY[100], 'Check count fromid_taptest_table_p100000_p106000_p106000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p106000_p106100', ARRAY[100], 'Check count fromid_taptest_table_p100000_p106000_p106100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p106000_p106200', ARRAY[100], 'Check count fromid_taptest_table_p100000_p106000_p106200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p106000_p106300', ARRAY[100], 'Check count fromid_taptest_table_p100000_p106000_p106300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p106000_p106400', ARRAY[100], 'Check count fromid_taptest_table_p100000_p106000_p106400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p106000_p106500', ARRAY[100], 'Check count fromid_taptest_table_p100000_p106000_p106500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p106000_p106600', ARRAY[100], 'Check count fromid_taptest_table_p100000_p106000_p106600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p106000_p106700', ARRAY[100], 'Check count fromid_taptest_table_p100000_p106000_p106700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p106000_p106800', ARRAY[100], 'Check count fromid_taptest_table_p100000_p106000_p106800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p106000_p106900', ARRAY[100], 'Check count fromid_taptest_table_p100000_p106000_p106900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p107000_p107000', ARRAY[100], 'Check count fromid_taptest_table_p100000_p107000_p107000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p107000_p107100', ARRAY[100], 'Check count fromid_taptest_table_p100000_p107000_p107100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p107000_p107200', ARRAY[100], 'Check count fromid_taptest_table_p100000_p107000_p107200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p107000_p107300', ARRAY[100], 'Check count fromid_taptest_table_p100000_p107000_p107300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p107000_p107400', ARRAY[100], 'Check count fromid_taptest_table_p100000_p107000_p107400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p107000_p107500', ARRAY[1], 'Check count fromid_taptest_table_p100000_p107000_p107500'); SELECT run_maintenance(); -- UNDO ALL THE THINGS!! SELECT throws_matching('SELECT undo_partition(''partman_test.id_taptest_table'', 20, p_keep_table := false)', 'Child table for this parent has child table\(s\) itself \(partman_test\.id_taptest_table_p0\)\. Run undo partitioning on this table or remove inheritance first to ensure all data is properly moved to parent.*', 'Check that undoing partitions is prevented if subpartitions still exist'); -- First the sub-sub parents SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p0_p0'', 20, p_keep_table := false)', ARRAY[999], 'Undo partitioning for parent table partman_test.id_taptest_table_p0_p0'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p0_p1000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p0_p1000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p0_p2000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p0_p2000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p0_p3000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p0_p3000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p0_p4000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p0_p4000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p0_p5000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p0_p5000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p0_p6000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p0_p6000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p0_p7000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p0_p7000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p0_p8000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p0_p8000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p0_p9000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p0_p9000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p10000_p10000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p10000_p10000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p10000_p11000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p10000_p11000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p10000_p12000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p10000_p12000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p10000_p13000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p10000_p13000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p10000_p14000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p10000_p14000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p10000_p15000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p10000_p15000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p10000_p16000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p10000_p16000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p10000_p17000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p10000_p17000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p10000_p18000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p10000_p18000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p10000_p19000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p10000_p19000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p20000_p20000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p20000_p20000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p20000_p21000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p20000_p21000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p20000_p22000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p20000_p22000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p20000_p23000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p20000_p23000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p20000_p24000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p20000_p24000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p20000_p25000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p20000_p25000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p20000_p26000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p20000_p26000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p20000_p27000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p20000_p27000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p20000_p28000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p20000_p28000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p20000_p29000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p20000_p29000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p30000_p30000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p30000_p30000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p30000_p31000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p30000_p31000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p30000_p32000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p30000_p32000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p30000_p33000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p30000_p33000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p30000_p34000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p30000_p34000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p30000_p35000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p30000_p35000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p30000_p36000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p30000_p36000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p30000_p37000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p30000_p37000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p30000_p38000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p30000_p38000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p30000_p39000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p30000_p39000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p40000_p40000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p40000_p40000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p40000_p41000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p40000_p41000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p40000_p42000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p40000_p42000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p40000_p43000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p40000_p43000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p40000_p44000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p40000_p44000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p40000_p45000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p40000_p45000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p40000_p46000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p40000_p46000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p40000_p47000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p40000_p47000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p40000_p48000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p40000_p48000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p40000_p49000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p40000_p49000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p50000_p50000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p50000_p50000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p50000_p51000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p50000_p51000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p50000_p52000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p50000_p52000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p50000_p53000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p50000_p53000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p50000_p54000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p50000_p54000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p50000_p55000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p50000_p55000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p50000_p56000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p50000_p56000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p50000_p57000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p50000_p57000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p50000_p58000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p50000_p58000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p50000_p59000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p50000_p59000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p60000_p60000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p60000_p60000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p60000_p61000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p60000_p61000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p60000_p62000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p60000_p62000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p60000_p63000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p60000_p63000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p60000_p64000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p60000_p64000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p60000_p65000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p60000_p65000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p60000_p66000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p60000_p66000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p60000_p67000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p60000_p67000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p60000_p68000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p60000_p68000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p60000_p69000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p60000_p69000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p70000_p70000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p70000_p70000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p70000_p71000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p70000_p71000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p70000_p72000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p70000_p72000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p70000_p73000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p70000_p73000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p70000_p74000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p70000_p74000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p70000_p75000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p70000_p75000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p70000_p76000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p70000_p76000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p70000_p77000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p70000_p77000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p70000_p78000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p70000_p78000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p70000_p79000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p70000_p79000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p80000_p80000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p80000_p80000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p80000_p81000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p80000_p81000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p80000_p82000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p80000_p82000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p80000_p83000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p80000_p83000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p80000_p84000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p80000_p84000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p80000_p85000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p80000_p85000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p80000_p86000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p80000_p86000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p80000_p87000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p80000_p87000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p80000_p88000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p80000_p88000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p80000_p89000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p80000_p89000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p90000_p90000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p90000_p90000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p90000_p91000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p90000_p91000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p90000_p92000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p90000_p92000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p90000_p93000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p90000_p93000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p90000_p94000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p90000_p94000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p90000_p95000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p90000_p95000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p90000_p96000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p90000_p96000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p90000_p97000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p90000_p97000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p90000_p98000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p90000_p98000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p90000_p99000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p90000_p99000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p100000_p100000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p100000_p100000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p100000_p101000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p100000_p101000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p100000_p102000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p100000_p102000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p100000_p103000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p100000_p103000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p100000_p104000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p100000_p104000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p100000_p105000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p100000_p105000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p100000_p106000'', 20, p_keep_table := false)', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p100000_p106000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p100000_p107000'', 20, p_keep_table := false)', ARRAY[501], 'Undo partitioning for parent table partman_test.id_taptest_table_p100000_p107000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p100000_p108000'', 20, p_keep_table := false)', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p100000_p108000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p100000_p109000'', 20, p_keep_table := false)', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p100000_p109000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p110000_p110000'', 20, p_keep_table := false)', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p110000_p110000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p120000_p120000'', 20, p_keep_table := false)', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p120000_p120000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p130000_p130000'', 20, p_keep_table := false)', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p130000_p130000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p140000_p140000'', 20, p_keep_table := false)', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p140000_p140000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p150000_p150000'', 20, p_keep_table := false)', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p150000_p150000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p160000_p160000'', 20, p_keep_table := false)', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p160000_p160000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p170000_p170000'', 20, p_keep_table := false)', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p170000_p170000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p180000_p180000'', 20, p_keep_table := false)', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p180000_p180000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p190000_p190000'', 20, p_keep_table := false)', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p190000_p190000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p200000_p200000'', 20, p_keep_table := false)', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p200000_p200000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p210000_p210000'', 20, p_keep_table := false)', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p210000_p210000'); -- Check that lowest child tables were removed (TODO need to clean this list up, it's got everything) SELECT hasnt_table('partman_test.id_taptest_table_p0_p0_p0', 'Check that table partman_test.id_taptest_table_p0_p0_p0 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p0_p100', 'Check that table partman_test.id_taptest_table_p0_p0_p100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p0_p200', 'Check that table partman_test.id_taptest_table_p0_p0_p200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p0_p300', 'Check that table partman_test.id_taptest_table_p0_p0_p300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p0_p400', 'Check that table partman_test.id_taptest_table_p0_p0_p400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p0_p500', 'Check that table partman_test.id_taptest_table_p0_p0_p500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p0_p600', 'Check that table partman_test.id_taptest_table_p0_p0_p600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p0_p700', 'Check that table partman_test.id_taptest_table_p0_p0_p700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p0_p800', 'Check that table partman_test.id_taptest_table_p0_p0_p800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p0_p900', 'Check that table partman_test.id_taptest_table_p0_p0_p900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p1000_p1000', 'Check that table partman_test.id_taptest_table_p0_p1000_p1000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p1000_p1100', 'Check that table partman_test.id_taptest_table_p0_p1000_p1100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p1000_p1200', 'Check that table partman_test.id_taptest_table_p0_p1000_p1200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p1000_p1300', 'Check that table partman_test.id_taptest_table_p0_p1000_p1300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p1000_p1400', 'Check that table partman_test.id_taptest_table_p0_p1000_p1400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p1000_p1500', 'Check that table partman_test.id_taptest_table_p0_p1000_p1500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p1000_p1600', 'Check that table partman_test.id_taptest_table_p0_p1000_p1600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p1000_p1700', 'Check that table partman_test.id_taptest_table_p0_p1000_p1700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p1000_p1800', 'Check that table partman_test.id_taptest_table_p0_p1000_p1800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p1000_p1900', 'Check that table partman_test.id_taptest_table_p0_p1000_p1900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p2000_p2000', 'Check that table partman_test.id_taptest_table_p0_p2000_p2000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p2000_p2100', 'Check that table partman_test.id_taptest_table_p0_p2000_p2100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p2000_p2200', 'Check that table partman_test.id_taptest_table_p0_p2000_p2200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p2000_p2300', 'Check that table partman_test.id_taptest_table_p0_p2000_p2300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p2000_p2400', 'Check that table partman_test.id_taptest_table_p0_p2000_p2400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p2000_p2500', 'Check that table partman_test.id_taptest_table_p0_p2000_p2500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p2000_p2600', 'Check that table partman_test.id_taptest_table_p0_p2000_p2600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p2000_p2700', 'Check that table partman_test.id_taptest_table_p0_p2000_p2700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p2000_p2800', 'Check that table partman_test.id_taptest_table_p0_p2000_p2800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p2000_p2900', 'Check that table partman_test.id_taptest_table_p0_p2000_p2900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p3000_p3000', 'Check that table partman_test.id_taptest_table_p0_p3000_p3000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p3000_p3100', 'Check that table partman_test.id_taptest_table_p0_p3000_p3100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p3000_p3200', 'Check that table partman_test.id_taptest_table_p0_p3000_p3200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p3000_p3300', 'Check that table partman_test.id_taptest_table_p0_p3000_p3300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p3000_p3400', 'Check that table partman_test.id_taptest_table_p0_p3000_p3400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p3000_p3500', 'Check that table partman_test.id_taptest_table_p0_p3000_p3500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p3000_p3600', 'Check that table partman_test.id_taptest_table_p0_p3000_p3600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p3000_p3700', 'Check that table partman_test.id_taptest_table_p0_p3000_p3700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p3000_p3800', 'Check that table partman_test.id_taptest_table_p0_p3000_p3800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p3000_p3900', 'Check that table partman_test.id_taptest_table_p0_p3000_p3900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p4000_p4000', 'Check that table partman_test.id_taptest_table_p0_p4000_p4000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p4000_p4100', 'Check that table partman_test.id_taptest_table_p0_p4000_p4100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p4000_p4200', 'Check that table partman_test.id_taptest_table_p0_p4000_p4200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p4000_p4300', 'Check that table partman_test.id_taptest_table_p0_p4000_p4300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p4000_p4400', 'Check that table partman_test.id_taptest_table_p0_p4000_p4400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p4000_p4500', 'Check that table partman_test.id_taptest_table_p0_p4000_p4500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p4000_p4600', 'Check that table partman_test.id_taptest_table_p0_p4000_p4600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p4000_p4700', 'Check that table partman_test.id_taptest_table_p0_p4000_p4700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p4000_p4800', 'Check that table partman_test.id_taptest_table_p0_p4000_p4800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p4000_p4900', 'Check that table partman_test.id_taptest_table_p0_p4000_p4900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p5000_p5000', 'Check that table partman_test.id_taptest_table_p0_p5000_p5000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p5000_p5100', 'Check that table partman_test.id_taptest_table_p0_p5000_p5100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p5000_p5200', 'Check that table partman_test.id_taptest_table_p0_p5000_p5200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p5000_p5300', 'Check that table partman_test.id_taptest_table_p0_p5000_p5300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p5000_p5400', 'Check that table partman_test.id_taptest_table_p0_p5000_p5400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p5000_p5500', 'Check that table partman_test.id_taptest_table_p0_p5000_p5500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p5000_p5600', 'Check that table partman_test.id_taptest_table_p0_p5000_p5600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p5000_p5700', 'Check that table partman_test.id_taptest_table_p0_p5000_p5700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p5000_p5800', 'Check that table partman_test.id_taptest_table_p0_p5000_p5800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p5000_p5900', 'Check that table partman_test.id_taptest_table_p0_p5000_p5900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p6000_p6000', 'Check that table partman_test.id_taptest_table_p0_p6000_p6000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p6000_p6100', 'Check that table partman_test.id_taptest_table_p0_p6000_p6100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p6000_p6200', 'Check that table partman_test.id_taptest_table_p0_p6000_p6200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p6000_p6300', 'Check that table partman_test.id_taptest_table_p0_p6000_p6300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p6000_p6400', 'Check that table partman_test.id_taptest_table_p0_p6000_p6400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p6000_p6500', 'Check that table partman_test.id_taptest_table_p0_p6000_p6500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p6000_p6600', 'Check that table partman_test.id_taptest_table_p0_p6000_p6600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p6000_p6700', 'Check that table partman_test.id_taptest_table_p0_p6000_p6700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p6000_p6800', 'Check that table partman_test.id_taptest_table_p0_p6000_p6800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p6000_p6900', 'Check that table partman_test.id_taptest_table_p0_p6000_p6900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p7000_p7000', 'Check that table partman_test.id_taptest_table_p0_p7000_p7000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p7000_p7100', 'Check that table partman_test.id_taptest_table_p0_p7000_p7100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p7000_p7200', 'Check that table partman_test.id_taptest_table_p0_p7000_p7200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p7000_p7300', 'Check that table partman_test.id_taptest_table_p0_p7000_p7300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p7000_p7400', 'Check that table partman_test.id_taptest_table_p0_p7000_p7400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p7000_p7500', 'Check that table partman_test.id_taptest_table_p0_p7000_p7500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p7000_p7600', 'Check that table partman_test.id_taptest_table_p0_p7000_p7600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p7000_p7700', 'Check that table partman_test.id_taptest_table_p0_p7000_p7700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p7000_p7800', 'Check that table partman_test.id_taptest_table_p0_p7000_p7800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p7000_p7900', 'Check that table partman_test.id_taptest_table_p0_p7000_p7900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p8000_p8000', 'Check that table partman_test.id_taptest_table_p0_p8000_p8000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p8000_p8100', 'Check that table partman_test.id_taptest_table_p0_p8000_p8100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p8000_p8200', 'Check that table partman_test.id_taptest_table_p0_p8000_p8200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p8000_p8300', 'Check that table partman_test.id_taptest_table_p0_p8000_p8300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p8000_p8400', 'Check that table partman_test.id_taptest_table_p0_p8000_p8400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p8000_p8500', 'Check that table partman_test.id_taptest_table_p0_p8000_p8500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p8000_p8600', 'Check that table partman_test.id_taptest_table_p0_p8000_p8600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p8000_p8700', 'Check that table partman_test.id_taptest_table_p0_p8000_p8700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p8000_p8800', 'Check that table partman_test.id_taptest_table_p0_p8000_p8800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p8000_p8900', 'Check that table partman_test.id_taptest_table_p0_p8000_p8900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p9000_p9000', 'Check that table partman_test.id_taptest_table_p0_p9000_p9000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p9000_p9100', 'Check that table partman_test.id_taptest_table_p0_p9000_p9100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p9000_p9200', 'Check that table partman_test.id_taptest_table_p0_p9000_p9200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p9000_p9300', 'Check that table partman_test.id_taptest_table_p0_p9000_p9300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p9000_p9400', 'Check that table partman_test.id_taptest_table_p0_p9000_p9400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p9000_p9500', 'Check that table partman_test.id_taptest_table_p0_p9000_p9500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p9000_p9600', 'Check that table partman_test.id_taptest_table_p0_p9000_p9600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p9000_p9700', 'Check that table partman_test.id_taptest_table_p0_p9000_p9700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p9000_p9800', 'Check that table partman_test.id_taptest_table_p0_p9000_p9800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p9000_p9900', 'Check that table partman_test.id_taptest_table_p0_p9000_p9900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p100000_p100000', 'Check that table partman_test.id_taptest_table_p100000_p100000_p100000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p100000_p100100', 'Check that table partman_test.id_taptest_table_p100000_p100000_p100100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p100000_p100200', 'Check that table partman_test.id_taptest_table_p100000_p100000_p100200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p100000_p100300', 'Check that table partman_test.id_taptest_table_p100000_p100000_p100300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p100000_p100400', 'Check that table partman_test.id_taptest_table_p100000_p100000_p100400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p100000_p100500', 'Check that table partman_test.id_taptest_table_p100000_p100000_p100500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p100000_p100600', 'Check that table partman_test.id_taptest_table_p100000_p100000_p100600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p100000_p100700', 'Check that table partman_test.id_taptest_table_p100000_p100000_p100700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p100000_p100800', 'Check that table partman_test.id_taptest_table_p100000_p100000_p100800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p100000_p100900', 'Check that table partman_test.id_taptest_table_p100000_p100000_p100900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p101000_p101000', 'Check that table partman_test.id_taptest_table_p100000_p101000_p101000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p101000_p101100', 'Check that table partman_test.id_taptest_table_p100000_p101000_p101100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p101000_p101200', 'Check that table partman_test.id_taptest_table_p100000_p101000_p101200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p101000_p101300', 'Check that table partman_test.id_taptest_table_p100000_p101000_p101300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p101000_p101400', 'Check that table partman_test.id_taptest_table_p100000_p101000_p101400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p101000_p101500', 'Check that table partman_test.id_taptest_table_p100000_p101000_p101500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p101000_p101600', 'Check that table partman_test.id_taptest_table_p100000_p101000_p101600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p101000_p101700', 'Check that table partman_test.id_taptest_table_p100000_p101000_p101700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p101000_p101800', 'Check that table partman_test.id_taptest_table_p100000_p101000_p101800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p101000_p101900', 'Check that table partman_test.id_taptest_table_p100000_p101000_p101900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p102000_p102000', 'Check that table partman_test.id_taptest_table_p100000_p102000_p102000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p102000_p102100', 'Check that table partman_test.id_taptest_table_p100000_p102000_p102100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p102000_p102200', 'Check that table partman_test.id_taptest_table_p100000_p102000_p102200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p102000_p102300', 'Check that table partman_test.id_taptest_table_p100000_p102000_p102300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p102000_p102400', 'Check that table partman_test.id_taptest_table_p100000_p102000_p102400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p102000_p102500', 'Check that table partman_test.id_taptest_table_p100000_p102000_p102500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p102000_p102600', 'Check that table partman_test.id_taptest_table_p100000_p102000_p102600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p102000_p102700', 'Check that table partman_test.id_taptest_table_p100000_p102000_p102700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p102000_p102800', 'Check that table partman_test.id_taptest_table_p100000_p102000_p102800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p102000_p102900', 'Check that table partman_test.id_taptest_table_p100000_p102000_p102900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p103000_p103000', 'Check that table partman_test.id_taptest_table_p100000_p103000_p103000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p103000_p103100', 'Check that table partman_test.id_taptest_table_p100000_p103000_p103100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p103000_p103200', 'Check that table partman_test.id_taptest_table_p100000_p103000_p103200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p103000_p103300', 'Check that table partman_test.id_taptest_table_p100000_p103000_p103300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p103000_p103400', 'Check that table partman_test.id_taptest_table_p100000_p103000_p103400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p103000_p103500', 'Check that table partman_test.id_taptest_table_p100000_p103000_p103500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p103000_p103600', 'Check that table partman_test.id_taptest_table_p100000_p103000_p103600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p103000_p103700', 'Check that table partman_test.id_taptest_table_p100000_p103000_p103700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p103000_p103800', 'Check that table partman_test.id_taptest_table_p100000_p103000_p103800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p103000_p103900', 'Check that table partman_test.id_taptest_table_p100000_p103000_p103900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p104000_p104000', 'Check that table partman_test.id_taptest_table_p100000_p104000_p104000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p104000_p104100', 'Check that table partman_test.id_taptest_table_p100000_p104000_p104100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p104000_p104200', 'Check that table partman_test.id_taptest_table_p100000_p104000_p104200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p104000_p104300', 'Check that table partman_test.id_taptest_table_p100000_p104000_p104300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p104000_p104400', 'Check that table partman_test.id_taptest_table_p100000_p104000_p104400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p104000_p104500', 'Check that table partman_test.id_taptest_table_p100000_p104000_p104500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p104000_p104600', 'Check that table partman_test.id_taptest_table_p100000_p104000_p104600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p104000_p104700', 'Check that table partman_test.id_taptest_table_p100000_p104000_p104700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p104000_p104800', 'Check that table partman_test.id_taptest_table_p100000_p104000_p104800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p104000_p104900', 'Check that table partman_test.id_taptest_table_p100000_p104000_p104900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p105000_p105000', 'Check that table partman_test.id_taptest_table_p100000_p105000_p105000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p105000_p105100', 'Check that table partman_test.id_taptest_table_p100000_p105000_p105100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p105000_p105200', 'Check that table partman_test.id_taptest_table_p100000_p105000_p105200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p105000_p105300', 'Check that table partman_test.id_taptest_table_p100000_p105000_p105300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p105000_p105400', 'Check that table partman_test.id_taptest_table_p100000_p105000_p105400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p105000_p105500', 'Check that table partman_test.id_taptest_table_p100000_p105000_p105500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p105000_p105600', 'Check that table partman_test.id_taptest_table_p100000_p105000_p105600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p105000_p105700', 'Check that table partman_test.id_taptest_table_p100000_p105000_p105700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p105000_p105800', 'Check that table partman_test.id_taptest_table_p100000_p105000_p105800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p105000_p105900', 'Check that table partman_test.id_taptest_table_p100000_p105000_p105900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p106000_p106000', 'Check that table partman_test.id_taptest_table_p100000_p106000_p106000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p106000_p106100', 'Check that table partman_test.id_taptest_table_p100000_p106000_p106100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p106000_p106200', 'Check that table partman_test.id_taptest_table_p100000_p106000_p106200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p106000_p106300', 'Check that table partman_test.id_taptest_table_p100000_p106000_p106300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p106000_p106400', 'Check that table partman_test.id_taptest_table_p100000_p106000_p106400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p106000_p106500', 'Check that table partman_test.id_taptest_table_p100000_p106000_p106500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p106000_p106600', 'Check that table partman_test.id_taptest_table_p100000_p106000_p106600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p106000_p106700', 'Check that table partman_test.id_taptest_table_p100000_p106000_p106700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p106000_p106800', 'Check that table partman_test.id_taptest_table_p100000_p106000_p106800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p106000_p106900', 'Check that table partman_test.id_taptest_table_p100000_p106000_p106900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p107000_p107000', 'Check that table partman_test.id_taptest_table_p100000_p107000_p107000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p107000_p107100', 'Check that table partman_test.id_taptest_table_p100000_p107000_p107100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p107000_p107200', 'Check that table partman_test.id_taptest_table_p100000_p107000_p107200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p107000_p107300', 'Check that table partman_test.id_taptest_table_p100000_p107000_p107300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p107000_p107400', 'Check that table partman_test.id_taptest_table_p100000_p107000_p107400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p107000_p107500', 'Check that table partman_test.id_taptest_table_p100000_p107000_p107500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p107000_p107600', 'Check that table partman_test.id_taptest_table_p100000_p107000_p107600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p107000_p107700', 'Check that table partman_test.id_taptest_table_p100000_p107000_p107700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p108000_p108000', 'Check that table partman_test.id_taptest_table_p100000_p108000_p108000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p109000_p109000', 'Check that table partman_test.id_taptest_table_p100000_p109000_p109000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p10000_p10000', 'Check that table partman_test.id_taptest_table_p10000_p10000_p10000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p10000_p10100', 'Check that table partman_test.id_taptest_table_p10000_p10000_p10100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p10000_p10200', 'Check that table partman_test.id_taptest_table_p10000_p10000_p10200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p10000_p10300', 'Check that table partman_test.id_taptest_table_p10000_p10000_p10300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p10000_p10400', 'Check that table partman_test.id_taptest_table_p10000_p10000_p10400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p10000_p10500', 'Check that table partman_test.id_taptest_table_p10000_p10000_p10500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p10000_p10600', 'Check that table partman_test.id_taptest_table_p10000_p10000_p10600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p10000_p10700', 'Check that table partman_test.id_taptest_table_p10000_p10000_p10700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p10000_p10800', 'Check that table partman_test.id_taptest_table_p10000_p10000_p10800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p10000_p10900', 'Check that table partman_test.id_taptest_table_p10000_p10000_p10900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p11000_p11000', 'Check that table partman_test.id_taptest_table_p10000_p11000_p11000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p11000_p11100', 'Check that table partman_test.id_taptest_table_p10000_p11000_p11100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p11000_p11200', 'Check that table partman_test.id_taptest_table_p10000_p11000_p11200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p11000_p11300', 'Check that table partman_test.id_taptest_table_p10000_p11000_p11300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p11000_p11400', 'Check that table partman_test.id_taptest_table_p10000_p11000_p11400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p11000_p11500', 'Check that table partman_test.id_taptest_table_p10000_p11000_p11500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p11000_p11600', 'Check that table partman_test.id_taptest_table_p10000_p11000_p11600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p11000_p11700', 'Check that table partman_test.id_taptest_table_p10000_p11000_p11700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p11000_p11800', 'Check that table partman_test.id_taptest_table_p10000_p11000_p11800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p11000_p11900', 'Check that table partman_test.id_taptest_table_p10000_p11000_p11900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p12000_p12000', 'Check that table partman_test.id_taptest_table_p10000_p12000_p12000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p12000_p12100', 'Check that table partman_test.id_taptest_table_p10000_p12000_p12100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p12000_p12200', 'Check that table partman_test.id_taptest_table_p10000_p12000_p12200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p12000_p12300', 'Check that table partman_test.id_taptest_table_p10000_p12000_p12300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p12000_p12400', 'Check that table partman_test.id_taptest_table_p10000_p12000_p12400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p12000_p12500', 'Check that table partman_test.id_taptest_table_p10000_p12000_p12500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p12000_p12600', 'Check that table partman_test.id_taptest_table_p10000_p12000_p12600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p12000_p12700', 'Check that table partman_test.id_taptest_table_p10000_p12000_p12700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p12000_p12800', 'Check that table partman_test.id_taptest_table_p10000_p12000_p12800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p12000_p12900', 'Check that table partman_test.id_taptest_table_p10000_p12000_p12900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p13000_p13000', 'Check that table partman_test.id_taptest_table_p10000_p13000_p13000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p13000_p13100', 'Check that table partman_test.id_taptest_table_p10000_p13000_p13100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p13000_p13200', 'Check that table partman_test.id_taptest_table_p10000_p13000_p13200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p13000_p13300', 'Check that table partman_test.id_taptest_table_p10000_p13000_p13300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p13000_p13400', 'Check that table partman_test.id_taptest_table_p10000_p13000_p13400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p13000_p13500', 'Check that table partman_test.id_taptest_table_p10000_p13000_p13500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p13000_p13600', 'Check that table partman_test.id_taptest_table_p10000_p13000_p13600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p13000_p13700', 'Check that table partman_test.id_taptest_table_p10000_p13000_p13700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p13000_p13800', 'Check that table partman_test.id_taptest_table_p10000_p13000_p13800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p13000_p13900', 'Check that table partman_test.id_taptest_table_p10000_p13000_p13900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p14000_p14000', 'Check that table partman_test.id_taptest_table_p10000_p14000_p14000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p14000_p14100', 'Check that table partman_test.id_taptest_table_p10000_p14000_p14100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p14000_p14200', 'Check that table partman_test.id_taptest_table_p10000_p14000_p14200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p14000_p14300', 'Check that table partman_test.id_taptest_table_p10000_p14000_p14300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p14000_p14400', 'Check that table partman_test.id_taptest_table_p10000_p14000_p14400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p14000_p14500', 'Check that table partman_test.id_taptest_table_p10000_p14000_p14500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p14000_p14600', 'Check that table partman_test.id_taptest_table_p10000_p14000_p14600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p14000_p14700', 'Check that table partman_test.id_taptest_table_p10000_p14000_p14700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p14000_p14800', 'Check that table partman_test.id_taptest_table_p10000_p14000_p14800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p14000_p14900', 'Check that table partman_test.id_taptest_table_p10000_p14000_p14900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p15000_p15000', 'Check that table partman_test.id_taptest_table_p10000_p15000_p15000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p15000_p15100', 'Check that table partman_test.id_taptest_table_p10000_p15000_p15100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p15000_p15200', 'Check that table partman_test.id_taptest_table_p10000_p15000_p15200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p15000_p15300', 'Check that table partman_test.id_taptest_table_p10000_p15000_p15300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p15000_p15400', 'Check that table partman_test.id_taptest_table_p10000_p15000_p15400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p15000_p15500', 'Check that table partman_test.id_taptest_table_p10000_p15000_p15500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p15000_p15600', 'Check that table partman_test.id_taptest_table_p10000_p15000_p15600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p15000_p15700', 'Check that table partman_test.id_taptest_table_p10000_p15000_p15700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p15000_p15800', 'Check that table partman_test.id_taptest_table_p10000_p15000_p15800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p15000_p15900', 'Check that table partman_test.id_taptest_table_p10000_p15000_p15900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p16000_p16000', 'Check that table partman_test.id_taptest_table_p10000_p16000_p16000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p16000_p16100', 'Check that table partman_test.id_taptest_table_p10000_p16000_p16100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p16000_p16200', 'Check that table partman_test.id_taptest_table_p10000_p16000_p16200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p16000_p16300', 'Check that table partman_test.id_taptest_table_p10000_p16000_p16300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p16000_p16400', 'Check that table partman_test.id_taptest_table_p10000_p16000_p16400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p16000_p16500', 'Check that table partman_test.id_taptest_table_p10000_p16000_p16500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p16000_p16600', 'Check that table partman_test.id_taptest_table_p10000_p16000_p16600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p16000_p16700', 'Check that table partman_test.id_taptest_table_p10000_p16000_p16700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p16000_p16800', 'Check that table partman_test.id_taptest_table_p10000_p16000_p16800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p16000_p16900', 'Check that table partman_test.id_taptest_table_p10000_p16000_p16900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p17000_p17000', 'Check that table partman_test.id_taptest_table_p10000_p17000_p17000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p17000_p17100', 'Check that table partman_test.id_taptest_table_p10000_p17000_p17100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p17000_p17200', 'Check that table partman_test.id_taptest_table_p10000_p17000_p17200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p17000_p17300', 'Check that table partman_test.id_taptest_table_p10000_p17000_p17300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p17000_p17400', 'Check that table partman_test.id_taptest_table_p10000_p17000_p17400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p17000_p17500', 'Check that table partman_test.id_taptest_table_p10000_p17000_p17500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p17000_p17600', 'Check that table partman_test.id_taptest_table_p10000_p17000_p17600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p17000_p17700', 'Check that table partman_test.id_taptest_table_p10000_p17000_p17700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p17000_p17800', 'Check that table partman_test.id_taptest_table_p10000_p17000_p17800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p17000_p17900', 'Check that table partman_test.id_taptest_table_p10000_p17000_p17900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p18000_p18000', 'Check that table partman_test.id_taptest_table_p10000_p18000_p18000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p18000_p18100', 'Check that table partman_test.id_taptest_table_p10000_p18000_p18100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p18000_p18200', 'Check that table partman_test.id_taptest_table_p10000_p18000_p18200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p18000_p18300', 'Check that table partman_test.id_taptest_table_p10000_p18000_p18300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p18000_p18400', 'Check that table partman_test.id_taptest_table_p10000_p18000_p18400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p18000_p18500', 'Check that table partman_test.id_taptest_table_p10000_p18000_p18500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p18000_p18600', 'Check that table partman_test.id_taptest_table_p10000_p18000_p18600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p18000_p18700', 'Check that table partman_test.id_taptest_table_p10000_p18000_p18700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p18000_p18800', 'Check that table partman_test.id_taptest_table_p10000_p18000_p18800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p18000_p18900', 'Check that table partman_test.id_taptest_table_p10000_p18000_p18900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p19000_p19000', 'Check that table partman_test.id_taptest_table_p10000_p19000_p19000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p19000_p19100', 'Check that table partman_test.id_taptest_table_p10000_p19000_p19100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p19000_p19200', 'Check that table partman_test.id_taptest_table_p10000_p19000_p19200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p19000_p19300', 'Check that table partman_test.id_taptest_table_p10000_p19000_p19300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p19000_p19400', 'Check that table partman_test.id_taptest_table_p10000_p19000_p19400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p19000_p19500', 'Check that table partman_test.id_taptest_table_p10000_p19000_p19500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p19000_p19600', 'Check that table partman_test.id_taptest_table_p10000_p19000_p19600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p19000_p19700', 'Check that table partman_test.id_taptest_table_p10000_p19000_p19700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p19000_p19800', 'Check that table partman_test.id_taptest_table_p10000_p19000_p19800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p19000_p19900', 'Check that table partman_test.id_taptest_table_p10000_p19000_p19900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p110000_p110000_p110000', 'Check that table partman_test.id_taptest_table_p110000_p110000_p110000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p120000_p120000_p120000', 'Check that table partman_test.id_taptest_table_p120000_p120000_p120000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p130000_p130000_p130000', 'Check that table partman_test.id_taptest_table_p130000_p130000_p130000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p140000_p140000_p140000', 'Check that table partman_test.id_taptest_table_p140000_p140000_p140000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p150000_p150000_p150000', 'Check that table partman_test.id_taptest_table_p150000_p150000_p150000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p160000_p160000_p160000', 'Check that table partman_test.id_taptest_table_p160000_p160000_p160000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p170000_p170000_p170000', 'Check that table partman_test.id_taptest_table_p170000_p170000_p170000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p180000_p180000_p180000', 'Check that table partman_test.id_taptest_table_p180000_p180000_p180000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p190000_p190000_p190000', 'Check that table partman_test.id_taptest_table_p190000_p190000_p190000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p200000_p200000_p200000', 'Check that table partman_test.id_taptest_table_p200000_p200000_p200000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p20000_p20000', 'Check that table partman_test.id_taptest_table_p20000_p20000_p20000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p20000_p20100', 'Check that table partman_test.id_taptest_table_p20000_p20000_p20100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p20000_p20200', 'Check that table partman_test.id_taptest_table_p20000_p20000_p20200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p20000_p20300', 'Check that table partman_test.id_taptest_table_p20000_p20000_p20300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p20000_p20400', 'Check that table partman_test.id_taptest_table_p20000_p20000_p20400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p20000_p20500', 'Check that table partman_test.id_taptest_table_p20000_p20000_p20500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p20000_p20600', 'Check that table partman_test.id_taptest_table_p20000_p20000_p20600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p20000_p20700', 'Check that table partman_test.id_taptest_table_p20000_p20000_p20700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p20000_p20800', 'Check that table partman_test.id_taptest_table_p20000_p20000_p20800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p20000_p20900', 'Check that table partman_test.id_taptest_table_p20000_p20000_p20900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p21000_p21000', 'Check that table partman_test.id_taptest_table_p20000_p21000_p21000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p21000_p21100', 'Check that table partman_test.id_taptest_table_p20000_p21000_p21100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p21000_p21200', 'Check that table partman_test.id_taptest_table_p20000_p21000_p21200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p21000_p21300', 'Check that table partman_test.id_taptest_table_p20000_p21000_p21300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p21000_p21400', 'Check that table partman_test.id_taptest_table_p20000_p21000_p21400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p21000_p21500', 'Check that table partman_test.id_taptest_table_p20000_p21000_p21500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p21000_p21600', 'Check that table partman_test.id_taptest_table_p20000_p21000_p21600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p21000_p21700', 'Check that table partman_test.id_taptest_table_p20000_p21000_p21700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p21000_p21800', 'Check that table partman_test.id_taptest_table_p20000_p21000_p21800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p21000_p21900', 'Check that table partman_test.id_taptest_table_p20000_p21000_p21900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p22000_p22000', 'Check that table partman_test.id_taptest_table_p20000_p22000_p22000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p22000_p22100', 'Check that table partman_test.id_taptest_table_p20000_p22000_p22100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p22000_p22200', 'Check that table partman_test.id_taptest_table_p20000_p22000_p22200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p22000_p22300', 'Check that table partman_test.id_taptest_table_p20000_p22000_p22300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p22000_p22400', 'Check that table partman_test.id_taptest_table_p20000_p22000_p22400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p22000_p22500', 'Check that table partman_test.id_taptest_table_p20000_p22000_p22500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p22000_p22600', 'Check that table partman_test.id_taptest_table_p20000_p22000_p22600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p22000_p22700', 'Check that table partman_test.id_taptest_table_p20000_p22000_p22700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p22000_p22800', 'Check that table partman_test.id_taptest_table_p20000_p22000_p22800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p22000_p22900', 'Check that table partman_test.id_taptest_table_p20000_p22000_p22900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p23000_p23000', 'Check that table partman_test.id_taptest_table_p20000_p23000_p23000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p23000_p23100', 'Check that table partman_test.id_taptest_table_p20000_p23000_p23100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p23000_p23200', 'Check that table partman_test.id_taptest_table_p20000_p23000_p23200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p23000_p23300', 'Check that table partman_test.id_taptest_table_p20000_p23000_p23300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p23000_p23400', 'Check that table partman_test.id_taptest_table_p20000_p23000_p23400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p23000_p23500', 'Check that table partman_test.id_taptest_table_p20000_p23000_p23500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p23000_p23600', 'Check that table partman_test.id_taptest_table_p20000_p23000_p23600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p23000_p23700', 'Check that table partman_test.id_taptest_table_p20000_p23000_p23700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p23000_p23800', 'Check that table partman_test.id_taptest_table_p20000_p23000_p23800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p23000_p23900', 'Check that table partman_test.id_taptest_table_p20000_p23000_p23900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p24000_p24000', 'Check that table partman_test.id_taptest_table_p20000_p24000_p24000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p24000_p24100', 'Check that table partman_test.id_taptest_table_p20000_p24000_p24100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p24000_p24200', 'Check that table partman_test.id_taptest_table_p20000_p24000_p24200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p24000_p24300', 'Check that table partman_test.id_taptest_table_p20000_p24000_p24300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p24000_p24400', 'Check that table partman_test.id_taptest_table_p20000_p24000_p24400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p24000_p24500', 'Check that table partman_test.id_taptest_table_p20000_p24000_p24500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p24000_p24600', 'Check that table partman_test.id_taptest_table_p20000_p24000_p24600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p24000_p24700', 'Check that table partman_test.id_taptest_table_p20000_p24000_p24700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p24000_p24800', 'Check that table partman_test.id_taptest_table_p20000_p24000_p24800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p24000_p24900', 'Check that table partman_test.id_taptest_table_p20000_p24000_p24900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p25000_p25000', 'Check that table partman_test.id_taptest_table_p20000_p25000_p25000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p25000_p25100', 'Check that table partman_test.id_taptest_table_p20000_p25000_p25100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p25000_p25200', 'Check that table partman_test.id_taptest_table_p20000_p25000_p25200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p25000_p25300', 'Check that table partman_test.id_taptest_table_p20000_p25000_p25300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p25000_p25400', 'Check that table partman_test.id_taptest_table_p20000_p25000_p25400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p25000_p25500', 'Check that table partman_test.id_taptest_table_p20000_p25000_p25500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p25000_p25600', 'Check that table partman_test.id_taptest_table_p20000_p25000_p25600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p25000_p25700', 'Check that table partman_test.id_taptest_table_p20000_p25000_p25700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p25000_p25800', 'Check that table partman_test.id_taptest_table_p20000_p25000_p25800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p25000_p25900', 'Check that table partman_test.id_taptest_table_p20000_p25000_p25900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p26000_p26000', 'Check that table partman_test.id_taptest_table_p20000_p26000_p26000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p26000_p26100', 'Check that table partman_test.id_taptest_table_p20000_p26000_p26100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p26000_p26200', 'Check that table partman_test.id_taptest_table_p20000_p26000_p26200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p26000_p26300', 'Check that table partman_test.id_taptest_table_p20000_p26000_p26300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p26000_p26400', 'Check that table partman_test.id_taptest_table_p20000_p26000_p26400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p26000_p26500', 'Check that table partman_test.id_taptest_table_p20000_p26000_p26500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p26000_p26600', 'Check that table partman_test.id_taptest_table_p20000_p26000_p26600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p26000_p26700', 'Check that table partman_test.id_taptest_table_p20000_p26000_p26700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p26000_p26800', 'Check that table partman_test.id_taptest_table_p20000_p26000_p26800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p26000_p26900', 'Check that table partman_test.id_taptest_table_p20000_p26000_p26900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p27000_p27000', 'Check that table partman_test.id_taptest_table_p20000_p27000_p27000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p27000_p27100', 'Check that table partman_test.id_taptest_table_p20000_p27000_p27100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p27000_p27200', 'Check that table partman_test.id_taptest_table_p20000_p27000_p27200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p27000_p27300', 'Check that table partman_test.id_taptest_table_p20000_p27000_p27300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p27000_p27400', 'Check that table partman_test.id_taptest_table_p20000_p27000_p27400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p27000_p27500', 'Check that table partman_test.id_taptest_table_p20000_p27000_p27500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p27000_p27600', 'Check that table partman_test.id_taptest_table_p20000_p27000_p27600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p27000_p27700', 'Check that table partman_test.id_taptest_table_p20000_p27000_p27700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p27000_p27800', 'Check that table partman_test.id_taptest_table_p20000_p27000_p27800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p27000_p27900', 'Check that table partman_test.id_taptest_table_p20000_p27000_p27900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p28000_p28000', 'Check that table partman_test.id_taptest_table_p20000_p28000_p28000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p28000_p28100', 'Check that table partman_test.id_taptest_table_p20000_p28000_p28100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p28000_p28200', 'Check that table partman_test.id_taptest_table_p20000_p28000_p28200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p28000_p28300', 'Check that table partman_test.id_taptest_table_p20000_p28000_p28300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p28000_p28400', 'Check that table partman_test.id_taptest_table_p20000_p28000_p28400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p28000_p28500', 'Check that table partman_test.id_taptest_table_p20000_p28000_p28500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p28000_p28600', 'Check that table partman_test.id_taptest_table_p20000_p28000_p28600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p28000_p28700', 'Check that table partman_test.id_taptest_table_p20000_p28000_p28700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p28000_p28800', 'Check that table partman_test.id_taptest_table_p20000_p28000_p28800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p28000_p28900', 'Check that table partman_test.id_taptest_table_p20000_p28000_p28900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p29000_p29000', 'Check that table partman_test.id_taptest_table_p20000_p29000_p29000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p29000_p29100', 'Check that table partman_test.id_taptest_table_p20000_p29000_p29100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p29000_p29200', 'Check that table partman_test.id_taptest_table_p20000_p29000_p29200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p29000_p29300', 'Check that table partman_test.id_taptest_table_p20000_p29000_p29300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p29000_p29400', 'Check that table partman_test.id_taptest_table_p20000_p29000_p29400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p29000_p29500', 'Check that table partman_test.id_taptest_table_p20000_p29000_p29500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p29000_p29600', 'Check that table partman_test.id_taptest_table_p20000_p29000_p29600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p29000_p29700', 'Check that table partman_test.id_taptest_table_p20000_p29000_p29700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p29000_p29800', 'Check that table partman_test.id_taptest_table_p20000_p29000_p29800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p29000_p29900', 'Check that table partman_test.id_taptest_table_p20000_p29000_p29900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p210000_p210000_p210000', 'Check that table partman_test.id_taptest_table_p210000_p210000_p210000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p30000_p30000', 'Check that table partman_test.id_taptest_table_p30000_p30000_p30000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p30000_p30100', 'Check that table partman_test.id_taptest_table_p30000_p30000_p30100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p30000_p30200', 'Check that table partman_test.id_taptest_table_p30000_p30000_p30200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p30000_p30300', 'Check that table partman_test.id_taptest_table_p30000_p30000_p30300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p30000_p30400', 'Check that table partman_test.id_taptest_table_p30000_p30000_p30400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p30000_p30500', 'Check that table partman_test.id_taptest_table_p30000_p30000_p30500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p30000_p30600', 'Check that table partman_test.id_taptest_table_p30000_p30000_p30600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p30000_p30700', 'Check that table partman_test.id_taptest_table_p30000_p30000_p30700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p30000_p30800', 'Check that table partman_test.id_taptest_table_p30000_p30000_p30800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p30000_p30900', 'Check that table partman_test.id_taptest_table_p30000_p30000_p30900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p31000_p31000', 'Check that table partman_test.id_taptest_table_p30000_p31000_p31000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p31000_p31100', 'Check that table partman_test.id_taptest_table_p30000_p31000_p31100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p31000_p31200', 'Check that table partman_test.id_taptest_table_p30000_p31000_p31200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p31000_p31300', 'Check that table partman_test.id_taptest_table_p30000_p31000_p31300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p31000_p31400', 'Check that table partman_test.id_taptest_table_p30000_p31000_p31400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p31000_p31500', 'Check that table partman_test.id_taptest_table_p30000_p31000_p31500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p31000_p31600', 'Check that table partman_test.id_taptest_table_p30000_p31000_p31600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p31000_p31700', 'Check that table partman_test.id_taptest_table_p30000_p31000_p31700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p31000_p31800', 'Check that table partman_test.id_taptest_table_p30000_p31000_p31800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p31000_p31900', 'Check that table partman_test.id_taptest_table_p30000_p31000_p31900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p32000_p32000', 'Check that table partman_test.id_taptest_table_p30000_p32000_p32000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p32000_p32100', 'Check that table partman_test.id_taptest_table_p30000_p32000_p32100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p32000_p32200', 'Check that table partman_test.id_taptest_table_p30000_p32000_p32200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p32000_p32300', 'Check that table partman_test.id_taptest_table_p30000_p32000_p32300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p32000_p32400', 'Check that table partman_test.id_taptest_table_p30000_p32000_p32400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p32000_p32500', 'Check that table partman_test.id_taptest_table_p30000_p32000_p32500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p32000_p32600', 'Check that table partman_test.id_taptest_table_p30000_p32000_p32600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p32000_p32700', 'Check that table partman_test.id_taptest_table_p30000_p32000_p32700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p32000_p32800', 'Check that table partman_test.id_taptest_table_p30000_p32000_p32800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p32000_p32900', 'Check that table partman_test.id_taptest_table_p30000_p32000_p32900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p33000_p33000', 'Check that table partman_test.id_taptest_table_p30000_p33000_p33000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p33000_p33100', 'Check that table partman_test.id_taptest_table_p30000_p33000_p33100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p33000_p33200', 'Check that table partman_test.id_taptest_table_p30000_p33000_p33200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p33000_p33300', 'Check that table partman_test.id_taptest_table_p30000_p33000_p33300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p33000_p33400', 'Check that table partman_test.id_taptest_table_p30000_p33000_p33400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p33000_p33500', 'Check that table partman_test.id_taptest_table_p30000_p33000_p33500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p33000_p33600', 'Check that table partman_test.id_taptest_table_p30000_p33000_p33600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p33000_p33700', 'Check that table partman_test.id_taptest_table_p30000_p33000_p33700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p33000_p33800', 'Check that table partman_test.id_taptest_table_p30000_p33000_p33800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p33000_p33900', 'Check that table partman_test.id_taptest_table_p30000_p33000_p33900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p34000_p34000', 'Check that table partman_test.id_taptest_table_p30000_p34000_p34000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p34000_p34100', 'Check that table partman_test.id_taptest_table_p30000_p34000_p34100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p34000_p34200', 'Check that table partman_test.id_taptest_table_p30000_p34000_p34200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p34000_p34300', 'Check that table partman_test.id_taptest_table_p30000_p34000_p34300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p34000_p34400', 'Check that table partman_test.id_taptest_table_p30000_p34000_p34400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p34000_p34500', 'Check that table partman_test.id_taptest_table_p30000_p34000_p34500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p34000_p34600', 'Check that table partman_test.id_taptest_table_p30000_p34000_p34600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p34000_p34700', 'Check that table partman_test.id_taptest_table_p30000_p34000_p34700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p34000_p34800', 'Check that table partman_test.id_taptest_table_p30000_p34000_p34800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p34000_p34900', 'Check that table partman_test.id_taptest_table_p30000_p34000_p34900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p35000_p35000', 'Check that table partman_test.id_taptest_table_p30000_p35000_p35000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p35000_p35100', 'Check that table partman_test.id_taptest_table_p30000_p35000_p35100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p35000_p35200', 'Check that table partman_test.id_taptest_table_p30000_p35000_p35200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p35000_p35300', 'Check that table partman_test.id_taptest_table_p30000_p35000_p35300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p35000_p35400', 'Check that table partman_test.id_taptest_table_p30000_p35000_p35400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p35000_p35500', 'Check that table partman_test.id_taptest_table_p30000_p35000_p35500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p35000_p35600', 'Check that table partman_test.id_taptest_table_p30000_p35000_p35600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p35000_p35700', 'Check that table partman_test.id_taptest_table_p30000_p35000_p35700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p35000_p35800', 'Check that table partman_test.id_taptest_table_p30000_p35000_p35800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p35000_p35900', 'Check that table partman_test.id_taptest_table_p30000_p35000_p35900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p36000_p36000', 'Check that table partman_test.id_taptest_table_p30000_p36000_p36000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p36000_p36100', 'Check that table partman_test.id_taptest_table_p30000_p36000_p36100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p36000_p36200', 'Check that table partman_test.id_taptest_table_p30000_p36000_p36200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p36000_p36300', 'Check that table partman_test.id_taptest_table_p30000_p36000_p36300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p36000_p36400', 'Check that table partman_test.id_taptest_table_p30000_p36000_p36400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p36000_p36500', 'Check that table partman_test.id_taptest_table_p30000_p36000_p36500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p36000_p36600', 'Check that table partman_test.id_taptest_table_p30000_p36000_p36600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p36000_p36700', 'Check that table partman_test.id_taptest_table_p30000_p36000_p36700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p36000_p36800', 'Check that table partman_test.id_taptest_table_p30000_p36000_p36800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p36000_p36900', 'Check that table partman_test.id_taptest_table_p30000_p36000_p36900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p37000_p37000', 'Check that table partman_test.id_taptest_table_p30000_p37000_p37000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p37000_p37100', 'Check that table partman_test.id_taptest_table_p30000_p37000_p37100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p37000_p37200', 'Check that table partman_test.id_taptest_table_p30000_p37000_p37200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p37000_p37300', 'Check that table partman_test.id_taptest_table_p30000_p37000_p37300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p37000_p37400', 'Check that table partman_test.id_taptest_table_p30000_p37000_p37400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p37000_p37500', 'Check that table partman_test.id_taptest_table_p30000_p37000_p37500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p37000_p37600', 'Check that table partman_test.id_taptest_table_p30000_p37000_p37600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p37000_p37700', 'Check that table partman_test.id_taptest_table_p30000_p37000_p37700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p37000_p37800', 'Check that table partman_test.id_taptest_table_p30000_p37000_p37800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p37000_p37900', 'Check that table partman_test.id_taptest_table_p30000_p37000_p37900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p38000_p38000', 'Check that table partman_test.id_taptest_table_p30000_p38000_p38000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p38000_p38100', 'Check that table partman_test.id_taptest_table_p30000_p38000_p38100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p38000_p38200', 'Check that table partman_test.id_taptest_table_p30000_p38000_p38200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p38000_p38300', 'Check that table partman_test.id_taptest_table_p30000_p38000_p38300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p38000_p38400', 'Check that table partman_test.id_taptest_table_p30000_p38000_p38400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p38000_p38500', 'Check that table partman_test.id_taptest_table_p30000_p38000_p38500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p38000_p38600', 'Check that table partman_test.id_taptest_table_p30000_p38000_p38600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p38000_p38700', 'Check that table partman_test.id_taptest_table_p30000_p38000_p38700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p38000_p38800', 'Check that table partman_test.id_taptest_table_p30000_p38000_p38800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p38000_p38900', 'Check that table partman_test.id_taptest_table_p30000_p38000_p38900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p39000_p39000', 'Check that table partman_test.id_taptest_table_p30000_p39000_p39000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p39000_p39100', 'Check that table partman_test.id_taptest_table_p30000_p39000_p39100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p39000_p39200', 'Check that table partman_test.id_taptest_table_p30000_p39000_p39200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p39000_p39300', 'Check that table partman_test.id_taptest_table_p30000_p39000_p39300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p39000_p39400', 'Check that table partman_test.id_taptest_table_p30000_p39000_p39400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p39000_p39500', 'Check that table partman_test.id_taptest_table_p30000_p39000_p39500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p39000_p39600', 'Check that table partman_test.id_taptest_table_p30000_p39000_p39600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p39000_p39700', 'Check that table partman_test.id_taptest_table_p30000_p39000_p39700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p39000_p39800', 'Check that table partman_test.id_taptest_table_p30000_p39000_p39800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p39000_p39900', 'Check that table partman_test.id_taptest_table_p30000_p39000_p39900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p40000_p40000', 'Check that table partman_test.id_taptest_table_p40000_p40000_p40000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p40000_p40100', 'Check that table partman_test.id_taptest_table_p40000_p40000_p40100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p40000_p40200', 'Check that table partman_test.id_taptest_table_p40000_p40000_p40200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p40000_p40300', 'Check that table partman_test.id_taptest_table_p40000_p40000_p40300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p40000_p40400', 'Check that table partman_test.id_taptest_table_p40000_p40000_p40400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p40000_p40500', 'Check that table partman_test.id_taptest_table_p40000_p40000_p40500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p40000_p40600', 'Check that table partman_test.id_taptest_table_p40000_p40000_p40600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p40000_p40700', 'Check that table partman_test.id_taptest_table_p40000_p40000_p40700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p40000_p40800', 'Check that table partman_test.id_taptest_table_p40000_p40000_p40800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p40000_p40900', 'Check that table partman_test.id_taptest_table_p40000_p40000_p40900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p41000_p41000', 'Check that table partman_test.id_taptest_table_p40000_p41000_p41000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p41000_p41100', 'Check that table partman_test.id_taptest_table_p40000_p41000_p41100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p41000_p41200', 'Check that table partman_test.id_taptest_table_p40000_p41000_p41200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p41000_p41300', 'Check that table partman_test.id_taptest_table_p40000_p41000_p41300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p41000_p41400', 'Check that table partman_test.id_taptest_table_p40000_p41000_p41400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p41000_p41500', 'Check that table partman_test.id_taptest_table_p40000_p41000_p41500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p41000_p41600', 'Check that table partman_test.id_taptest_table_p40000_p41000_p41600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p41000_p41700', 'Check that table partman_test.id_taptest_table_p40000_p41000_p41700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p41000_p41800', 'Check that table partman_test.id_taptest_table_p40000_p41000_p41800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p41000_p41900', 'Check that table partman_test.id_taptest_table_p40000_p41000_p41900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p42000_p42000', 'Check that table partman_test.id_taptest_table_p40000_p42000_p42000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p42000_p42100', 'Check that table partman_test.id_taptest_table_p40000_p42000_p42100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p42000_p42200', 'Check that table partman_test.id_taptest_table_p40000_p42000_p42200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p42000_p42300', 'Check that table partman_test.id_taptest_table_p40000_p42000_p42300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p42000_p42400', 'Check that table partman_test.id_taptest_table_p40000_p42000_p42400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p42000_p42500', 'Check that table partman_test.id_taptest_table_p40000_p42000_p42500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p42000_p42600', 'Check that table partman_test.id_taptest_table_p40000_p42000_p42600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p42000_p42700', 'Check that table partman_test.id_taptest_table_p40000_p42000_p42700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p42000_p42800', 'Check that table partman_test.id_taptest_table_p40000_p42000_p42800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p42000_p42900', 'Check that table partman_test.id_taptest_table_p40000_p42000_p42900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p43000_p43000', 'Check that table partman_test.id_taptest_table_p40000_p43000_p43000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p43000_p43100', 'Check that table partman_test.id_taptest_table_p40000_p43000_p43100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p43000_p43200', 'Check that table partman_test.id_taptest_table_p40000_p43000_p43200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p43000_p43300', 'Check that table partman_test.id_taptest_table_p40000_p43000_p43300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p43000_p43400', 'Check that table partman_test.id_taptest_table_p40000_p43000_p43400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p43000_p43500', 'Check that table partman_test.id_taptest_table_p40000_p43000_p43500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p43000_p43600', 'Check that table partman_test.id_taptest_table_p40000_p43000_p43600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p43000_p43700', 'Check that table partman_test.id_taptest_table_p40000_p43000_p43700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p43000_p43800', 'Check that table partman_test.id_taptest_table_p40000_p43000_p43800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p43000_p43900', 'Check that table partman_test.id_taptest_table_p40000_p43000_p43900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p44000_p44000', 'Check that table partman_test.id_taptest_table_p40000_p44000_p44000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p44000_p44100', 'Check that table partman_test.id_taptest_table_p40000_p44000_p44100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p44000_p44200', 'Check that table partman_test.id_taptest_table_p40000_p44000_p44200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p44000_p44300', 'Check that table partman_test.id_taptest_table_p40000_p44000_p44300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p44000_p44400', 'Check that table partman_test.id_taptest_table_p40000_p44000_p44400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p44000_p44500', 'Check that table partman_test.id_taptest_table_p40000_p44000_p44500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p44000_p44600', 'Check that table partman_test.id_taptest_table_p40000_p44000_p44600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p44000_p44700', 'Check that table partman_test.id_taptest_table_p40000_p44000_p44700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p44000_p44800', 'Check that table partman_test.id_taptest_table_p40000_p44000_p44800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p44000_p44900', 'Check that table partman_test.id_taptest_table_p40000_p44000_p44900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p45000_p45000', 'Check that table partman_test.id_taptest_table_p40000_p45000_p45000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p45000_p45100', 'Check that table partman_test.id_taptest_table_p40000_p45000_p45100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p45000_p45200', 'Check that table partman_test.id_taptest_table_p40000_p45000_p45200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p45000_p45300', 'Check that table partman_test.id_taptest_table_p40000_p45000_p45300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p45000_p45400', 'Check that table partman_test.id_taptest_table_p40000_p45000_p45400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p45000_p45500', 'Check that table partman_test.id_taptest_table_p40000_p45000_p45500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p45000_p45600', 'Check that table partman_test.id_taptest_table_p40000_p45000_p45600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p45000_p45700', 'Check that table partman_test.id_taptest_table_p40000_p45000_p45700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p45000_p45800', 'Check that table partman_test.id_taptest_table_p40000_p45000_p45800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p45000_p45900', 'Check that table partman_test.id_taptest_table_p40000_p45000_p45900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p46000_p46000', 'Check that table partman_test.id_taptest_table_p40000_p46000_p46000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p46000_p46100', 'Check that table partman_test.id_taptest_table_p40000_p46000_p46100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p46000_p46200', 'Check that table partman_test.id_taptest_table_p40000_p46000_p46200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p46000_p46300', 'Check that table partman_test.id_taptest_table_p40000_p46000_p46300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p46000_p46400', 'Check that table partman_test.id_taptest_table_p40000_p46000_p46400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p46000_p46500', 'Check that table partman_test.id_taptest_table_p40000_p46000_p46500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p46000_p46600', 'Check that table partman_test.id_taptest_table_p40000_p46000_p46600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p46000_p46700', 'Check that table partman_test.id_taptest_table_p40000_p46000_p46700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p46000_p46800', 'Check that table partman_test.id_taptest_table_p40000_p46000_p46800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p46000_p46900', 'Check that table partman_test.id_taptest_table_p40000_p46000_p46900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p47000_p47000', 'Check that table partman_test.id_taptest_table_p40000_p47000_p47000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p47000_p47100', 'Check that table partman_test.id_taptest_table_p40000_p47000_p47100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p47000_p47200', 'Check that table partman_test.id_taptest_table_p40000_p47000_p47200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p47000_p47300', 'Check that table partman_test.id_taptest_table_p40000_p47000_p47300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p47000_p47400', 'Check that table partman_test.id_taptest_table_p40000_p47000_p47400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p47000_p47500', 'Check that table partman_test.id_taptest_table_p40000_p47000_p47500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p47000_p47600', 'Check that table partman_test.id_taptest_table_p40000_p47000_p47600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p47000_p47700', 'Check that table partman_test.id_taptest_table_p40000_p47000_p47700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p47000_p47800', 'Check that table partman_test.id_taptest_table_p40000_p47000_p47800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p47000_p47900', 'Check that table partman_test.id_taptest_table_p40000_p47000_p47900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p48000_p48000', 'Check that table partman_test.id_taptest_table_p40000_p48000_p48000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p48000_p48100', 'Check that table partman_test.id_taptest_table_p40000_p48000_p48100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p48000_p48200', 'Check that table partman_test.id_taptest_table_p40000_p48000_p48200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p48000_p48300', 'Check that table partman_test.id_taptest_table_p40000_p48000_p48300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p48000_p48400', 'Check that table partman_test.id_taptest_table_p40000_p48000_p48400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p48000_p48500', 'Check that table partman_test.id_taptest_table_p40000_p48000_p48500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p48000_p48600', 'Check that table partman_test.id_taptest_table_p40000_p48000_p48600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p48000_p48700', 'Check that table partman_test.id_taptest_table_p40000_p48000_p48700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p48000_p48800', 'Check that table partman_test.id_taptest_table_p40000_p48000_p48800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p48000_p48900', 'Check that table partman_test.id_taptest_table_p40000_p48000_p48900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p49000_p49000', 'Check that table partman_test.id_taptest_table_p40000_p49000_p49000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p49000_p49100', 'Check that table partman_test.id_taptest_table_p40000_p49000_p49100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p49000_p49200', 'Check that table partman_test.id_taptest_table_p40000_p49000_p49200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p49000_p49300', 'Check that table partman_test.id_taptest_table_p40000_p49000_p49300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p49000_p49400', 'Check that table partman_test.id_taptest_table_p40000_p49000_p49400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p49000_p49500', 'Check that table partman_test.id_taptest_table_p40000_p49000_p49500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p49000_p49600', 'Check that table partman_test.id_taptest_table_p40000_p49000_p49600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p49000_p49700', 'Check that table partman_test.id_taptest_table_p40000_p49000_p49700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p49000_p49800', 'Check that table partman_test.id_taptest_table_p40000_p49000_p49800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p49000_p49900', 'Check that table partman_test.id_taptest_table_p40000_p49000_p49900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p50000_p50000', 'Check that table partman_test.id_taptest_table_p50000_p50000_p50000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p50000_p50100', 'Check that table partman_test.id_taptest_table_p50000_p50000_p50100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p50000_p50200', 'Check that table partman_test.id_taptest_table_p50000_p50000_p50200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p50000_p50300', 'Check that table partman_test.id_taptest_table_p50000_p50000_p50300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p50000_p50400', 'Check that table partman_test.id_taptest_table_p50000_p50000_p50400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p50000_p50500', 'Check that table partman_test.id_taptest_table_p50000_p50000_p50500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p50000_p50600', 'Check that table partman_test.id_taptest_table_p50000_p50000_p50600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p50000_p50700', 'Check that table partman_test.id_taptest_table_p50000_p50000_p50700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p50000_p50800', 'Check that table partman_test.id_taptest_table_p50000_p50000_p50800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p50000_p50900', 'Check that table partman_test.id_taptest_table_p50000_p50000_p50900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p51000_p51000', 'Check that table partman_test.id_taptest_table_p50000_p51000_p51000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p51000_p51100', 'Check that table partman_test.id_taptest_table_p50000_p51000_p51100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p51000_p51200', 'Check that table partman_test.id_taptest_table_p50000_p51000_p51200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p51000_p51300', 'Check that table partman_test.id_taptest_table_p50000_p51000_p51300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p51000_p51400', 'Check that table partman_test.id_taptest_table_p50000_p51000_p51400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p51000_p51500', 'Check that table partman_test.id_taptest_table_p50000_p51000_p51500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p51000_p51600', 'Check that table partman_test.id_taptest_table_p50000_p51000_p51600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p51000_p51700', 'Check that table partman_test.id_taptest_table_p50000_p51000_p51700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p51000_p51800', 'Check that table partman_test.id_taptest_table_p50000_p51000_p51800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p51000_p51900', 'Check that table partman_test.id_taptest_table_p50000_p51000_p51900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p52000_p52000', 'Check that table partman_test.id_taptest_table_p50000_p52000_p52000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p52000_p52100', 'Check that table partman_test.id_taptest_table_p50000_p52000_p52100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p52000_p52200', 'Check that table partman_test.id_taptest_table_p50000_p52000_p52200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p52000_p52300', 'Check that table partman_test.id_taptest_table_p50000_p52000_p52300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p52000_p52400', 'Check that table partman_test.id_taptest_table_p50000_p52000_p52400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p52000_p52500', 'Check that table partman_test.id_taptest_table_p50000_p52000_p52500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p52000_p52600', 'Check that table partman_test.id_taptest_table_p50000_p52000_p52600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p52000_p52700', 'Check that table partman_test.id_taptest_table_p50000_p52000_p52700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p52000_p52800', 'Check that table partman_test.id_taptest_table_p50000_p52000_p52800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p52000_p52900', 'Check that table partman_test.id_taptest_table_p50000_p52000_p52900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p53000_p53000', 'Check that table partman_test.id_taptest_table_p50000_p53000_p53000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p53000_p53100', 'Check that table partman_test.id_taptest_table_p50000_p53000_p53100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p53000_p53200', 'Check that table partman_test.id_taptest_table_p50000_p53000_p53200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p53000_p53300', 'Check that table partman_test.id_taptest_table_p50000_p53000_p53300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p53000_p53400', 'Check that table partman_test.id_taptest_table_p50000_p53000_p53400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p53000_p53500', 'Check that table partman_test.id_taptest_table_p50000_p53000_p53500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p53000_p53600', 'Check that table partman_test.id_taptest_table_p50000_p53000_p53600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p53000_p53700', 'Check that table partman_test.id_taptest_table_p50000_p53000_p53700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p53000_p53800', 'Check that table partman_test.id_taptest_table_p50000_p53000_p53800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p53000_p53900', 'Check that table partman_test.id_taptest_table_p50000_p53000_p53900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p54000_p54000', 'Check that table partman_test.id_taptest_table_p50000_p54000_p54000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p54000_p54100', 'Check that table partman_test.id_taptest_table_p50000_p54000_p54100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p54000_p54200', 'Check that table partman_test.id_taptest_table_p50000_p54000_p54200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p54000_p54300', 'Check that table partman_test.id_taptest_table_p50000_p54000_p54300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p54000_p54400', 'Check that table partman_test.id_taptest_table_p50000_p54000_p54400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p54000_p54500', 'Check that table partman_test.id_taptest_table_p50000_p54000_p54500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p54000_p54600', 'Check that table partman_test.id_taptest_table_p50000_p54000_p54600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p54000_p54700', 'Check that table partman_test.id_taptest_table_p50000_p54000_p54700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p54000_p54800', 'Check that table partman_test.id_taptest_table_p50000_p54000_p54800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p54000_p54900', 'Check that table partman_test.id_taptest_table_p50000_p54000_p54900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p55000_p55000', 'Check that table partman_test.id_taptest_table_p50000_p55000_p55000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p55000_p55100', 'Check that table partman_test.id_taptest_table_p50000_p55000_p55100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p55000_p55200', 'Check that table partman_test.id_taptest_table_p50000_p55000_p55200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p55000_p55300', 'Check that table partman_test.id_taptest_table_p50000_p55000_p55300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p55000_p55400', 'Check that table partman_test.id_taptest_table_p50000_p55000_p55400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p55000_p55500', 'Check that table partman_test.id_taptest_table_p50000_p55000_p55500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p55000_p55600', 'Check that table partman_test.id_taptest_table_p50000_p55000_p55600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p55000_p55700', 'Check that table partman_test.id_taptest_table_p50000_p55000_p55700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p55000_p55800', 'Check that table partman_test.id_taptest_table_p50000_p55000_p55800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p55000_p55900', 'Check that table partman_test.id_taptest_table_p50000_p55000_p55900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p56000_p56000', 'Check that table partman_test.id_taptest_table_p50000_p56000_p56000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p56000_p56100', 'Check that table partman_test.id_taptest_table_p50000_p56000_p56100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p56000_p56200', 'Check that table partman_test.id_taptest_table_p50000_p56000_p56200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p56000_p56300', 'Check that table partman_test.id_taptest_table_p50000_p56000_p56300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p56000_p56400', 'Check that table partman_test.id_taptest_table_p50000_p56000_p56400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p56000_p56500', 'Check that table partman_test.id_taptest_table_p50000_p56000_p56500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p56000_p56600', 'Check that table partman_test.id_taptest_table_p50000_p56000_p56600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p56000_p56700', 'Check that table partman_test.id_taptest_table_p50000_p56000_p56700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p56000_p56800', 'Check that table partman_test.id_taptest_table_p50000_p56000_p56800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p56000_p56900', 'Check that table partman_test.id_taptest_table_p50000_p56000_p56900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p57000_p57000', 'Check that table partman_test.id_taptest_table_p50000_p57000_p57000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p57000_p57100', 'Check that table partman_test.id_taptest_table_p50000_p57000_p57100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p57000_p57200', 'Check that table partman_test.id_taptest_table_p50000_p57000_p57200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p57000_p57300', 'Check that table partman_test.id_taptest_table_p50000_p57000_p57300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p57000_p57400', 'Check that table partman_test.id_taptest_table_p50000_p57000_p57400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p57000_p57500', 'Check that table partman_test.id_taptest_table_p50000_p57000_p57500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p57000_p57600', 'Check that table partman_test.id_taptest_table_p50000_p57000_p57600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p57000_p57700', 'Check that table partman_test.id_taptest_table_p50000_p57000_p57700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p57000_p57800', 'Check that table partman_test.id_taptest_table_p50000_p57000_p57800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p57000_p57900', 'Check that table partman_test.id_taptest_table_p50000_p57000_p57900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p58000_p58000', 'Check that table partman_test.id_taptest_table_p50000_p58000_p58000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p58000_p58100', 'Check that table partman_test.id_taptest_table_p50000_p58000_p58100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p58000_p58200', 'Check that table partman_test.id_taptest_table_p50000_p58000_p58200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p58000_p58300', 'Check that table partman_test.id_taptest_table_p50000_p58000_p58300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p58000_p58400', 'Check that table partman_test.id_taptest_table_p50000_p58000_p58400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p58000_p58500', 'Check that table partman_test.id_taptest_table_p50000_p58000_p58500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p58000_p58600', 'Check that table partman_test.id_taptest_table_p50000_p58000_p58600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p58000_p58700', 'Check that table partman_test.id_taptest_table_p50000_p58000_p58700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p58000_p58800', 'Check that table partman_test.id_taptest_table_p50000_p58000_p58800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p58000_p58900', 'Check that table partman_test.id_taptest_table_p50000_p58000_p58900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p59000_p59000', 'Check that table partman_test.id_taptest_table_p50000_p59000_p59000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p59000_p59100', 'Check that table partman_test.id_taptest_table_p50000_p59000_p59100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p59000_p59200', 'Check that table partman_test.id_taptest_table_p50000_p59000_p59200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p59000_p59300', 'Check that table partman_test.id_taptest_table_p50000_p59000_p59300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p59000_p59400', 'Check that table partman_test.id_taptest_table_p50000_p59000_p59400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p59000_p59500', 'Check that table partman_test.id_taptest_table_p50000_p59000_p59500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p59000_p59600', 'Check that table partman_test.id_taptest_table_p50000_p59000_p59600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p59000_p59700', 'Check that table partman_test.id_taptest_table_p50000_p59000_p59700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p59000_p59800', 'Check that table partman_test.id_taptest_table_p50000_p59000_p59800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p59000_p59900', 'Check that table partman_test.id_taptest_table_p50000_p59000_p59900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p60000_p60000', 'Check that table partman_test.id_taptest_table_p60000_p60000_p60000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p60000_p60100', 'Check that table partman_test.id_taptest_table_p60000_p60000_p60100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p60000_p60200', 'Check that table partman_test.id_taptest_table_p60000_p60000_p60200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p60000_p60300', 'Check that table partman_test.id_taptest_table_p60000_p60000_p60300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p60000_p60400', 'Check that table partman_test.id_taptest_table_p60000_p60000_p60400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p60000_p60500', 'Check that table partman_test.id_taptest_table_p60000_p60000_p60500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p60000_p60600', 'Check that table partman_test.id_taptest_table_p60000_p60000_p60600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p60000_p60700', 'Check that table partman_test.id_taptest_table_p60000_p60000_p60700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p60000_p60800', 'Check that table partman_test.id_taptest_table_p60000_p60000_p60800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p60000_p60900', 'Check that table partman_test.id_taptest_table_p60000_p60000_p60900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p61000_p61000', 'Check that table partman_test.id_taptest_table_p60000_p61000_p61000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p61000_p61100', 'Check that table partman_test.id_taptest_table_p60000_p61000_p61100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p61000_p61200', 'Check that table partman_test.id_taptest_table_p60000_p61000_p61200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p61000_p61300', 'Check that table partman_test.id_taptest_table_p60000_p61000_p61300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p61000_p61400', 'Check that table partman_test.id_taptest_table_p60000_p61000_p61400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p61000_p61500', 'Check that table partman_test.id_taptest_table_p60000_p61000_p61500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p61000_p61600', 'Check that table partman_test.id_taptest_table_p60000_p61000_p61600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p61000_p61700', 'Check that table partman_test.id_taptest_table_p60000_p61000_p61700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p61000_p61800', 'Check that table partman_test.id_taptest_table_p60000_p61000_p61800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p61000_p61900', 'Check that table partman_test.id_taptest_table_p60000_p61000_p61900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p62000_p62000', 'Check that table partman_test.id_taptest_table_p60000_p62000_p62000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p62000_p62100', 'Check that table partman_test.id_taptest_table_p60000_p62000_p62100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p62000_p62200', 'Check that table partman_test.id_taptest_table_p60000_p62000_p62200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p62000_p62300', 'Check that table partman_test.id_taptest_table_p60000_p62000_p62300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p62000_p62400', 'Check that table partman_test.id_taptest_table_p60000_p62000_p62400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p62000_p62500', 'Check that table partman_test.id_taptest_table_p60000_p62000_p62500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p62000_p62600', 'Check that table partman_test.id_taptest_table_p60000_p62000_p62600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p62000_p62700', 'Check that table partman_test.id_taptest_table_p60000_p62000_p62700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p62000_p62800', 'Check that table partman_test.id_taptest_table_p60000_p62000_p62800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p62000_p62900', 'Check that table partman_test.id_taptest_table_p60000_p62000_p62900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p63000_p63000', 'Check that table partman_test.id_taptest_table_p60000_p63000_p63000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p63000_p63100', 'Check that table partman_test.id_taptest_table_p60000_p63000_p63100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p63000_p63200', 'Check that table partman_test.id_taptest_table_p60000_p63000_p63200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p63000_p63300', 'Check that table partman_test.id_taptest_table_p60000_p63000_p63300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p63000_p63400', 'Check that table partman_test.id_taptest_table_p60000_p63000_p63400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p63000_p63500', 'Check that table partman_test.id_taptest_table_p60000_p63000_p63500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p63000_p63600', 'Check that table partman_test.id_taptest_table_p60000_p63000_p63600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p63000_p63700', 'Check that table partman_test.id_taptest_table_p60000_p63000_p63700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p63000_p63800', 'Check that table partman_test.id_taptest_table_p60000_p63000_p63800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p63000_p63900', 'Check that table partman_test.id_taptest_table_p60000_p63000_p63900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p64000_p64000', 'Check that table partman_test.id_taptest_table_p60000_p64000_p64000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p64000_p64100', 'Check that table partman_test.id_taptest_table_p60000_p64000_p64100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p64000_p64200', 'Check that table partman_test.id_taptest_table_p60000_p64000_p64200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p64000_p64300', 'Check that table partman_test.id_taptest_table_p60000_p64000_p64300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p64000_p64400', 'Check that table partman_test.id_taptest_table_p60000_p64000_p64400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p64000_p64500', 'Check that table partman_test.id_taptest_table_p60000_p64000_p64500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p64000_p64600', 'Check that table partman_test.id_taptest_table_p60000_p64000_p64600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p64000_p64700', 'Check that table partman_test.id_taptest_table_p60000_p64000_p64700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p64000_p64800', 'Check that table partman_test.id_taptest_table_p60000_p64000_p64800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p64000_p64900', 'Check that table partman_test.id_taptest_table_p60000_p64000_p64900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p65000_p65000', 'Check that table partman_test.id_taptest_table_p60000_p65000_p65000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p65000_p65100', 'Check that table partman_test.id_taptest_table_p60000_p65000_p65100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p65000_p65200', 'Check that table partman_test.id_taptest_table_p60000_p65000_p65200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p65000_p65300', 'Check that table partman_test.id_taptest_table_p60000_p65000_p65300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p65000_p65400', 'Check that table partman_test.id_taptest_table_p60000_p65000_p65400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p65000_p65500', 'Check that table partman_test.id_taptest_table_p60000_p65000_p65500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p65000_p65600', 'Check that table partman_test.id_taptest_table_p60000_p65000_p65600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p65000_p65700', 'Check that table partman_test.id_taptest_table_p60000_p65000_p65700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p65000_p65800', 'Check that table partman_test.id_taptest_table_p60000_p65000_p65800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p65000_p65900', 'Check that table partman_test.id_taptest_table_p60000_p65000_p65900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p66000_p66000', 'Check that table partman_test.id_taptest_table_p60000_p66000_p66000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p66000_p66100', 'Check that table partman_test.id_taptest_table_p60000_p66000_p66100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p66000_p66200', 'Check that table partman_test.id_taptest_table_p60000_p66000_p66200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p66000_p66300', 'Check that table partman_test.id_taptest_table_p60000_p66000_p66300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p66000_p66400', 'Check that table partman_test.id_taptest_table_p60000_p66000_p66400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p66000_p66500', 'Check that table partman_test.id_taptest_table_p60000_p66000_p66500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p66000_p66600', 'Check that table partman_test.id_taptest_table_p60000_p66000_p66600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p66000_p66700', 'Check that table partman_test.id_taptest_table_p60000_p66000_p66700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p66000_p66800', 'Check that table partman_test.id_taptest_table_p60000_p66000_p66800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p66000_p66900', 'Check that table partman_test.id_taptest_table_p60000_p66000_p66900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p67000_p67000', 'Check that table partman_test.id_taptest_table_p60000_p67000_p67000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p67000_p67100', 'Check that table partman_test.id_taptest_table_p60000_p67000_p67100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p67000_p67200', 'Check that table partman_test.id_taptest_table_p60000_p67000_p67200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p67000_p67300', 'Check that table partman_test.id_taptest_table_p60000_p67000_p67300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p67000_p67400', 'Check that table partman_test.id_taptest_table_p60000_p67000_p67400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p67000_p67500', 'Check that table partman_test.id_taptest_table_p60000_p67000_p67500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p67000_p67600', 'Check that table partman_test.id_taptest_table_p60000_p67000_p67600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p67000_p67700', 'Check that table partman_test.id_taptest_table_p60000_p67000_p67700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p67000_p67800', 'Check that table partman_test.id_taptest_table_p60000_p67000_p67800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p67000_p67900', 'Check that table partman_test.id_taptest_table_p60000_p67000_p67900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p68000_p68000', 'Check that table partman_test.id_taptest_table_p60000_p68000_p68000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p68000_p68100', 'Check that table partman_test.id_taptest_table_p60000_p68000_p68100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p68000_p68200', 'Check that table partman_test.id_taptest_table_p60000_p68000_p68200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p68000_p68300', 'Check that table partman_test.id_taptest_table_p60000_p68000_p68300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p68000_p68400', 'Check that table partman_test.id_taptest_table_p60000_p68000_p68400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p68000_p68500', 'Check that table partman_test.id_taptest_table_p60000_p68000_p68500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p68000_p68600', 'Check that table partman_test.id_taptest_table_p60000_p68000_p68600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p68000_p68700', 'Check that table partman_test.id_taptest_table_p60000_p68000_p68700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p68000_p68800', 'Check that table partman_test.id_taptest_table_p60000_p68000_p68800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p68000_p68900', 'Check that table partman_test.id_taptest_table_p60000_p68000_p68900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p69000_p69000', 'Check that table partman_test.id_taptest_table_p60000_p69000_p69000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p69000_p69100', 'Check that table partman_test.id_taptest_table_p60000_p69000_p69100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p69000_p69200', 'Check that table partman_test.id_taptest_table_p60000_p69000_p69200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p69000_p69300', 'Check that table partman_test.id_taptest_table_p60000_p69000_p69300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p69000_p69400', 'Check that table partman_test.id_taptest_table_p60000_p69000_p69400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p69000_p69500', 'Check that table partman_test.id_taptest_table_p60000_p69000_p69500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p69000_p69600', 'Check that table partman_test.id_taptest_table_p60000_p69000_p69600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p69000_p69700', 'Check that table partman_test.id_taptest_table_p60000_p69000_p69700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p69000_p69800', 'Check that table partman_test.id_taptest_table_p60000_p69000_p69800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p69000_p69900', 'Check that table partman_test.id_taptest_table_p60000_p69000_p69900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p70000_p70000', 'Check that table partman_test.id_taptest_table_p70000_p70000_p70000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p70000_p70100', 'Check that table partman_test.id_taptest_table_p70000_p70000_p70100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p70000_p70200', 'Check that table partman_test.id_taptest_table_p70000_p70000_p70200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p70000_p70300', 'Check that table partman_test.id_taptest_table_p70000_p70000_p70300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p70000_p70400', 'Check that table partman_test.id_taptest_table_p70000_p70000_p70400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p70000_p70500', 'Check that table partman_test.id_taptest_table_p70000_p70000_p70500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p70000_p70600', 'Check that table partman_test.id_taptest_table_p70000_p70000_p70600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p70000_p70700', 'Check that table partman_test.id_taptest_table_p70000_p70000_p70700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p70000_p70800', 'Check that table partman_test.id_taptest_table_p70000_p70000_p70800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p70000_p70900', 'Check that table partman_test.id_taptest_table_p70000_p70000_p70900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p71000_p71000', 'Check that table partman_test.id_taptest_table_p70000_p71000_p71000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p71000_p71100', 'Check that table partman_test.id_taptest_table_p70000_p71000_p71100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p71000_p71200', 'Check that table partman_test.id_taptest_table_p70000_p71000_p71200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p71000_p71300', 'Check that table partman_test.id_taptest_table_p70000_p71000_p71300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p71000_p71400', 'Check that table partman_test.id_taptest_table_p70000_p71000_p71400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p71000_p71500', 'Check that table partman_test.id_taptest_table_p70000_p71000_p71500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p71000_p71600', 'Check that table partman_test.id_taptest_table_p70000_p71000_p71600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p71000_p71700', 'Check that table partman_test.id_taptest_table_p70000_p71000_p71700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p71000_p71800', 'Check that table partman_test.id_taptest_table_p70000_p71000_p71800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p71000_p71900', 'Check that table partman_test.id_taptest_table_p70000_p71000_p71900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p72000_p72000', 'Check that table partman_test.id_taptest_table_p70000_p72000_p72000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p72000_p72100', 'Check that table partman_test.id_taptest_table_p70000_p72000_p72100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p72000_p72200', 'Check that table partman_test.id_taptest_table_p70000_p72000_p72200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p72000_p72300', 'Check that table partman_test.id_taptest_table_p70000_p72000_p72300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p72000_p72400', 'Check that table partman_test.id_taptest_table_p70000_p72000_p72400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p72000_p72500', 'Check that table partman_test.id_taptest_table_p70000_p72000_p72500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p72000_p72600', 'Check that table partman_test.id_taptest_table_p70000_p72000_p72600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p72000_p72700', 'Check that table partman_test.id_taptest_table_p70000_p72000_p72700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p72000_p72800', 'Check that table partman_test.id_taptest_table_p70000_p72000_p72800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p72000_p72900', 'Check that table partman_test.id_taptest_table_p70000_p72000_p72900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p73000_p73000', 'Check that table partman_test.id_taptest_table_p70000_p73000_p73000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p73000_p73100', 'Check that table partman_test.id_taptest_table_p70000_p73000_p73100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p73000_p73200', 'Check that table partman_test.id_taptest_table_p70000_p73000_p73200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p73000_p73300', 'Check that table partman_test.id_taptest_table_p70000_p73000_p73300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p73000_p73400', 'Check that table partman_test.id_taptest_table_p70000_p73000_p73400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p73000_p73500', 'Check that table partman_test.id_taptest_table_p70000_p73000_p73500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p73000_p73600', 'Check that table partman_test.id_taptest_table_p70000_p73000_p73600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p73000_p73700', 'Check that table partman_test.id_taptest_table_p70000_p73000_p73700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p73000_p73800', 'Check that table partman_test.id_taptest_table_p70000_p73000_p73800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p73000_p73900', 'Check that table partman_test.id_taptest_table_p70000_p73000_p73900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p74000_p74000', 'Check that table partman_test.id_taptest_table_p70000_p74000_p74000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p74000_p74100', 'Check that table partman_test.id_taptest_table_p70000_p74000_p74100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p74000_p74200', 'Check that table partman_test.id_taptest_table_p70000_p74000_p74200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p74000_p74300', 'Check that table partman_test.id_taptest_table_p70000_p74000_p74300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p74000_p74400', 'Check that table partman_test.id_taptest_table_p70000_p74000_p74400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p74000_p74500', 'Check that table partman_test.id_taptest_table_p70000_p74000_p74500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p74000_p74600', 'Check that table partman_test.id_taptest_table_p70000_p74000_p74600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p74000_p74700', 'Check that table partman_test.id_taptest_table_p70000_p74000_p74700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p74000_p74800', 'Check that table partman_test.id_taptest_table_p70000_p74000_p74800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p74000_p74900', 'Check that table partman_test.id_taptest_table_p70000_p74000_p74900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p75000_p75000', 'Check that table partman_test.id_taptest_table_p70000_p75000_p75000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p75000_p75100', 'Check that table partman_test.id_taptest_table_p70000_p75000_p75100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p75000_p75200', 'Check that table partman_test.id_taptest_table_p70000_p75000_p75200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p75000_p75300', 'Check that table partman_test.id_taptest_table_p70000_p75000_p75300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p75000_p75400', 'Check that table partman_test.id_taptest_table_p70000_p75000_p75400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p75000_p75500', 'Check that table partman_test.id_taptest_table_p70000_p75000_p75500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p75000_p75600', 'Check that table partman_test.id_taptest_table_p70000_p75000_p75600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p75000_p75700', 'Check that table partman_test.id_taptest_table_p70000_p75000_p75700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p75000_p75800', 'Check that table partman_test.id_taptest_table_p70000_p75000_p75800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p75000_p75900', 'Check that table partman_test.id_taptest_table_p70000_p75000_p75900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p76000_p76000', 'Check that table partman_test.id_taptest_table_p70000_p76000_p76000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p76000_p76100', 'Check that table partman_test.id_taptest_table_p70000_p76000_p76100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p76000_p76200', 'Check that table partman_test.id_taptest_table_p70000_p76000_p76200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p76000_p76300', 'Check that table partman_test.id_taptest_table_p70000_p76000_p76300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p76000_p76400', 'Check that table partman_test.id_taptest_table_p70000_p76000_p76400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p76000_p76500', 'Check that table partman_test.id_taptest_table_p70000_p76000_p76500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p76000_p76600', 'Check that table partman_test.id_taptest_table_p70000_p76000_p76600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p76000_p76700', 'Check that table partman_test.id_taptest_table_p70000_p76000_p76700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p76000_p76800', 'Check that table partman_test.id_taptest_table_p70000_p76000_p76800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p76000_p76900', 'Check that table partman_test.id_taptest_table_p70000_p76000_p76900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p77000_p77000', 'Check that table partman_test.id_taptest_table_p70000_p77000_p77000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p77000_p77100', 'Check that table partman_test.id_taptest_table_p70000_p77000_p77100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p77000_p77200', 'Check that table partman_test.id_taptest_table_p70000_p77000_p77200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p77000_p77300', 'Check that table partman_test.id_taptest_table_p70000_p77000_p77300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p77000_p77400', 'Check that table partman_test.id_taptest_table_p70000_p77000_p77400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p77000_p77500', 'Check that table partman_test.id_taptest_table_p70000_p77000_p77500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p77000_p77600', 'Check that table partman_test.id_taptest_table_p70000_p77000_p77600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p77000_p77700', 'Check that table partman_test.id_taptest_table_p70000_p77000_p77700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p77000_p77800', 'Check that table partman_test.id_taptest_table_p70000_p77000_p77800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p77000_p77900', 'Check that table partman_test.id_taptest_table_p70000_p77000_p77900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p78000_p78000', 'Check that table partman_test.id_taptest_table_p70000_p78000_p78000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p78000_p78100', 'Check that table partman_test.id_taptest_table_p70000_p78000_p78100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p78000_p78200', 'Check that table partman_test.id_taptest_table_p70000_p78000_p78200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p78000_p78300', 'Check that table partman_test.id_taptest_table_p70000_p78000_p78300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p78000_p78400', 'Check that table partman_test.id_taptest_table_p70000_p78000_p78400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p78000_p78500', 'Check that table partman_test.id_taptest_table_p70000_p78000_p78500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p78000_p78600', 'Check that table partman_test.id_taptest_table_p70000_p78000_p78600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p78000_p78700', 'Check that table partman_test.id_taptest_table_p70000_p78000_p78700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p78000_p78800', 'Check that table partman_test.id_taptest_table_p70000_p78000_p78800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p78000_p78900', 'Check that table partman_test.id_taptest_table_p70000_p78000_p78900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p79000_p79000', 'Check that table partman_test.id_taptest_table_p70000_p79000_p79000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p79000_p79100', 'Check that table partman_test.id_taptest_table_p70000_p79000_p79100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p79000_p79200', 'Check that table partman_test.id_taptest_table_p70000_p79000_p79200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p79000_p79300', 'Check that table partman_test.id_taptest_table_p70000_p79000_p79300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p79000_p79400', 'Check that table partman_test.id_taptest_table_p70000_p79000_p79400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p79000_p79500', 'Check that table partman_test.id_taptest_table_p70000_p79000_p79500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p79000_p79600', 'Check that table partman_test.id_taptest_table_p70000_p79000_p79600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p79000_p79700', 'Check that table partman_test.id_taptest_table_p70000_p79000_p79700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p79000_p79800', 'Check that table partman_test.id_taptest_table_p70000_p79000_p79800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p79000_p79900', 'Check that table partman_test.id_taptest_table_p70000_p79000_p79900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p80000_p80000', 'Check that table partman_test.id_taptest_table_p80000_p80000_p80000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p80000_p80100', 'Check that table partman_test.id_taptest_table_p80000_p80000_p80100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p80000_p80200', 'Check that table partman_test.id_taptest_table_p80000_p80000_p80200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p80000_p80300', 'Check that table partman_test.id_taptest_table_p80000_p80000_p80300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p80000_p80400', 'Check that table partman_test.id_taptest_table_p80000_p80000_p80400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p80000_p80500', 'Check that table partman_test.id_taptest_table_p80000_p80000_p80500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p80000_p80600', 'Check that table partman_test.id_taptest_table_p80000_p80000_p80600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p80000_p80700', 'Check that table partman_test.id_taptest_table_p80000_p80000_p80700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p80000_p80800', 'Check that table partman_test.id_taptest_table_p80000_p80000_p80800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p80000_p80900', 'Check that table partman_test.id_taptest_table_p80000_p80000_p80900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p81000_p81000', 'Check that table partman_test.id_taptest_table_p80000_p81000_p81000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p81000_p81100', 'Check that table partman_test.id_taptest_table_p80000_p81000_p81100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p81000_p81200', 'Check that table partman_test.id_taptest_table_p80000_p81000_p81200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p81000_p81300', 'Check that table partman_test.id_taptest_table_p80000_p81000_p81300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p81000_p81400', 'Check that table partman_test.id_taptest_table_p80000_p81000_p81400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p81000_p81500', 'Check that table partman_test.id_taptest_table_p80000_p81000_p81500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p81000_p81600', 'Check that table partman_test.id_taptest_table_p80000_p81000_p81600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p81000_p81700', 'Check that table partman_test.id_taptest_table_p80000_p81000_p81700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p81000_p81800', 'Check that table partman_test.id_taptest_table_p80000_p81000_p81800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p81000_p81900', 'Check that table partman_test.id_taptest_table_p80000_p81000_p81900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p82000_p82000', 'Check that table partman_test.id_taptest_table_p80000_p82000_p82000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p82000_p82100', 'Check that table partman_test.id_taptest_table_p80000_p82000_p82100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p82000_p82200', 'Check that table partman_test.id_taptest_table_p80000_p82000_p82200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p82000_p82300', 'Check that table partman_test.id_taptest_table_p80000_p82000_p82300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p82000_p82400', 'Check that table partman_test.id_taptest_table_p80000_p82000_p82400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p82000_p82500', 'Check that table partman_test.id_taptest_table_p80000_p82000_p82500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p82000_p82600', 'Check that table partman_test.id_taptest_table_p80000_p82000_p82600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p82000_p82700', 'Check that table partman_test.id_taptest_table_p80000_p82000_p82700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p82000_p82800', 'Check that table partman_test.id_taptest_table_p80000_p82000_p82800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p82000_p82900', 'Check that table partman_test.id_taptest_table_p80000_p82000_p82900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p83000_p83000', 'Check that table partman_test.id_taptest_table_p80000_p83000_p83000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p83000_p83100', 'Check that table partman_test.id_taptest_table_p80000_p83000_p83100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p83000_p83200', 'Check that table partman_test.id_taptest_table_p80000_p83000_p83200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p83000_p83300', 'Check that table partman_test.id_taptest_table_p80000_p83000_p83300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p83000_p83400', 'Check that table partman_test.id_taptest_table_p80000_p83000_p83400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p83000_p83500', 'Check that table partman_test.id_taptest_table_p80000_p83000_p83500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p83000_p83600', 'Check that table partman_test.id_taptest_table_p80000_p83000_p83600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p83000_p83700', 'Check that table partman_test.id_taptest_table_p80000_p83000_p83700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p83000_p83800', 'Check that table partman_test.id_taptest_table_p80000_p83000_p83800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p83000_p83900', 'Check that table partman_test.id_taptest_table_p80000_p83000_p83900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p84000_p84000', 'Check that table partman_test.id_taptest_table_p80000_p84000_p84000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p84000_p84100', 'Check that table partman_test.id_taptest_table_p80000_p84000_p84100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p84000_p84200', 'Check that table partman_test.id_taptest_table_p80000_p84000_p84200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p84000_p84300', 'Check that table partman_test.id_taptest_table_p80000_p84000_p84300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p84000_p84400', 'Check that table partman_test.id_taptest_table_p80000_p84000_p84400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p84000_p84500', 'Check that table partman_test.id_taptest_table_p80000_p84000_p84500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p84000_p84600', 'Check that table partman_test.id_taptest_table_p80000_p84000_p84600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p84000_p84700', 'Check that table partman_test.id_taptest_table_p80000_p84000_p84700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p84000_p84800', 'Check that table partman_test.id_taptest_table_p80000_p84000_p84800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p84000_p84900', 'Check that table partman_test.id_taptest_table_p80000_p84000_p84900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p85000_p85000', 'Check that table partman_test.id_taptest_table_p80000_p85000_p85000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p85000_p85100', 'Check that table partman_test.id_taptest_table_p80000_p85000_p85100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p85000_p85200', 'Check that table partman_test.id_taptest_table_p80000_p85000_p85200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p85000_p85300', 'Check that table partman_test.id_taptest_table_p80000_p85000_p85300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p85000_p85400', 'Check that table partman_test.id_taptest_table_p80000_p85000_p85400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p85000_p85500', 'Check that table partman_test.id_taptest_table_p80000_p85000_p85500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p85000_p85600', 'Check that table partman_test.id_taptest_table_p80000_p85000_p85600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p85000_p85700', 'Check that table partman_test.id_taptest_table_p80000_p85000_p85700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p85000_p85800', 'Check that table partman_test.id_taptest_table_p80000_p85000_p85800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p85000_p85900', 'Check that table partman_test.id_taptest_table_p80000_p85000_p85900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p86000_p86000', 'Check that table partman_test.id_taptest_table_p80000_p86000_p86000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p86000_p86100', 'Check that table partman_test.id_taptest_table_p80000_p86000_p86100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p86000_p86200', 'Check that table partman_test.id_taptest_table_p80000_p86000_p86200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p86000_p86300', 'Check that table partman_test.id_taptest_table_p80000_p86000_p86300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p86000_p86400', 'Check that table partman_test.id_taptest_table_p80000_p86000_p86400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p86000_p86500', 'Check that table partman_test.id_taptest_table_p80000_p86000_p86500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p86000_p86600', 'Check that table partman_test.id_taptest_table_p80000_p86000_p86600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p86000_p86700', 'Check that table partman_test.id_taptest_table_p80000_p86000_p86700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p86000_p86800', 'Check that table partman_test.id_taptest_table_p80000_p86000_p86800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p86000_p86900', 'Check that table partman_test.id_taptest_table_p80000_p86000_p86900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p87000_p87000', 'Check that table partman_test.id_taptest_table_p80000_p87000_p87000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p87000_p87100', 'Check that table partman_test.id_taptest_table_p80000_p87000_p87100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p87000_p87200', 'Check that table partman_test.id_taptest_table_p80000_p87000_p87200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p87000_p87300', 'Check that table partman_test.id_taptest_table_p80000_p87000_p87300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p87000_p87400', 'Check that table partman_test.id_taptest_table_p80000_p87000_p87400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p87000_p87500', 'Check that table partman_test.id_taptest_table_p80000_p87000_p87500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p87000_p87600', 'Check that table partman_test.id_taptest_table_p80000_p87000_p87600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p87000_p87700', 'Check that table partman_test.id_taptest_table_p80000_p87000_p87700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p87000_p87800', 'Check that table partman_test.id_taptest_table_p80000_p87000_p87800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p87000_p87900', 'Check that table partman_test.id_taptest_table_p80000_p87000_p87900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p88000_p88000', 'Check that table partman_test.id_taptest_table_p80000_p88000_p88000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p88000_p88100', 'Check that table partman_test.id_taptest_table_p80000_p88000_p88100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p88000_p88200', 'Check that table partman_test.id_taptest_table_p80000_p88000_p88200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p88000_p88300', 'Check that table partman_test.id_taptest_table_p80000_p88000_p88300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p88000_p88400', 'Check that table partman_test.id_taptest_table_p80000_p88000_p88400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p88000_p88500', 'Check that table partman_test.id_taptest_table_p80000_p88000_p88500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p88000_p88600', 'Check that table partman_test.id_taptest_table_p80000_p88000_p88600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p88000_p88700', 'Check that table partman_test.id_taptest_table_p80000_p88000_p88700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p88000_p88800', 'Check that table partman_test.id_taptest_table_p80000_p88000_p88800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p88000_p88900', 'Check that table partman_test.id_taptest_table_p80000_p88000_p88900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p89000_p89000', 'Check that table partman_test.id_taptest_table_p80000_p89000_p89000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p89000_p89100', 'Check that table partman_test.id_taptest_table_p80000_p89000_p89100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p89000_p89200', 'Check that table partman_test.id_taptest_table_p80000_p89000_p89200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p89000_p89300', 'Check that table partman_test.id_taptest_table_p80000_p89000_p89300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p89000_p89400', 'Check that table partman_test.id_taptest_table_p80000_p89000_p89400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p89000_p89500', 'Check that table partman_test.id_taptest_table_p80000_p89000_p89500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p89000_p89600', 'Check that table partman_test.id_taptest_table_p80000_p89000_p89600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p89000_p89700', 'Check that table partman_test.id_taptest_table_p80000_p89000_p89700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p89000_p89800', 'Check that table partman_test.id_taptest_table_p80000_p89000_p89800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p89000_p89900', 'Check that table partman_test.id_taptest_table_p80000_p89000_p89900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p90000_p90000', 'Check that table partman_test.id_taptest_table_p90000_p90000_p90000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p90000_p90100', 'Check that table partman_test.id_taptest_table_p90000_p90000_p90100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p90000_p90200', 'Check that table partman_test.id_taptest_table_p90000_p90000_p90200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p90000_p90300', 'Check that table partman_test.id_taptest_table_p90000_p90000_p90300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p90000_p90400', 'Check that table partman_test.id_taptest_table_p90000_p90000_p90400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p90000_p90500', 'Check that table partman_test.id_taptest_table_p90000_p90000_p90500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p90000_p90600', 'Check that table partman_test.id_taptest_table_p90000_p90000_p90600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p90000_p90700', 'Check that table partman_test.id_taptest_table_p90000_p90000_p90700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p90000_p90800', 'Check that table partman_test.id_taptest_table_p90000_p90000_p90800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p90000_p90900', 'Check that table partman_test.id_taptest_table_p90000_p90000_p90900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p91000_p91000', 'Check that table partman_test.id_taptest_table_p90000_p91000_p91000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p91000_p91100', 'Check that table partman_test.id_taptest_table_p90000_p91000_p91100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p91000_p91200', 'Check that table partman_test.id_taptest_table_p90000_p91000_p91200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p91000_p91300', 'Check that table partman_test.id_taptest_table_p90000_p91000_p91300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p91000_p91400', 'Check that table partman_test.id_taptest_table_p90000_p91000_p91400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p91000_p91500', 'Check that table partman_test.id_taptest_table_p90000_p91000_p91500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p91000_p91600', 'Check that table partman_test.id_taptest_table_p90000_p91000_p91600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p91000_p91700', 'Check that table partman_test.id_taptest_table_p90000_p91000_p91700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p91000_p91800', 'Check that table partman_test.id_taptest_table_p90000_p91000_p91800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p91000_p91900', 'Check that table partman_test.id_taptest_table_p90000_p91000_p91900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p92000_p92000', 'Check that table partman_test.id_taptest_table_p90000_p92000_p92000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p92000_p92100', 'Check that table partman_test.id_taptest_table_p90000_p92000_p92100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p92000_p92200', 'Check that table partman_test.id_taptest_table_p90000_p92000_p92200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p92000_p92300', 'Check that table partman_test.id_taptest_table_p90000_p92000_p92300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p92000_p92400', 'Check that table partman_test.id_taptest_table_p90000_p92000_p92400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p92000_p92500', 'Check that table partman_test.id_taptest_table_p90000_p92000_p92500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p92000_p92600', 'Check that table partman_test.id_taptest_table_p90000_p92000_p92600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p92000_p92700', 'Check that table partman_test.id_taptest_table_p90000_p92000_p92700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p92000_p92800', 'Check that table partman_test.id_taptest_table_p90000_p92000_p92800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p92000_p92900', 'Check that table partman_test.id_taptest_table_p90000_p92000_p92900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p93000_p93000', 'Check that table partman_test.id_taptest_table_p90000_p93000_p93000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p93000_p93100', 'Check that table partman_test.id_taptest_table_p90000_p93000_p93100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p93000_p93200', 'Check that table partman_test.id_taptest_table_p90000_p93000_p93200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p93000_p93300', 'Check that table partman_test.id_taptest_table_p90000_p93000_p93300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p93000_p93400', 'Check that table partman_test.id_taptest_table_p90000_p93000_p93400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p93000_p93500', 'Check that table partman_test.id_taptest_table_p90000_p93000_p93500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p93000_p93600', 'Check that table partman_test.id_taptest_table_p90000_p93000_p93600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p93000_p93700', 'Check that table partman_test.id_taptest_table_p90000_p93000_p93700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p93000_p93800', 'Check that table partman_test.id_taptest_table_p90000_p93000_p93800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p93000_p93900', 'Check that table partman_test.id_taptest_table_p90000_p93000_p93900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p94000_p94000', 'Check that table partman_test.id_taptest_table_p90000_p94000_p94000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p94000_p94100', 'Check that table partman_test.id_taptest_table_p90000_p94000_p94100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p94000_p94200', 'Check that table partman_test.id_taptest_table_p90000_p94000_p94200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p94000_p94300', 'Check that table partman_test.id_taptest_table_p90000_p94000_p94300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p94000_p94400', 'Check that table partman_test.id_taptest_table_p90000_p94000_p94400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p94000_p94500', 'Check that table partman_test.id_taptest_table_p90000_p94000_p94500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p94000_p94600', 'Check that table partman_test.id_taptest_table_p90000_p94000_p94600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p94000_p94700', 'Check that table partman_test.id_taptest_table_p90000_p94000_p94700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p94000_p94800', 'Check that table partman_test.id_taptest_table_p90000_p94000_p94800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p94000_p94900', 'Check that table partman_test.id_taptest_table_p90000_p94000_p94900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p95000_p95000', 'Check that table partman_test.id_taptest_table_p90000_p95000_p95000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p95000_p95100', 'Check that table partman_test.id_taptest_table_p90000_p95000_p95100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p95000_p95200', 'Check that table partman_test.id_taptest_table_p90000_p95000_p95200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p95000_p95300', 'Check that table partman_test.id_taptest_table_p90000_p95000_p95300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p95000_p95400', 'Check that table partman_test.id_taptest_table_p90000_p95000_p95400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p95000_p95500', 'Check that table partman_test.id_taptest_table_p90000_p95000_p95500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p95000_p95600', 'Check that table partman_test.id_taptest_table_p90000_p95000_p95600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p95000_p95700', 'Check that table partman_test.id_taptest_table_p90000_p95000_p95700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p95000_p95800', 'Check that table partman_test.id_taptest_table_p90000_p95000_p95800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p95000_p95900', 'Check that table partman_test.id_taptest_table_p90000_p95000_p95900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p96000_p96000', 'Check that table partman_test.id_taptest_table_p90000_p96000_p96000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p96000_p96100', 'Check that table partman_test.id_taptest_table_p90000_p96000_p96100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p96000_p96200', 'Check that table partman_test.id_taptest_table_p90000_p96000_p96200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p96000_p96300', 'Check that table partman_test.id_taptest_table_p90000_p96000_p96300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p96000_p96400', 'Check that table partman_test.id_taptest_table_p90000_p96000_p96400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p96000_p96500', 'Check that table partman_test.id_taptest_table_p90000_p96000_p96500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p96000_p96600', 'Check that table partman_test.id_taptest_table_p90000_p96000_p96600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p96000_p96700', 'Check that table partman_test.id_taptest_table_p90000_p96000_p96700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p96000_p96800', 'Check that table partman_test.id_taptest_table_p90000_p96000_p96800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p96000_p96900', 'Check that table partman_test.id_taptest_table_p90000_p96000_p96900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p97000_p97000', 'Check that table partman_test.id_taptest_table_p90000_p97000_p97000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p97000_p97100', 'Check that table partman_test.id_taptest_table_p90000_p97000_p97100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p97000_p97200', 'Check that table partman_test.id_taptest_table_p90000_p97000_p97200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p97000_p97300', 'Check that table partman_test.id_taptest_table_p90000_p97000_p97300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p97000_p97400', 'Check that table partman_test.id_taptest_table_p90000_p97000_p97400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p97000_p97500', 'Check that table partman_test.id_taptest_table_p90000_p97000_p97500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p97000_p97600', 'Check that table partman_test.id_taptest_table_p90000_p97000_p97600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p97000_p97700', 'Check that table partman_test.id_taptest_table_p90000_p97000_p97700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p97000_p97800', 'Check that table partman_test.id_taptest_table_p90000_p97000_p97800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p97000_p97900', 'Check that table partman_test.id_taptest_table_p90000_p97000_p97900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p98000_p98000', 'Check that table partman_test.id_taptest_table_p90000_p98000_p98000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p98000_p98100', 'Check that table partman_test.id_taptest_table_p90000_p98000_p98100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p98000_p98200', 'Check that table partman_test.id_taptest_table_p90000_p98000_p98200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p98000_p98300', 'Check that table partman_test.id_taptest_table_p90000_p98000_p98300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p98000_p98400', 'Check that table partman_test.id_taptest_table_p90000_p98000_p98400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p98000_p98500', 'Check that table partman_test.id_taptest_table_p90000_p98000_p98500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p98000_p98600', 'Check that table partman_test.id_taptest_table_p90000_p98000_p98600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p98000_p98700', 'Check that table partman_test.id_taptest_table_p90000_p98000_p98700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p98000_p98800', 'Check that table partman_test.id_taptest_table_p90000_p98000_p98800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p98000_p98900', 'Check that table partman_test.id_taptest_table_p90000_p98000_p98900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p99000_p99000', 'Check that table partman_test.id_taptest_table_p90000_p99000_p99000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p99000_p99100', 'Check that table partman_test.id_taptest_table_p90000_p99000_p99100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p99000_p99200', 'Check that table partman_test.id_taptest_table_p90000_p99000_p99200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p99000_p99300', 'Check that table partman_test.id_taptest_table_p90000_p99000_p99300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p99000_p99400', 'Check that table partman_test.id_taptest_table_p90000_p99000_p99400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p99000_p99500', 'Check that table partman_test.id_taptest_table_p90000_p99000_p99500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p99000_p99600', 'Check that table partman_test.id_taptest_table_p90000_p99000_p99600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p99000_p99700', 'Check that table partman_test.id_taptest_table_p90000_p99000_p99700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p99000_p99800', 'Check that table partman_test.id_taptest_table_p90000_p99000_p99800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p99000_p99900', 'Check that table partman_test.id_taptest_table_p90000_p99000_p99900 no longer exists'); -- Recheck that all parents partitions have their data SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table is still empty'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[107500], 'Check count from parent table'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p0', 'Check that parent table id_taptest_table_p0 is still empty '); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p0_p0', ARRAY[999], 'Check count from id_taptest_table_p0_p0'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p0_p1000', ARRAY[1000], 'Check count from id_taptest_table_p0_p1000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p0_p2000', ARRAY[1000], 'Check count from id_taptest_table_p0_p2000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p0_p3000', ARRAY[1000], 'Check count from id_taptest_table_p0_p3000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p0_p4000', ARRAY[1000], 'Check count from id_taptest_table_p0_p4000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p0_p5000', ARRAY[1000], 'Check count from id_taptest_table_p0_p5000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p0_p6000', ARRAY[1000], 'Check count from id_taptest_table_p0_p6000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p0_p7000', ARRAY[1000], 'Check count from id_taptest_table_p0_p7000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p0_p8000', ARRAY[1000], 'Check count from id_taptest_table_p0_p8000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p0_p9000', ARRAY[1000], 'Check count from id_taptest_table_p0_p9000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10000', 'Check that parent table id_taptest_table_p10000 is still empty '); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p10000_p10000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p10000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p10000_p11000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p11000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p10000_p12000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p12000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p10000_p13000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p13000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p10000_p14000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p14000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p10000_p15000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p15000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p10000_p16000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p16000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p10000_p17000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p17000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p10000_p18000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p18000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p10000_p19000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p19000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p20000', 'Check that parent table id_taptest_table_p20000 is still empty '); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p20000_p20000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p20000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p20000_p21000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p21000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p20000_p22000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p22000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p20000_p23000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p23000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p20000_p24000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p24000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p20000_p25000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p25000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p20000_p26000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p26000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p20000_p27000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p27000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p20000_p28000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p28000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p20000_p29000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p29000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p30000', 'Check that parent table id_taptest_table_p30000 is still empty '); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p30000_p30000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p30000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p30000_p31000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p31000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p30000_p32000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p32000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p30000_p33000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p33000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p30000_p34000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p34000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p30000_p35000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p35000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p30000_p36000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p36000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p30000_p37000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p37000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p30000_p38000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p38000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p30000_p39000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p39000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p40000', 'Check that parent table id_taptest_table_p40000 is still empty '); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p40000_p40000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p40000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p40000_p41000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p41000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p40000_p42000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p42000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p40000_p43000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p43000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p40000_p44000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p44000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p40000_p45000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p45000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p40000_p46000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p46000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p40000_p47000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p47000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p40000_p48000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p48000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p40000_p49000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p49000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p50000', 'Check that parent table id_taptest_table_p50000 is still empty '); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p50000_p50000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p50000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p50000_p51000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p51000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p50000_p52000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p52000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p50000_p53000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p53000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p50000_p54000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p54000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p50000_p55000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p55000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p50000_p56000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p56000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p50000_p57000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p57000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p50000_p58000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p58000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p50000_p59000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p59000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p60000', 'Check that parent table id_taptest_table_p60000 is still empty '); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p60000_p60000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p60000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p60000_p61000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p61000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p60000_p62000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p62000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p60000_p63000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p63000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p60000_p64000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p64000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p60000_p65000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p65000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p60000_p66000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p66000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p60000_p67000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p67000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p60000_p68000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p68000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p60000_p69000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p69000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p70000', 'Check that parent table id_taptest_table_p70000 is still empty '); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p70000_p70000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p70000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p70000_p71000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p71000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p70000_p72000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p72000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p70000_p73000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p73000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p70000_p74000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p74000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p70000_p75000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p75000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p70000_p76000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p76000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p70000_p77000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p77000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p70000_p78000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p78000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p70000_p79000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p79000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p80000', 'Check that parent table id_taptest_table_p80000 is still empty '); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p80000_p80000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p80000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p80000_p81000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p81000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p80000_p82000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p82000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p80000_p83000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p83000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p80000_p84000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p84000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p80000_p85000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p85000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p80000_p86000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p86000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p80000_p87000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p87000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p80000_p88000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p88000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p80000_p89000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p89000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p90000', 'Check that parent table id_taptest_table_p90000 is still empty '); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p90000_p90000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p90000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p90000_p91000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p91000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p90000_p92000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p92000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p90000_p93000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p93000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p90000_p94000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p94000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p90000_p95000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p95000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p90000_p96000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p96000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p90000_p97000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p97000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p90000_p98000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p98000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p90000_p99000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p99000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p100000', 'Check that parent table id_taptest_table_p100000 is still empty '); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p100000_p100000', ARRAY[1000], 'Check count from id_taptest_table_p100000_p100000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p100000_p101000', ARRAY[1000], 'Check count from id_taptest_table_p100000_p100000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p100000_p102000', ARRAY[1000], 'Check count from id_taptest_table_p100000_p100000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p100000_p103000', ARRAY[1000], 'Check count from id_taptest_table_p100000_p100000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p100000_p104000', ARRAY[1000], 'Check count from id_taptest_table_p100000_p100000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p100000_p105000', ARRAY[1000], 'Check count from id_taptest_table_p100000_p100000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p100000_p106000', ARRAY[1000], 'Check count from id_taptest_table_p100000_p100000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p100000_p107000', ARRAY[501], 'Check count from id_taptest_table_p100000_p100000'); -- Undo sub-parent partitions SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p0'', 20, p_keep_table := false)', ARRAY[9999], 'Undo partitioning for parent table partman_test.id_taptest_table_p0'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p10000'', 20, p_keep_table := false)', ARRAY[10000], 'Undo partitioning for parent table partman_test.id_taptest_table_p10000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p20000'', 20, p_keep_table := false)', ARRAY[10000], 'Undo partitioning for parent table partman_test.id_taptest_table_p20000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p30000'', 20, p_keep_table := false)', ARRAY[10000], 'Undo partitioning for parent table partman_test.id_taptest_table_p30000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p40000'', 20, p_keep_table := false)', ARRAY[10000], 'Undo partitioning for parent table partman_test.id_taptest_table_p40000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p50000'', 20, p_keep_table := false)', ARRAY[10000], 'Undo partitioning for parent table partman_test.id_taptest_table_p50000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p60000'', 20, p_keep_table := false)', ARRAY[10000], 'Undo partitioning for parent table partman_test.id_taptest_table_p60000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p70000'', 20, p_keep_table := false)', ARRAY[10000], 'Undo partitioning for parent table partman_test.id_taptest_table_p70000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p80000'', 20, p_keep_table := false)', ARRAY[10000], 'Undo partitioning for parent table partman_test.id_taptest_table_p80000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p90000'', 20, p_keep_table := false)', ARRAY[10000], 'Undo partitioning for parent table partman_test.id_taptest_table_p90000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p100000'', 20, p_keep_table := false)', ARRAY[7501], 'Undo partitioning for parent table partman_test.id_taptest_table_p100000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p110000'', 20, p_keep_table := false)', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p110000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p120000'', 20, p_keep_table := false)', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p120000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p130000'', 20, p_keep_table := false)', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p130000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p140000'', 20, p_keep_table := false)', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p140000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p150000'', 20, p_keep_table := false)', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p150000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p160000'', 20, p_keep_table := false)', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p160000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p170000'', 20, p_keep_table := false)', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p170000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p180000'', 20, p_keep_table := false)', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p180000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p190000'', 20, p_keep_table := false)', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p190000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p200000'', 20, p_keep_table := false)', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p200000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table_p210000'', 20, p_keep_table := false)', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p210000'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p0', 'Check that table partman_test.id_taptest_table_p0_p0 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p1000', 'Check that table partman_test.id_taptest_table_p0_p1000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p2000', 'Check that table partman_test.id_taptest_table_p0_p2000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p3000', 'Check that table partman_test.id_taptest_table_p0_p3000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p4000', 'Check that table partman_test.id_taptest_table_p0_p4000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p5000', 'Check that table partman_test.id_taptest_table_p0_p5000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p6000', 'Check that table partman_test.id_taptest_table_p0_p6000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p7000', 'Check that table partman_test.id_taptest_table_p0_p7000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p8000', 'Check that table partman_test.id_taptest_table_p0_p8000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p9000', 'Check that table partman_test.id_taptest_table_p0_p9000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p100000', 'Check that table partman_test.id_taptest_table_p100000_p100000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p101000', 'Check that table partman_test.id_taptest_table_p100000_p101000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p102000', 'Check that table partman_test.id_taptest_table_p100000_p102000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p103000', 'Check that table partman_test.id_taptest_table_p100000_p103000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p104000', 'Check that table partman_test.id_taptest_table_p100000_p104000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p105000', 'Check that table partman_test.id_taptest_table_p100000_p105000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p106000', 'Check that table partman_test.id_taptest_table_p100000_p106000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p107000', 'Check that table partman_test.id_taptest_table_p100000_p107000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p108000', 'Check that table partman_test.id_taptest_table_p100000_p108000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p109000', 'Check that table partman_test.id_taptest_table_p100000_p109000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p10000', 'Check that table partman_test.id_taptest_table_p10000_p10000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p11000', 'Check that table partman_test.id_taptest_table_p10000_p11000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p12000', 'Check that table partman_test.id_taptest_table_p10000_p12000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p13000', 'Check that table partman_test.id_taptest_table_p10000_p13000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p14000', 'Check that table partman_test.id_taptest_table_p10000_p14000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p15000', 'Check that table partman_test.id_taptest_table_p10000_p15000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p16000', 'Check that table partman_test.id_taptest_table_p10000_p16000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p17000', 'Check that table partman_test.id_taptest_table_p10000_p17000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p18000', 'Check that table partman_test.id_taptest_table_p10000_p18000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p19000', 'Check that table partman_test.id_taptest_table_p10000_p19000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p110000_p110000', 'Check that table partman_test.id_taptest_table_p110000_p110000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p120000_p120000', 'Check that table partman_test.id_taptest_table_p120000_p120000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p130000_p130000', 'Check that table partman_test.id_taptest_table_p130000_p130000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p140000_p140000', 'Check that table partman_test.id_taptest_table_p140000_p140000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p150000_p150000', 'Check that table partman_test.id_taptest_table_p150000_p150000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p160000_p160000', 'Check that table partman_test.id_taptest_table_p160000_p160000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p170000_p170000', 'Check that table partman_test.id_taptest_table_p170000_p170000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p180000_p180000', 'Check that table partman_test.id_taptest_table_p180000_p180000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p190000_p190000', 'Check that table partman_test.id_taptest_table_p190000_p190000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p200000_p200000', 'Check that table partman_test.id_taptest_table_p200000_p200000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p20000', 'Check that table partman_test.id_taptest_table_p20000_p20000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p21000', 'Check that table partman_test.id_taptest_table_p20000_p21000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p22000', 'Check that table partman_test.id_taptest_table_p20000_p22000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p23000', 'Check that table partman_test.id_taptest_table_p20000_p23000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p24000', 'Check that table partman_test.id_taptest_table_p20000_p24000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p25000', 'Check that table partman_test.id_taptest_table_p20000_p25000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p26000', 'Check that table partman_test.id_taptest_table_p20000_p26000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p27000', 'Check that table partman_test.id_taptest_table_p20000_p27000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p28000', 'Check that table partman_test.id_taptest_table_p20000_p28000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p29000', 'Check that table partman_test.id_taptest_table_p20000_p29000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p210000_p210000', 'Check that table partman_test.id_taptest_table_p210000_p210000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p30000', 'Check that table partman_test.id_taptest_table_p30000_p30000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p31000', 'Check that table partman_test.id_taptest_table_p30000_p31000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p32000', 'Check that table partman_test.id_taptest_table_p30000_p32000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p33000', 'Check that table partman_test.id_taptest_table_p30000_p33000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p34000', 'Check that table partman_test.id_taptest_table_p30000_p34000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p35000', 'Check that table partman_test.id_taptest_table_p30000_p35000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p36000', 'Check that table partman_test.id_taptest_table_p30000_p36000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p37000', 'Check that table partman_test.id_taptest_table_p30000_p37000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p38000', 'Check that table partman_test.id_taptest_table_p30000_p38000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p39000', 'Check that table partman_test.id_taptest_table_p30000_p39000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p40000', 'Check that table partman_test.id_taptest_table_p40000_p40000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p41000', 'Check that table partman_test.id_taptest_table_p40000_p41000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p42000', 'Check that table partman_test.id_taptest_table_p40000_p42000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p43000', 'Check that table partman_test.id_taptest_table_p40000_p43000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p44000', 'Check that table partman_test.id_taptest_table_p40000_p44000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p45000', 'Check that table partman_test.id_taptest_table_p40000_p45000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p46000', 'Check that table partman_test.id_taptest_table_p40000_p46000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p47000', 'Check that table partman_test.id_taptest_table_p40000_p47000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p48000', 'Check that table partman_test.id_taptest_table_p40000_p48000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p49000', 'Check that table partman_test.id_taptest_table_p40000_p49000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p50000', 'Check that table partman_test.id_taptest_table_p50000_p50000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p51000', 'Check that table partman_test.id_taptest_table_p50000_p51000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p52000', 'Check that table partman_test.id_taptest_table_p50000_p52000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p53000', 'Check that table partman_test.id_taptest_table_p50000_p53000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p54000', 'Check that table partman_test.id_taptest_table_p50000_p54000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p55000', 'Check that table partman_test.id_taptest_table_p50000_p55000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p56000', 'Check that table partman_test.id_taptest_table_p50000_p56000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p57000', 'Check that table partman_test.id_taptest_table_p50000_p57000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p58000', 'Check that table partman_test.id_taptest_table_p50000_p58000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p59000', 'Check that table partman_test.id_taptest_table_p50000_p59000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p60000', 'Check that table partman_test.id_taptest_table_p60000_p60000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p61000', 'Check that table partman_test.id_taptest_table_p60000_p61000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p62000', 'Check that table partman_test.id_taptest_table_p60000_p62000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p63000', 'Check that table partman_test.id_taptest_table_p60000_p63000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p64000', 'Check that table partman_test.id_taptest_table_p60000_p64000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p65000', 'Check that table partman_test.id_taptest_table_p60000_p65000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p66000', 'Check that table partman_test.id_taptest_table_p60000_p66000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p67000', 'Check that table partman_test.id_taptest_table_p60000_p67000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p68000', 'Check that table partman_test.id_taptest_table_p60000_p68000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p69000', 'Check that table partman_test.id_taptest_table_p60000_p69000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p70000', 'Check that table partman_test.id_taptest_table_p70000_p70000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p71000', 'Check that table partman_test.id_taptest_table_p70000_p71000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p72000', 'Check that table partman_test.id_taptest_table_p70000_p72000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p73000', 'Check that table partman_test.id_taptest_table_p70000_p73000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p74000', 'Check that table partman_test.id_taptest_table_p70000_p74000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p75000', 'Check that table partman_test.id_taptest_table_p70000_p75000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p76000', 'Check that table partman_test.id_taptest_table_p70000_p76000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p77000', 'Check that table partman_test.id_taptest_table_p70000_p77000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p78000', 'Check that table partman_test.id_taptest_table_p70000_p78000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p79000', 'Check that table partman_test.id_taptest_table_p70000_p79000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p80000', 'Check that table partman_test.id_taptest_table_p80000_p80000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p81000', 'Check that table partman_test.id_taptest_table_p80000_p81000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p82000', 'Check that table partman_test.id_taptest_table_p80000_p82000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p83000', 'Check that table partman_test.id_taptest_table_p80000_p83000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p84000', 'Check that table partman_test.id_taptest_table_p80000_p84000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p85000', 'Check that table partman_test.id_taptest_table_p80000_p85000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p86000', 'Check that table partman_test.id_taptest_table_p80000_p86000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p87000', 'Check that table partman_test.id_taptest_table_p80000_p87000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p88000', 'Check that table partman_test.id_taptest_table_p80000_p88000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p89000', 'Check that table partman_test.id_taptest_table_p80000_p89000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p90000', 'Check that table partman_test.id_taptest_table_p90000_p90000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p91000', 'Check that table partman_test.id_taptest_table_p90000_p91000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p92000', 'Check that table partman_test.id_taptest_table_p90000_p92000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p93000', 'Check that table partman_test.id_taptest_table_p90000_p93000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p94000', 'Check that table partman_test.id_taptest_table_p90000_p94000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p95000', 'Check that table partman_test.id_taptest_table_p90000_p95000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p96000', 'Check that table partman_test.id_taptest_table_p90000_p96000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p97000', 'Check that table partman_test.id_taptest_table_p90000_p97000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p98000', 'Check that table partman_test.id_taptest_table_p90000_p98000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p99000', 'Check that table partman_test.id_taptest_table_p90000_p99000 no longer exists'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table'', 20, p_keep_table := false)', ARRAY[107500], 'Undo partitioning for final parent table partman_test.id_taptest_table'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table', ARRAY[107500], 'Check count from final, non-partitioned table'); SELECT hasnt_table('partman_test.id_taptest_table_p0', 'Check that table partman_test.id_taptest_table_p0 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000', 'Check that table partman_test.id_taptest_table_p10000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000', 'Check that table partman_test.id_taptest_table_p100000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p110000', 'Check that table partman_test.id_taptest_table_p110000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p120000', 'Check that table partman_test.id_taptest_table_p120000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p130000', 'Check that table partman_test.id_taptest_table_p130000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p140000', 'Check that table partman_test.id_taptest_table_p140000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p150000', 'Check that table partman_test.id_taptest_table_p150000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p160000', 'Check that table partman_test.id_taptest_table_p160000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p170000', 'Check that table partman_test.id_taptest_table_p170000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p180000', 'Check that table partman_test.id_taptest_table_p180000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p190000', 'Check that table partman_test.id_taptest_table_p190000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000', 'Check that table partman_test.id_taptest_table_p20000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p200000', 'Check that table partman_test.id_taptest_table_p200000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p210000', 'Check that table partman_test.id_taptest_table_p210000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000', 'Check that table partman_test.id_taptest_table_p30000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000', 'Check that table partman_test.id_taptest_table_p40000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000', 'Check that table partman_test.id_taptest_table_p50000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000', 'Check that table partman_test.id_taptest_table_p60000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000', 'Check that table partman_test.id_taptest_table_p70000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000', 'Check that table partman_test.id_taptest_table_p80000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000', 'Check that table partman_test.id_taptest_table_p90000 no longer exists'); SELECT is_empty('SELECT * FROM part_config WHERE parent_table LIKE ''partman_test.id_taptest_table%''', 'Check that part_config has all test tables removed'); SELECT is_empty('SELECT * FROM part_config_sub WHERE sub_parent LIKE ''partman_test.id_taptest_table%''', 'Check that part_config_sub has all test tables removed'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test-id-mixed-case.sql000066400000000000000000001574311412761706100207570ustar00rootroot00000000000000-- ########## ID MIXED CASE SUBPARTITION TESTS ########## -- Other tests: Foreign keys, hybrid trigger puts data outside premake into proper tables, retention schema, no jobmon, test 50% trigger with mixed case \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(374); CREATE SCHEMA "Partman_test"; CREATE SCHEMA "Partman-retention-Test"; CREATE TABLE "Partman_test"."FK_test_reference" (col2 text not null, col4 text not null); CREATE UNIQUE INDEX ON "Partman_test"."FK_test_reference"(col2, col4); INSERT INTO "Partman_test"."FK_test_reference" VALUES ('stuff', 'stuff'); CREATE TABLE "Partman_test"."ID-taptest_Table" ( "COL1" int primary key , col2 text default 'stuff' , "Col-3" timestamptz NOT NULL DEFAULT now() , col4 text default 'stuff' , FOREIGN KEY (col2, col4) REFERENCES "Partman_test"."FK_test_reference"(col2, col4) MATCH FULL ON DELETE RESTRICT DEFERRABLE); INSERT INTO "Partman_test"."ID-taptest_Table" ("COL1") VALUES (generate_series(1,50000)); SELECT create_parent('Partman_test.ID-taptest_Table', 'COL1', 'partman', '10000', p_jobmon := false, p_premake := 2); UPDATE part_config SET drop_cascade_fk = TRUE WHERE parent_table = 'Partman_test.ID-taptest_Table'; SELECT results_eq('SELECT partition_data_id(''Partman_test.ID-taptest_Table'', p_batch_count := 20)::int', ARRAY[50000], 'Check that partitioning function returns correct count of rows moved'); SELECT is_empty('SELECT * FROM ONLY "Partman_test"."ID-taptest_Table"', 'Check that parent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table"', ARRAY[50000], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p0"', ARRAY[9999], 'Check count from ID-taptest_Table_p0'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p10000"', ARRAY[10000], 'Check count from ID-taptest_Table_p10000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p20000"', ARRAY[10000], 'Check count from ID-taptest_Table_p20000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p30000"', ARRAY[10000], 'Check count from ID-taptest_Table_p30000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p40000"', ARRAY[10000], 'Check count from ID-taptest_Table_p40000'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p0', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p0'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p10000', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p10000'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p20000', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p20000'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p30000', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p30000'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p40000', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p40000'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p50000', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p50000'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p60000', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p60000'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p70000', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p70000'); SELECT col_is_fk('Partman_test', 'ID-taptest_Table_p0', ARRAY['col2', 'col4'], 'Check for inherited foreign key in ID-taptest_Table_p0'); SELECT col_is_fk('Partman_test', 'ID-taptest_Table_p10000', ARRAY['col2', 'col4'], 'Check for inherited foreign key in ID-taptest_Table_p10000'); SELECT col_is_fk('Partman_test', 'ID-taptest_Table_p20000', ARRAY['col2', 'col4'], 'Check for inherited foreign key in ID-taptest_Table_p20000'); SELECT col_is_fk('Partman_test', 'ID-taptest_Table_p30000', ARRAY['col2', 'col4'], 'Check for inherited foreign key in ID-taptest_Table_p30000'); SELECT col_is_fk('Partman_test', 'ID-taptest_Table_p40000', ARRAY['col2', 'col4'], 'Check for inherited foreign key in ID-taptest_Table_p40000'); SELECT col_is_fk('Partman_test', 'ID-taptest_Table_p50000', ARRAY['col2', 'col4'], 'Check for inherited foreign key in ID-taptest_Table_p50000'); SELECT col_is_fk('Partman_test', 'ID-taptest_Table_p60000', ARRAY['col2', 'col4'], 'Check for inherited foreign key in ID-taptest_Table_p60000'); SELECT col_is_fk('Partman_test', 'ID-taptest_Table_p70000', ARRAY['col2', 'col4'], 'Check for inherited foreign key in ID-taptest_Table_p70000'); INSERT INTO "Partman_test"."ID-taptest_Table" ("COL1") VALUES (generate_series(50001, 70000)); SELECT run_maintenance(); INSERT INTO "Partman_test"."ID-taptest_Table" ("COL1") VALUES (generate_series(70001, 90000)); SELECT run_maintenance(); INSERT INTO "Partman_test"."ID-taptest_Table" ("COL1") VALUES (generate_series(90001, 100000)); SELECT has_table('Partman_test', 'ID-taptest_Table_p80000', 'Check "ID-taptest_Table_p80000 exists'); SELECT has_table('Partman_test', 'ID-taptest_Table_p90000', 'Check "ID-taptest_Table_p90000 exists'); SELECT has_table('Partman_test', 'ID-taptest_Table_p100000', 'Check "ID-taptest_Table_p100000 exists'); SELECT has_table('Partman_test', 'ID-taptest_Table_p110000', 'Check "ID-taptest_Table_p110000 exists'); -- Won't get created by 50% rule unless 100000 row table gets half full. run_maintenance() call below after subpart will create it SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p120000', 'Check "ID-taptest_Table_p120000 does not exist'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p50000"', ARRAY[10000], 'Check count from ID-taptest_Table_p50000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p60000"', ARRAY[10000], 'Check count from ID-taptest_Table_p60000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p70000"', ARRAY[10000], 'Check count from ID-taptest_Table_p70000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p80000"', ARRAY[10000], 'Check count from ID-taptest_Table_p80000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p90000"', ARRAY[10000], 'Check count from ID-taptest_Table_p90000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p100000"', ARRAY[1], 'Check count from ID-taptest_Table_p100000'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p80000', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p80000'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p90000', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p90000'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p100000', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p100000'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p110000', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p110000'); SELECT col_is_fk('Partman_test', 'ID-taptest_Table_p80000', ARRAY['col2', 'col4'], 'Check for inherited foreign key in ID-taptest_Table_p80000'); SELECT col_is_fk('Partman_test', 'ID-taptest_Table_p90000', ARRAY['col2', 'col4'], 'Check for inherited foreign key in ID-taptest_Table_p90000'); SELECT col_is_fk('Partman_test', 'ID-taptest_Table_p100000', ARRAY['col2', 'col4'], 'Check for inherited foreign key in ID-taptest_Table_p100000'); SELECT col_is_fk('Partman_test', 'ID-taptest_Table_p110000', ARRAY['col2', 'col4'], 'Check for inherited foreign key in ID-taptest_Table_p110000'); SELECT create_sub_parent('Partman_test.ID-taptest_Table', 'COL1', 'partman', '1000', p_jobmon := false, p_premake := 2); -- Test for normal partitions that should be made based on current max value of 100,000 SELECT has_table('Partman_test', 'ID-taptest_Table_p90000_p98000', 'Check ID-taptest_Table_p90000_p98000 exists'); SELECT has_table('Partman_test', 'ID-taptest_Table_p90000_p99000', 'Check ID-taptest_Table_p90000_p99000 exists'); SELECT has_table('Partman_test', 'ID-taptest_Table_p100000_p100000', 'Check ID-taptest_Table_p100000_p100000 exists'); SELECT has_table('Partman_test', 'ID-taptest_Table_p100000_p101000', 'Check ID-taptest_Table_p100000_p101000 exists'); SELECT has_table('Partman_test', 'ID-taptest_Table_p100000_p102000', 'Check ID-taptest_Table_p100000_p102000 exists'); -- Tests that ensure minimal partition was made in all other sets SELECT has_table('Partman_test', 'ID-taptest_Table_p0_p0', 'Check ID-taptest_Table_p0_p0 exists'); SELECT has_table('Partman_test', 'ID-taptest_Table_p10000_p10000', 'Check ID-taptest_Table_p10000_p10000 exists'); SELECT has_table('Partman_test', 'ID-taptest_Table_p20000_p20000', 'Check ID-taptest_Table_p20000_p20000 exists'); SELECT has_table('Partman_test', 'ID-taptest_Table_p30000_p30000', 'Check ID-taptest_Table_p30000_p30000 exists'); SELECT has_table('Partman_test', 'ID-taptest_Table_p40000_p40000', 'Check ID-taptest_Table_p40000_p40000 exists'); SELECT has_table('Partman_test', 'ID-taptest_Table_p50000_p50000', 'Check ID-taptest_Table_p50000_p50000 exists'); SELECT has_table('Partman_test', 'ID-taptest_Table_p60000_p60000', 'Check ID-taptest_Table_p60000_p60000 exists'); SELECT has_table('Partman_test', 'ID-taptest_Table_p70000_p70000', 'Check ID-taptest_Table_p70000_p70000 exists'); SELECT has_table('Partman_test', 'ID-taptest_Table_p80000_p80000', 'Check ID-taptest_Table_p80000_p80000 exists'); SELECT has_table('Partman_test', 'ID-taptest_Table_p110000_p110000', 'Check ID-taptest_Table_p110000_p110000 exists'); -- Partition all data again SELECT results_eq('SELECT partition_data_id(''Partman_test.ID-taptest_Table_p0'', p_batch_count := 20)::int', ARRAY[9999], 'Check that partitioning function returns correct count of rows moved for ID-taptest_Table_p0'); SELECT results_eq('SELECT partition_data_id(''Partman_test.ID-taptest_Table_p10000'', p_batch_count := 20)::int', ARRAY[10000], 'Check that partitioning function returns correct count of rows moved for ID-taptest_Table_p10000'); SELECT results_eq('SELECT partition_data_id(''Partman_test.ID-taptest_Table_p20000'', p_batch_count := 20)::int', ARRAY[10000], 'Check that partitioning function returns correct count of rows moved for ID-taptest_Table_p20000'); SELECT results_eq('SELECT partition_data_id(''Partman_test.ID-taptest_Table_p30000'', p_batch_count := 20)::int', ARRAY[10000], 'Check that partitioning function returns correct count of rows moved for ID-taptest_Table_p30000'); SELECT results_eq('SELECT partition_data_id(''Partman_test.ID-taptest_Table_p40000'', p_batch_count := 20)::int', ARRAY[10000], 'Check that partitioning function returns correct count of rows moved for ID-taptest_Table_p40000'); SELECT results_eq('SELECT partition_data_id(''Partman_test.ID-taptest_Table_p50000'', p_batch_count := 20)::int', ARRAY[10000], 'Check that partitioning function returns correct count of rows moved for ID-taptest_Table_p50000'); SELECT results_eq('SELECT partition_data_id(''Partman_test.ID-taptest_Table_p60000'', p_batch_count := 20)::int', ARRAY[10000], 'Check that partitioning function returns correct count of rows moved for ID-taptest_Table_p60000'); SELECT results_eq('SELECT partition_data_id(''Partman_test.ID-taptest_Table_p70000'', p_batch_count := 20)::int', ARRAY[10000], 'Check that partitioning function returns correct count of rows moved for ID-taptest_Table_p70000'); SELECT results_eq('SELECT partition_data_id(''Partman_test.ID-taptest_Table_p80000'', p_batch_count := 20)::int', ARRAY[10000], 'Check that partitioning function returns correct count of rows moved for ID-taptest_Table_p80000'); SELECT results_eq('SELECT partition_data_id(''Partman_test.ID-taptest_Table_p90000'', p_batch_count := 20)::int', ARRAY[10000], 'Check that partitioning function returns correct count of rows moved for ID-taptest_Table_p90000'); SELECT results_eq('SELECT partition_data_id(''Partman_test.ID-taptest_Table_p100000'', p_batch_count := 20)::int', ARRAY[1], 'Check that partitioning function returns correct count of rows moved for ID-taptest_Table_p100000'); -- Test that all partitions have their data/exist SELECT is_empty('SELECT * FROM ONLY "Partman_test"."ID-taptest_Table"', 'Check that parent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table"', ARRAY[100000], 'Check count from parent table'); SELECT is_empty('SELECT * FROM ONLY "Partman_test"."ID-taptest_Table_p0"', 'Check that parent table ID-taptest_Table_p0 has had data moved to partition '); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p0_p0"', ARRAY[999], 'Check count from ID-taptest_Table_p0_p0'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p0_p1000"', ARRAY[1000], 'Check count from ID-taptest_Table_p0_p1000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p0_p2000"', ARRAY[1000], 'Check count from ID-taptest_Table_p0_p2000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p0_p3000"', ARRAY[1000], 'Check count from ID-taptest_Table_p0_p3000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p0_p4000"', ARRAY[1000], 'Check count from ID-taptest_Table_p0_p4000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p0_p5000"', ARRAY[1000], 'Check count from ID-taptest_Table_p0_p5000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p0_p6000"', ARRAY[1000], 'Check count from ID-taptest_Table_p0_p6000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p0_p7000"', ARRAY[1000], 'Check count from ID-taptest_Table_p0_p7000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p0_p8000"', ARRAY[1000], 'Check count from ID-taptest_Table_p0_p8000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p0_p9000"', ARRAY[1000], 'Check count from ID-taptest_Table_p0_p9000'); SELECT is_empty('SELECT * FROM ONLY "Partman_test"."ID-taptest_Table_p10000"', 'Check that parent table ID-taptest_Table_p10000 has had data moved to partition '); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p10000_p10000"', ARRAY[1000], 'Check count from ID-taptest_Table_p10000_p10000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p10000_p11000"', ARRAY[1000], 'Check count from ID-taptest_Table_p10000_p11000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p10000_p12000"', ARRAY[1000], 'Check count from ID-taptest_Table_p10000_p12000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p10000_p13000"', ARRAY[1000], 'Check count from ID-taptest_Table_p10000_p13000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p10000_p14000"', ARRAY[1000], 'Check count from ID-taptest_Table_p10000_p14000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p10000_p15000"', ARRAY[1000], 'Check count from ID-taptest_Table_p10000_p15000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p10000_p16000"', ARRAY[1000], 'Check count from ID-taptest_Table_p10000_p16000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p10000_p17000"', ARRAY[1000], 'Check count from ID-taptest_Table_p10000_p17000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p10000_p18000"', ARRAY[1000], 'Check count from ID-taptest_Table_p10000_p18000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p10000_p19000"', ARRAY[1000], 'Check count from ID-taptest_Table_p10000_p19000'); SELECT is_empty('SELECT * FROM ONLY "Partman_test"."ID-taptest_Table_p20000"', 'Check that parent table ID-taptest_Table_p20000 has had data moved to partition '); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p20000_p20000"', ARRAY[1000], 'Check count from ID-taptest_Table_p20000_p20000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p20000_p21000"', ARRAY[1000], 'Check count from ID-taptest_Table_p20000_p21000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p20000_p22000"', ARRAY[1000], 'Check count from ID-taptest_Table_p20000_p22000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p20000_p23000"', ARRAY[1000], 'Check count from ID-taptest_Table_p20000_p23000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p20000_p24000"', ARRAY[1000], 'Check count from ID-taptest_Table_p20000_p24000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p20000_p25000"', ARRAY[1000], 'Check count from ID-taptest_Table_p20000_p25000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p20000_p26000"', ARRAY[1000], 'Check count from ID-taptest_Table_p20000_p26000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p20000_p27000"', ARRAY[1000], 'Check count from ID-taptest_Table_p20000_p27000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p20000_p28000"', ARRAY[1000], 'Check count from ID-taptest_Table_p20000_p28000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p20000_p29000"', ARRAY[1000], 'Check count from ID-taptest_Table_p20000_p29000'); SELECT is_empty('SELECT * FROM ONLY "Partman_test"."ID-taptest_Table_p30000"', 'Check that parent table ID-taptest_Table_p30000 has had data moved to partition '); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p30000_p30000"', ARRAY[1000], 'Check count from ID-taptest_Table_p30000_p30000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p30000_p31000"', ARRAY[1000], 'Check count from ID-taptest_Table_p30000_p31000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p30000_p32000"', ARRAY[1000], 'Check count from ID-taptest_Table_p30000_p32000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p30000_p33000"', ARRAY[1000], 'Check count from ID-taptest_Table_p30000_p33000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p30000_p34000"', ARRAY[1000], 'Check count from ID-taptest_Table_p30000_p34000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p30000_p35000"', ARRAY[1000], 'Check count from ID-taptest_Table_p30000_p35000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p30000_p36000"', ARRAY[1000], 'Check count from ID-taptest_Table_p30000_p36000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p30000_p37000"', ARRAY[1000], 'Check count from ID-taptest_Table_p30000_p37000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p30000_p38000"', ARRAY[1000], 'Check count from ID-taptest_Table_p30000_p38000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p30000_p39000"', ARRAY[1000], 'Check count from ID-taptest_Table_p30000_p39000'); SELECT is_empty('SELECT * FROM ONLY "Partman_test"."ID-taptest_Table_p40000"', 'Check that parent table ID-taptest_Table_p40000 has had data moved to partition '); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p40000_p40000"', ARRAY[1000], 'Check count from ID-taptest_Table_p40000_p40000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p40000_p41000"', ARRAY[1000], 'Check count from ID-taptest_Table_p40000_p41000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p40000_p42000"', ARRAY[1000], 'Check count from ID-taptest_Table_p40000_p42000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p40000_p43000"', ARRAY[1000], 'Check count from ID-taptest_Table_p40000_p43000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p40000_p44000"', ARRAY[1000], 'Check count from ID-taptest_Table_p40000_p44000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p40000_p45000"', ARRAY[1000], 'Check count from ID-taptest_Table_p40000_p45000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p40000_p46000"', ARRAY[1000], 'Check count from ID-taptest_Table_p40000_p46000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p40000_p47000"', ARRAY[1000], 'Check count from ID-taptest_Table_p40000_p47000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p40000_p48000"', ARRAY[1000], 'Check count from ID-taptest_Table_p40000_p48000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p40000_p49000"', ARRAY[1000], 'Check count from ID-taptest_Table_p40000_p49000'); SELECT is_empty('SELECT * FROM ONLY "Partman_test"."ID-taptest_Table_p50000"', 'Check that parent table ID-taptest_Table_p50000 has had data moved to partition '); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p50000_p50000"', ARRAY[1000], 'Check count from ID-taptest_Table_p50000_p50000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p50000_p51000"', ARRAY[1000], 'Check count from ID-taptest_Table_p50000_p51000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p50000_p52000"', ARRAY[1000], 'Check count from ID-taptest_Table_p50000_p52000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p50000_p53000"', ARRAY[1000], 'Check count from ID-taptest_Table_p50000_p53000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p50000_p54000"', ARRAY[1000], 'Check count from ID-taptest_Table_p50000_p54000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p50000_p55000"', ARRAY[1000], 'Check count from ID-taptest_Table_p50000_p55000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p50000_p56000"', ARRAY[1000], 'Check count from ID-taptest_Table_p50000_p56000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p50000_p57000"', ARRAY[1000], 'Check count from ID-taptest_Table_p50000_p57000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p50000_p58000"', ARRAY[1000], 'Check count from ID-taptest_Table_p50000_p58000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p50000_p59000"', ARRAY[1000], 'Check count from ID-taptest_Table_p50000_p59000'); SELECT is_empty('SELECT * FROM ONLY "Partman_test"."ID-taptest_Table_p60000"', 'Check that parent table ID-taptest_Table_p60000 has had data moved to partition '); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p60000_p60000"', ARRAY[1000], 'Check count from ID-taptest_Table_p60000_p60000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p60000_p61000"', ARRAY[1000], 'Check count from ID-taptest_Table_p60000_p61000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p60000_p62000"', ARRAY[1000], 'Check count from ID-taptest_Table_p60000_p62000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p60000_p63000"', ARRAY[1000], 'Check count from ID-taptest_Table_p60000_p63000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p60000_p64000"', ARRAY[1000], 'Check count from ID-taptest_Table_p60000_p64000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p60000_p65000"', ARRAY[1000], 'Check count from ID-taptest_Table_p60000_p65000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p60000_p66000"', ARRAY[1000], 'Check count from ID-taptest_Table_p60000_p66000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p60000_p67000"', ARRAY[1000], 'Check count from ID-taptest_Table_p60000_p67000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p60000_p68000"', ARRAY[1000], 'Check count from ID-taptest_Table_p60000_p68000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p60000_p69000"', ARRAY[1000], 'Check count from ID-taptest_Table_p60000_p69000'); SELECT is_empty('SELECT * FROM ONLY "Partman_test"."ID-taptest_Table_p70000"', 'Check that parent table ID-taptest_Table_p70000 has had data moved to partition '); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p70000_p70000"', ARRAY[1000], 'Check count from ID-taptest_Table_p70000_p70000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p70000_p71000"', ARRAY[1000], 'Check count from ID-taptest_Table_p70000_p71000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p70000_p72000"', ARRAY[1000], 'Check count from ID-taptest_Table_p70000_p72000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p70000_p73000"', ARRAY[1000], 'Check count from ID-taptest_Table_p70000_p73000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p70000_p74000"', ARRAY[1000], 'Check count from ID-taptest_Table_p70000_p74000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p70000_p75000"', ARRAY[1000], 'Check count from ID-taptest_Table_p70000_p75000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p70000_p76000"', ARRAY[1000], 'Check count from ID-taptest_Table_p70000_p76000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p70000_p77000"', ARRAY[1000], 'Check count from ID-taptest_Table_p70000_p77000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p70000_p78000"', ARRAY[1000], 'Check count from ID-taptest_Table_p70000_p78000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p70000_p79000"', ARRAY[1000], 'Check count from ID-taptest_Table_p70000_p79000'); SELECT is_empty('SELECT * FROM ONLY "Partman_test"."ID-taptest_Table_p80000"', 'Check that parent table ID-taptest_Table_p80000 has had data moved to partition '); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p80000_p80000"', ARRAY[1000], 'Check count from ID-taptest_Table_p80000_p80000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p80000_p81000"', ARRAY[1000], 'Check count from ID-taptest_Table_p80000_p81000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p80000_p82000"', ARRAY[1000], 'Check count from ID-taptest_Table_p80000_p82000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p80000_p83000"', ARRAY[1000], 'Check count from ID-taptest_Table_p80000_p83000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p80000_p84000"', ARRAY[1000], 'Check count from ID-taptest_Table_p80000_p84000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p80000_p85000"', ARRAY[1000], 'Check count from ID-taptest_Table_p80000_p85000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p80000_p86000"', ARRAY[1000], 'Check count from ID-taptest_Table_p80000_p86000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p80000_p87000"', ARRAY[1000], 'Check count from ID-taptest_Table_p80000_p87000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p80000_p88000"', ARRAY[1000], 'Check count from ID-taptest_Table_p80000_p88000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p80000_p89000"', ARRAY[1000], 'Check count from ID-taptest_Table_p80000_p89000'); SELECT is_empty('SELECT * FROM ONLY "Partman_test"."ID-taptest_Table_p90000"', 'Check that parent table ID-taptest_Table_p90000 has had data moved to partition '); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p90000_p90000"', ARRAY[1000], 'Check count from ID-taptest_Table_p90000_p90000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p90000_p91000"', ARRAY[1000], 'Check count from ID-taptest_Table_p90000_p91000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p90000_p92000"', ARRAY[1000], 'Check count from ID-taptest_Table_p90000_p92000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p90000_p93000"', ARRAY[1000], 'Check count from ID-taptest_Table_p90000_p93000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p90000_p94000"', ARRAY[1000], 'Check count from ID-taptest_Table_p90000_p94000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p90000_p95000"', ARRAY[1000], 'Check count from ID-taptest_Table_p90000_p95000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p90000_p96000"', ARRAY[1000], 'Check count from ID-taptest_Table_p90000_p96000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p90000_p97000"', ARRAY[1000], 'Check count from ID-taptest_Table_p90000_p97000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p90000_p98000"', ARRAY[1000], 'Check count from ID-taptest_Table_p90000_p98000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p90000_p99000"', ARRAY[1000], 'Check count from ID-taptest_Table_p90000_p99000'); SELECT is_empty('SELECT * FROM ONLY "Partman_test"."ID-taptest_Table_p100000"', 'Check that parent table ID-taptest_Table_p100000 has had data moved to partition '); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p100000_p100000"', ARRAY[1], 'Check count from ID-taptest_Table_p100000_p100000'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p0_p0', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p0_p0'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p0_p1000', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p0_p1000'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p0_p2000', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p0_p2000'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p0_p3000', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p0_p3000'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p0_p4000', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p0_p4000'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p0_p5000', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p0_p5000'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p0_p6000', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p0_p6000'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p0_p7000', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p0_p7000'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p0_p8000', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p0_p8000'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p0_p9000', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p0_p9000'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p90000_p90000', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p90000_p90000'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p90000_p91000', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p90000_p91000'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p90000_p92000', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p90000_p92000'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p90000_p93000', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p90000_p93000'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p90000_p94000', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p90000_p94000'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p90000_p95000', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p90000_p95000'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p90000_p96000', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p90000_p96000'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p90000_p97000', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p90000_p97000'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p90000_p98000', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p90000_p98000'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p90000_p99000', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p90000_p99000'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p100000_p100000', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p100000_p100000'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p100000_p101000', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p100000_p101000'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p100000_p102000', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p100000_p102000'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p110000_p110000', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p110000_p110000'); SELECT col_is_fk('Partman_test', 'ID-taptest_Table_p10000_p10000', ARRAY['col2', 'col4'], 'Check for inherited foreign key in ID-taptest_Table_p10000_10000'); SELECT col_is_fk('Partman_test', 'ID-taptest_Table_p10000_p11000', ARRAY['col2', 'col4'], 'Check for inherited foreign key in ID-taptest_Table_p10000_11000'); SELECT col_is_fk('Partman_test', 'ID-taptest_Table_p10000_p12000', ARRAY['col2', 'col4'], 'Check for inherited foreign key in ID-taptest_Table_p10000_12000'); SELECT col_is_fk('Partman_test', 'ID-taptest_Table_p10000_p13000', ARRAY['col2', 'col4'], 'Check for inherited foreign key in ID-taptest_Table_p10000_13000'); SELECT col_is_fk('Partman_test', 'ID-taptest_Table_p10000_p14000', ARRAY['col2', 'col4'], 'Check for inherited foreign key in ID-taptest_Table_p10000_14000'); SELECT col_is_fk('Partman_test', 'ID-taptest_Table_p10000_p15000', ARRAY['col2', 'col4'], 'Check for inherited foreign key in ID-taptest_Table_p10000_15000'); SELECT col_is_fk('Partman_test', 'ID-taptest_Table_p10000_p16000', ARRAY['col2', 'col4'], 'Check for inherited foreign key in ID-taptest_Table_p10000_16000'); SELECT col_is_fk('Partman_test', 'ID-taptest_Table_p10000_p17000', ARRAY['col2', 'col4'], 'Check for inherited foreign key in ID-taptest_Table_p10000_17000'); SELECT col_is_fk('Partman_test', 'ID-taptest_Table_p10000_p18000', ARRAY['col2', 'col4'], 'Check for inherited foreign key in ID-taptest_Table_p10000_18000'); SELECT col_is_fk('Partman_test', 'ID-taptest_Table_p10000_p19000', ARRAY['col2', 'col4'], 'Check for inherited foreign key in ID-taptest_Table_p10000_19000'); SELECT col_is_fk('Partman_test', 'ID-taptest_Table_p100000_p100000', ARRAY['col2', 'col4'], 'Check for inherited foreign key in ID-taptest_Table_p100000_100000'); SELECT col_is_fk('Partman_test', 'ID-taptest_Table_p100000_p101000', ARRAY['col2', 'col4'], 'Check for inherited foreign key in ID-taptest_Table_p100000_101000'); SELECT col_is_fk('Partman_test', 'ID-taptest_Table_p100000_p102000', ARRAY['col2', 'col4'], 'Check for inherited foreign key in ID-taptest_Table_p100000_102000'); SELECT col_is_fk('Partman_test', 'ID-taptest_Table_p110000_p110000', ARRAY['col2', 'col4'], 'Check for inherited foreign key in ID-taptest_Table_p110000_110000'); -- Remove & Replace some rows to test hybrid trigger dynamic fallback DELETE FROM "Partman_test"."ID-taptest_Table_p40000"; SELECT is_empty('SELECT * FROM "Partman_test"."ID-taptest_Table_p40000"', 'Make sure test table with deleted rows is empty'); SELECT is_empty('SELECT * FROM "Partman_test"."ID-taptest_Table_p40000_p40000"', 'Make sure test table with deleted rows is empty'); INSERT INTO "Partman_test"."ID-taptest_Table" ("COL1") VALUES (generate_series(40000,49999)); -- Check that all is back as it was SELECT is_empty('SELECT * FROM ONLY "Partman_test"."ID-taptest_Table"', 'Check that parent table top is empty'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table"', ARRAY[100000], 'Check count from parent table'); SELECT is_empty('SELECT * FROM ONLY "Partman_test"."ID-taptest_Table_p40000"', 'Check that parent table ID-taptest_Table_p40000 is empty'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p40000_p40000"', ARRAY[1000], 'Check count from ID-taptest_Table_p40000_p40000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p40000_p41000"', ARRAY[1000], 'Check count from ID-taptest_Table_p40000_p41000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p40000_p42000"', ARRAY[1000], 'Check count from ID-taptest_Table_p40000_p42000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p40000_p43000"', ARRAY[1000], 'Check count from ID-taptest_Table_p40000_p43000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p40000_p44000"', ARRAY[1000], 'Check count from ID-taptest_Table_p40000_p44000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p40000_p45000"', ARRAY[1000], 'Check count from ID-taptest_Table_p40000_p45000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p40000_p46000"', ARRAY[1000], 'Check count from ID-taptest_Table_p40000_p46000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p40000_p47000"', ARRAY[1000], 'Check count from ID-taptest_Table_p40000_p47000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p40000_p48000"', ARRAY[1000], 'Check count from ID-taptest_Table_p40000_p48000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p40000_p49000"', ARRAY[1000], 'Check count from ID-taptest_Table_p40000_p49000'); -- Check that static part of trigger is working INSERT INTO "Partman_test"."ID-taptest_Table" ("COL1") VALUES (generate_series(100001,102000)); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table"', ARRAY[102000], 'Check count from parent table'); SELECT is_empty('SELECT * FROM ONLY "Partman_test"."ID-taptest_Table"', 'Check that parent table top is empty'); SELECT is_empty('SELECT * FROM ONLY "Partman_test"."ID-taptest_Table_p100000"', 'Check that parent table ID-taptest_Table_p100000 is empty'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p100000_p100000"', ARRAY[1000], 'Check count from ID-taptest_Table_p100000_p100000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p100000_p101000"', ARRAY[1000], 'Check count from ID-taptest_Table_p100000_p101000'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."ID-taptest_Table_p100000_p102000"', ARRAY[1], 'Check count from ID-taptest_Table_p100000_p102000'); SELECT run_maintenance(); SELECT has_table('Partman_test', 'ID-taptest_Table_p100000_p103000', 'Check ID-taptest_Table_p100000_p103000 exists'); SELECT has_table('Partman_test', 'ID-taptest_Table_p100000_p104000', 'Check ID-taptest_Table_p100000_p104000 exists'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p100000_p105000', 'Check ID-taptest_Table_p100000_p105000 does not exist'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p100000_p103000', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p100000_p103000'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p100000_p104000', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p100000_p104000'); SELECT col_is_fk('Partman_test', 'ID-taptest_Table_p100000_p103000', ARRAY['col2', 'col4'], 'Check for inherited foreign key in ID-taptest_Table_p100000_p103000'); SELECT col_is_fk('Partman_test', 'ID-taptest_Table_p100000_p104000', ARRAY['col2', 'col4'], 'Check for inherited foreign key in ID-taptest_Table_p100000_p104000'); SELECT has_table('Partman_test', 'ID-taptest_Table_p120000', 'Check "ID-taptest_Table_p120000 exists'); SELECT col_is_pk('Partman_test', 'ID-taptest_Table_p120000', ARRAY['COL1'], 'Check for primary key in ID-taptest_Table_p120000'); SELECT col_is_fk('Partman_test', 'ID-taptest_Table_p120000', ARRAY['col2', 'col4'], 'Check for inherited foreign key in ID-taptest_Table_p120000'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p130000', 'Check "ID-taptest_Table_p130000 does not exist'); -- Testing retention for 50000 on top parent -- Retention for the sub-partitions won't work due to the nature of how it's done (current partition max - retention value). The max in each partition has no bearing on the overall integer value unlike time subpartitioning UPDATE part_config SET retention = '50000', retention_keep_table = false WHERE parent_table = 'Partman_test.ID-taptest_Table'; SELECT run_maintenance(); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p0', 'Check ID-taptest_Table_p0 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p0_p0', 'Check ID-taptest_Table_p0_p0 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p0_p1000', 'Check ID-taptest_Table_p0_p1000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p0_p2000', 'Check ID-taptest_Table_p0_p2000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p0_p3000', 'Check ID-taptest_Table_p0_p3000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p0_p4000', 'Check ID-taptest_Table_p0_p4000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p0_p5000', 'Check ID-taptest_Table_p0_p5000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p0_p6000', 'Check ID-taptest_Table_p0_p6000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p0_p7000', 'Check ID-taptest_Table_p0_p7000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p0_p8000', 'Check ID-taptest_Table_p0_p8000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p0_p9000', 'Check ID-taptest_Table_p0_p9000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p10000', 'Check ID-taptest_Table_p10000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p10000_p10000', 'Check ID-taptest_Table_p10000_p10000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p10000_p11000', 'Check ID-taptest_Table_p10000_p11000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p10000_p12000', 'Check ID-taptest_Table_p10000_p12000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p10000_p13000', 'Check ID-taptest_Table_p10000_p13000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p10000_p14000', 'Check ID-taptest_Table_p10000_p14000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p10000_p15000', 'Check ID-taptest_Table_p10000_p15000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p10000_p16000', 'Check ID-taptest_Table_p10000_p16000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p10000_p17000', 'Check ID-taptest_Table_p10000_p17000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p10000_p18000', 'Check ID-taptest_Table_p10000_p18000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p10000_p19000', 'Check ID-taptest_Table_p10000_p19000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p20000', 'Check ID-taptest_Table_p20000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p20000_p20000', 'Check ID-taptest_Table_p20000_p20000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p20000_p21000', 'Check ID-taptest_Table_p20000_p21000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p20000_p22000', 'Check ID-taptest_Table_p20000_p22000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p20000_p23000', 'Check ID-taptest_Table_p20000_p23000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p20000_p24000', 'Check ID-taptest_Table_p20000_p24000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p20000_p25000', 'Check ID-taptest_Table_p20000_p25000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p20000_p26000', 'Check ID-taptest_Table_p20000_p26000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p20000_p27000', 'Check ID-taptest_Table_p20000_p27000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p20000_p28000', 'Check ID-taptest_Table_p20000_p28000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p20000_p29000', 'Check ID-taptest_Table_p20000_p29000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p30000', 'Check ID-taptest_Table_p30000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p30000_p30000', 'Check ID-taptest_Table_p30000_p30000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p30000_p31000', 'Check ID-taptest_Table_p30000_p31000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p30000_p32000', 'Check ID-taptest_Table_p30000_p32000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p30000_p33000', 'Check ID-taptest_Table_p30000_p33000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p30000_p34000', 'Check ID-taptest_Table_p30000_p34000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p30000_p35000', 'Check ID-taptest_Table_p30000_p35000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p30000_p36000', 'Check ID-taptest_Table_p30000_p36000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p30000_p37000', 'Check ID-taptest_Table_p30000_p37000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p30000_p38000', 'Check ID-taptest_Table_p30000_p38000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p30000_p39000', 'Check ID-taptest_Table_p30000_p39000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p40000', 'Check ID-taptest_Table_p40000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p40000_p40000', 'Check ID-taptest_Table_p40000_p40000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p40000_p41000', 'Check ID-taptest_Table_p40000_p41000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p40000_p42000', 'Check ID-taptest_Table_p40000_p42000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p40000_p43000', 'Check ID-taptest_Table_p40000_p43000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p40000_p44000', 'Check ID-taptest_Table_p40000_p44000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p40000_p45000', 'Check ID-taptest_Table_p40000_p45000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p40000_p46000', 'Check ID-taptest_Table_p40000_p46000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p40000_p47000', 'Check ID-taptest_Table_p40000_p47000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p40000_p48000', 'Check ID-taptest_Table_p40000_p48000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p40000_p49000', 'Check ID-taptest_Table_p40000_p49000 does not exist'); -- Make sure 50000 are still here SELECT has_table('Partman_test', 'ID-taptest_Table_p50000', 'Check ID-taptest_Table_p50000 still exists after maintenance'); SELECT has_table('Partman_test', 'ID-taptest_Table_p50000_p50000', 'Check ID-taptest_Table_p50000_p50000 still exists after maintenance'); SELECT undo_partition('Partman_test.ID-taptest_Table_p50000', 20, p_keep_table := false); SELECT undo_partition('Partman_test.ID-taptest_Table_p60000', 20, p_keep_table := false); SELECT undo_partition('Partman_test.ID-taptest_Table_p70000', 20, p_keep_table := false); SELECT undo_partition('Partman_test.ID-taptest_Table_p80000', 20, p_keep_table := false); SELECT undo_partition('Partman_test.ID-taptest_Table_p90000', 20, p_keep_table := false); SELECT undo_partition('Partman_test.ID-taptest_Table_p100000', 20, p_keep_table := false); SELECT undo_partition('Partman_test.ID-taptest_Table_p110000', 20, p_keep_table := false); SELECT undo_partition('Partman_test.ID-taptest_Table_p120000', 20, p_keep_table := false); SELECT undo_partition('Partman_test.ID-taptest_Table', 20, p_keep_table := false); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p50000', 'Check ID-taptest_Table_p50000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p50000_p50000', 'Check ID-taptest_Table_p50000_p50000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p50000_p51000', 'Check ID-taptest_Table_p50000_p51000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p50000_p52000', 'Check ID-taptest_Table_p50000_p52000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p50000_p53000', 'Check ID-taptest_Table_p50000_p53000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p50000_p54000', 'Check ID-taptest_Table_p50000_p44000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p50000_p55000', 'Check ID-taptest_Table_p50000_p55000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p50000_p56000', 'Check ID-taptest_Table_p50000_p56000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p50000_p57000', 'Check ID-taptest_Table_p50000_p57000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p50000_p58000', 'Check ID-taptest_Table_p50000_p58000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p50000_p59000', 'Check ID-taptest_Table_p50000_p59000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p60000', 'Check ID-taptest_Table_p60000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p60000_p60000', 'Check ID-taptest_Table_p60000_p60000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p60000_p61000', 'Check ID-taptest_Table_p60000_p61000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p60000_p62000', 'Check ID-taptest_Table_p60000_p62000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p60000_p63000', 'Check ID-taptest_Table_p60000_p63000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p60000_p64000', 'Check ID-taptest_Table_p60000_p64000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p60000_p65000', 'Check ID-taptest_Table_p60000_p65000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p60000_p66000', 'Check ID-taptest_Table_p60000_p66000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p60000_p67000', 'Check ID-taptest_Table_p60000_p67000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p60000_p68000', 'Check ID-taptest_Table_p60000_p68000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p60000_p69000', 'Check ID-taptest_Table_p60000_p69000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p70000', 'Check ID-taptest_Table_p70000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p70000_p70000', 'Check ID-taptest_Table_p70000_p70000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p70000_p71000', 'Check ID-taptest_Table_p70000_p71000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p70000_p72000', 'Check ID-taptest_Table_p70000_p72000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p70000_p73000', 'Check ID-taptest_Table_p70000_p73000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p70000_p74000', 'Check ID-taptest_Table_p70000_p74000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p70000_p75000', 'Check ID-taptest_Table_p70000_p75000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p70000_p76000', 'Check ID-taptest_Table_p70000_p76000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p70000_p77000', 'Check ID-taptest_Table_p70000_p77000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p70000_p78000', 'Check ID-taptest_Table_p70000_p78000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p70000_p79000', 'Check ID-taptest_Table_p70000_p79000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p80000', 'Check ID-taptest_Table_p80000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p80000_p80000', 'Check ID-taptest_Table_p80000_p80000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p80000_p81000', 'Check ID-taptest_Table_p80000_p81000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p80000_p82000', 'Check ID-taptest_Table_p80000_p82000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p80000_p83000', 'Check ID-taptest_Table_p80000_p83000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p80000_p84000', 'Check ID-taptest_Table_p80000_p84000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p80000_p85000', 'Check ID-taptest_Table_p80000_p85000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p80000_p86000', 'Check ID-taptest_Table_p80000_p86000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p80000_p87000', 'Check ID-taptest_Table_p80000_p87000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p80000_p88000', 'Check ID-taptest_Table_p80000_p88000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p80000_p89000', 'Check ID-taptest_Table_p80000_p89000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p90000', 'Check ID-taptest_Table_p90000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p90000_p90000', 'Check ID-taptest_Table_p90000_p90000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p90000_p91000', 'Check ID-taptest_Table_p90000_p91000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p90000_p92000', 'Check ID-taptest_Table_p90000_p92000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p90000_p93000', 'Check ID-taptest_Table_p90000_p93000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p90000_p94000', 'Check ID-taptest_Table_p90000_p94000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p90000_p95000', 'Check ID-taptest_Table_p90000_p95000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p90000_p96000', 'Check ID-taptest_Table_p90000_p96000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p90000_p97000', 'Check ID-taptest_Table_p90000_p97000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p90000_p98000', 'Check ID-taptest_Table_p90000_p98000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p90000_p99000', 'Check ID-taptest_Table_p90000_p99000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p100000', 'Check ID-taptest_Table_p100000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p100000_p100000', 'Check ID-taptest_Table_p100000_p100000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p100000_p101000', 'Check ID-taptest_Table_p100000_p101000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p100000_p102000', 'Check ID-taptest_Table_p100000_p102000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p100000_p103000', 'Check ID-taptest_Table_p100000_p103000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p100000_p104000', 'Check ID-taptest_Table_p100000_p104000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p110000', 'Check ID-taptest_Table_p110000 does not exist'); SELECT hasnt_table('Partman_test', 'ID-taptest_Table_p120000', 'Check ID-taptest_Table_p120000 does not exist'); SELECT is_empty('SELECT * FROM part_config WHERE parent_table LIKE ''Partman_test.ID-taptest_Table''', 'Check that part_config table is empty'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test-id-nonsuperuser.sql000066400000000000000000000265671412761706100215150ustar00rootroot00000000000000-- ########## ID TESTS ########## -- Additional tests: UNLOGGED, Make sure option to not inherit foreign keys works, larger than necessary p_batch_count to partition_data_id(), retention, PUBLIC role \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(66); CREATE SCHEMA partman_test; CREATE SCHEMA partman_retention_test; CREATE TABLE partman_test.fk_test_reference (col2 text unique not null); INSERT INTO partman_test.fk_test_reference VALUES ('stuff'); CREATE UNLOGGED TABLE partman_test.id_taptest_table ( col1 bigint primary key , col2 text not null default 'stuff' references partman_test.fk_test_reference (col2) , col3 timestamptz DEFAULT now()); INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(3000000001,3000000009)); SELECT results_eq('SELECT create_parent(''partman_test.id_taptest_table'', ''col1'', ''partman'', ''10'', p_inherit_fk := false)::text', ARRAY['true'], 'Check that create_parent() returns true'); SELECT has_table('partman_test', 'id_taptest_table_p3000000000', 'Check id_taptest_table_p3000000000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p3000000010', 'Check id_taptest_table_p3000000010 exists'); SELECT has_table('partman_test', 'id_taptest_table_p3000000020', 'Check id_taptest_table_p3000000020 exists'); SELECT has_table('partman_test', 'id_taptest_table_p3000000030', 'Check id_taptest_table_p3000000030 exists'); SELECT has_table('partman_test', 'id_taptest_table_p3000000040', 'Check id_taptest_table_p3000000040 exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p3000000050', 'Check id_taptest_table_p3000000050 doesn''t exists yet'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000000', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000000'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000010', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000010'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000020', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000020'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000030', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000030'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000040', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000040'); SELECT col_isnt_fk('partman_test', 'id_taptest_table_p3000000000', 'col2', 'Check that foreign key was NOT inherited to id_taptest_table_p3000000000'); SELECT col_isnt_fk('partman_test', 'id_taptest_table_p3000000010', 'col2', 'Check that foreign key was NOT inherited to id_taptest_table_p3000000010'); SELECT col_isnt_fk('partman_test', 'id_taptest_table_p3000000020', 'col2', 'Check that foreign key was NOT inherited to id_taptest_table_p3000000020'); SELECT col_isnt_fk('partman_test', 'id_taptest_table_p3000000030', 'col2', 'Check that foreign key was NOT inherited to id_taptest_table_p3000000030'); SELECT col_isnt_fk('partman_test', 'id_taptest_table_p3000000040', 'col2', 'Check that foreign key was NOT inherited to id_taptest_table_p3000000040'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table''::regclass', ARRAY['u'], 'Check that parent table is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p3000000000''::regclass', ARRAY['u'], 'Check that id_taptest_table_p3000000000 is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p3000000010''::regclass', ARRAY['u'], 'Check that id_taptest_table_p3000000010 is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p3000000020''::regclass', ARRAY['u'], 'Check that id_taptest_table_p3000000020 is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p3000000030''::regclass', ARRAY['u'], 'Check that id_taptest_table_p3000000030 is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p3000000040''::regclass', ARRAY['u'], 'Check that id_taptest_table_p3000000040 is unlogged'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table'', p_batch_count := 5)::int', ARRAY[9], 'Check that partitioning function returns correct count of rows moved'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[9], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p3000000000', ARRAY[9], 'Check count from id_taptest_table_p3000000000'); INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(3000000010,3000000025)); SELECT run_maintenance(p_analyze := false); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p3000000010', ARRAY[10], 'Check count from id_taptest_table_p3000000010'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p3000000020', ARRAY[6], 'Check count from id_taptest_table_p3000000020'); SELECT has_table('partman_test', 'id_taptest_table_p3000000050', 'Check id_taptest_table_p3000000050 exists'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p3000000050''::regclass', ARRAY['u'], 'Check that id_taptest_table_p3000000050 is unlogged'); SELECT has_table('partman_test', 'id_taptest_table_p3000000060', 'Check id_taptest_table_p3000000060 exists'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p3000000060''::regclass', ARRAY['u'], 'Check that id_taptest_table_p3000000060 is unlogged'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000050', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000050'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000060', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000060'); SELECT col_isnt_fk('partman_test', 'id_taptest_table_p3000000050', 'col2', 'Check that foreign key was NOT inherited to id_taptest_table_p3000000050'); SELECT col_isnt_fk('partman_test', 'id_taptest_table_p3000000060', 'col2', 'Check that foreign key was NOT inherited to id_taptest_table_p3000000060'); SELECT hasnt_table('partman_test', 'id_taptest_table_p3000000070', 'Check id_taptest_table_p3000000070 doesn''t exists yet'); INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(3000000026,3000000038)); SELECT run_maintenance(p_analyze := false); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[38], 'Check count from id_taptest_table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p3000000020', ARRAY[10], 'Check count from id_taptest_table_p3000000020'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p3000000030', ARRAY[9], 'Check count from id_taptest_table_p3000000030'); SELECT has_table('partman_test', 'id_taptest_table_p3000000070', 'Check id_taptest_table_p3000000070 exists'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p3000000070''::regclass', ARRAY['u'], 'Check that id_taptest_table_p3000000070 is unlogged'); SELECT hasnt_table('partman_test', 'id_taptest_table_p3000000080', 'Check id_taptest_table_p3000000080 doesn''t exists yet'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000070', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000070'); SELECT col_isnt_fk('partman_test', 'id_taptest_table_p3000000070', 'col2', 'Check that foreign key was NOT inherited to id_taptest_table_p3000000070'); INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(3000000200,3000000210)); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT reapply_privileges('partman_test.id_taptest_table'); -- Max value is 38 above SELECT drop_partition_id('partman_test.id_taptest_table', '20', p_keep_table := false); SELECT hasnt_table('partman_test', 'id_taptest_table_p3000000000', 'Check id_taptest_table_p3000000000 doesn''t exists anymore'); UPDATE part_config SET retention = '10' WHERE parent_table = 'partman_test.id_taptest_table'; SELECT drop_partition_id('partman_test.id_taptest_table', p_retention_schema := 'partman_retention_test'); SELECT hasnt_table('partman_test', 'id_taptest_table_p3000000010', 'Check id_taptest_table_p3000000010 doesn''t exists anymore'); SELECT has_table('partman_retention_test', 'id_taptest_table_p3000000010', 'Check id_taptest_table_p3000000010 got moved to new schema'); SELECT results_eq('SELECT partitions_undone::int, rows_undone::int FROM undo_partition(''partman_test.id_taptest_table'', p_keep_table := false)' , $$VALUES(1, 10)$$ , 'Check that undo_partition() returns expected values for undoing single table (drop table)'); SELECT hasnt_table('partman_test', 'id_taptest_table_p3000000020', 'Check id_taptest_table_p3000000020 does not exist'); -- Test keeping the rest of the tables SELECT results_eq('SELECT partitions_undone::int, rows_undone::int FROM undo_partition(''partman_test.id_taptest_table'', 10)' , $$VALUES(5, 9)$$ , 'Check that undo_partition() returns expected values for undoing all tables (keep table)'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table', ARRAY[30], 'Check count from parent table after undo'); SELECT has_table('partman_test', 'id_taptest_table_p3000000030', 'Check id_taptest_table_p3000000030 still exists'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p3000000030', 'Check child table had its data removed id_taptest_table_p3000000030'); SELECT has_table('partman_test', 'id_taptest_table_p3000000040', 'Check id_taptest_table_p3000000040 still exists'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p3000000040', 'Check child table had its data removed id_taptest_table_p3000000040'); SELECT has_table('partman_test', 'id_taptest_table_p3000000050', 'Check id_taptest_table_p3000000050 still exists'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p3000000050', 'Check child table had its data removed id_taptest_table_p3000000050'); SELECT has_table('partman_test', 'id_taptest_table_p3000000060', 'Check id_taptest_table_p3000000060 still exists'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p3000000060', 'Check child table had its data removed id_taptest_table_p3000000060'); SELECT has_table('partman_test', 'id_taptest_table_p3000000070', 'Check id_taptest_table_p3000000070 still exists'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p3000000070', 'Check child table had its data removed id_taptest_table_p3000000070'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test-id-run-maint.sql000066400000000000000000000533111412761706100206420ustar00rootroot00000000000000-- ########## ID TESTS ########## -- Other tests: Single column Foreign Key, Use run_maintenance(), additional constraint single column, rows that hit dynamic fallback -- This test not really needed as of 3.0.0 since all ID partitions now use automatic_maintenance. -- Instead, this is a test with automatic_maintenance = off \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(138); CREATE SCHEMA partman_test; CREATE ROLE partman_basic; CREATE ROLE partman_revoke; CREATE ROLE partman_owner; CREATE TABLE partman_test.fk_test_reference (col2 text unique not null); INSERT INTO partman_test.fk_test_reference VALUES ('stuff'); CREATE TABLE partman_test.id_taptest_table ( col1 int primary key , col2 text not null default 'stuff' references partman_test.fk_test_reference (col2) ON UPDATE CASCADE DEFERRABLE INITIALLY IMMEDIATE , col3 timestamptz DEFAULT now()); INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(1,9)); GRANT SELECT,INSERT,UPDATE ON partman_test.id_taptest_table TO partman_basic; GRANT ALL ON partman_test.id_taptest_table TO partman_revoke; SELECT create_parent('partman_test.id_taptest_table', 'col1', 'partman', '10', '{"col3"}', p_automatic_maintenance := 'off'); UPDATE part_config SET optimize_constraint = 4 WHERE parent_table = 'partman_test.id_taptest_table'; SELECT has_table('partman_test', 'id_taptest_table_p0', 'Check id_taptest_table_p0 exists'); SELECT has_table('partman_test', 'id_taptest_table_p10', 'Check id_taptest_table_p10 exists'); SELECT has_table('partman_test', 'id_taptest_table_p20', 'Check id_taptest_table_p20 exists'); SELECT has_table('partman_test', 'id_taptest_table_p30', 'Check id_taptest_table_p30 exists'); SELECT has_table('partman_test', 'id_taptest_table_p40', 'Check id_taptest_table_p40 exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50', 'Check id_taptest_table_p50 doesn''t exists yet'); SELECT col_is_pk('partman_test', 'id_taptest_table_p0', ARRAY['col1'], 'Check for primary key in id_taptest_table_p0'); SELECT col_is_pk('partman_test', 'id_taptest_table_p10', ARRAY['col1'], 'Check for primary key in id_taptest_table_p10'); SELECT col_is_pk('partman_test', 'id_taptest_table_p20', ARRAY['col1'], 'Check for primary key in id_taptest_table_p20'); SELECT col_is_pk('partman_test', 'id_taptest_table_p30', ARRAY['col1'], 'Check for primary key in id_taptest_table_p30'); SELECT col_is_pk('partman_test', 'id_taptest_table_p40', ARRAY['col1'], 'Check for primary key in id_taptest_table_p40'); SELECT table_privs_are('partman_test', 'id_taptest_table_p0', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p0'); SELECT table_privs_are('partman_test', 'id_taptest_table_p10', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p10'); SELECT table_privs_are('partman_test', 'id_taptest_table_p20', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p20'); SELECT table_privs_are('partman_test', 'id_taptest_table_p30', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p30'); SELECT table_privs_are('partman_test', 'id_taptest_table_p40', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p40'); SELECT table_privs_are('partman_test', 'id_taptest_table_p0', 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of id_taptest_table_p0'); SELECT table_privs_are('partman_test', 'id_taptest_table_p10', 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of id_taptest_table_p10'); SELECT table_privs_are('partman_test', 'id_taptest_table_p20', 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of id_taptest_table_p20'); SELECT table_privs_are('partman_test', 'id_taptest_table_p30', 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of id_taptest_table_p30'); SELECT table_privs_are('partman_test', 'id_taptest_table_p40', 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of id_taptest_table_p40'); SELECT col_is_fk('partman_test', 'id_taptest_table_p0', 'col2', 'Check that id_taptest_table_p0 inherited foreign key'); SELECT col_is_fk('partman_test', 'id_taptest_table_p10', 'col2', 'Check that id_taptest_table_p10 inherited foreign key'); SELECT col_is_fk('partman_test', 'id_taptest_table_p20', 'col2', 'Check that id_taptest_table_p20 inherited foreign key'); SELECT col_is_fk('partman_test', 'id_taptest_table_p30', 'col2', 'Check that id_taptest_table_p30 inherited foreign key'); SELECT col_is_fk('partman_test', 'id_taptest_table_p40', 'col2', 'Check that id_taptest_table_p40 inherited foreign key'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table'')::int', ARRAY[9], 'Check that partitioning function returns correct count of rows moved'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[9], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0', ARRAY[9], 'Check count from id_taptest_table_p0'); REVOKE INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON partman_test.id_taptest_table FROM partman_revoke; INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(10,25)); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10', ARRAY[10], 'Check count from id_taptest_table_p10'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20', ARRAY[6], 'Check count from id_taptest_table_p20'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50', 'Check id_taptest_table_p50 doesn''t exists yet'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60', 'Check id_taptest_table_p60 doesn''t exists yet'); SELECT run_maintenance('partman_test.id_taptest_table'); SELECT has_table('partman_test', 'id_taptest_table_p50', 'Check id_taptest_table_p50 exists'); SELECT has_table('partman_test', 'id_taptest_table_p60', 'Check id_taptest_table_p60 exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p70', 'Check id_taptest_table_p70 doesn''t exist yet'); SELECT col_is_pk('partman_test', 'id_taptest_table_p50', ARRAY['col1'], 'Check for primary key in id_taptest_table_p50'); SELECT col_is_fk('partman_test', 'id_taptest_table_p50', 'col2', 'Check that id_taptest_table_p50 inherited foreign key'); SELECT col_is_fk('partman_test', 'id_taptest_table_p60', 'col2', 'Check that id_taptest_table_p60 inherited foreign key'); SELECT table_privs_are('partman_test', 'id_taptest_table_p0', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p0'); SELECT table_privs_are('partman_test', 'id_taptest_table_p10', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p10'); SELECT table_privs_are('partman_test', 'id_taptest_table_p20', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p20'); SELECT table_privs_are('partman_test', 'id_taptest_table_p30', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p30'); SELECT table_privs_are('partman_test', 'id_taptest_table_p40', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p40'); SELECT table_privs_are('partman_test', 'id_taptest_table_p50', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p50'); SELECT table_privs_are('partman_test', 'id_taptest_table_p50', 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of id_taptest_table_p50'); GRANT DELETE ON partman_test.id_taptest_table TO partman_basic; REVOKE ALL ON partman_test.id_taptest_table FROM partman_revoke; ALTER TABLE partman_test.id_taptest_table OWNER TO partman_owner; -- Skipped 26-29 to allow dynamic fallback insertion test below INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(30,60)); SELECT run_maintenance('partman_test.id_taptest_table'); -- Check for additional constraint on date column SELECT col_has_check('partman_test', 'id_taptest_table_p10', 'col3', 'Check for additional constraint on col3 on id_taptest_table_p10'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[56], 'Check count from id_taptest_table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20', ARRAY[6], 'Check count from id_taptest_table_p20'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30', ARRAY[10], 'Check count from id_taptest_table_p30'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40', ARRAY[10], 'Check count from id_taptest_table_p40'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50', ARRAY[10], 'Check count from id_taptest_table_p50'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60', ARRAY[1], 'Check count from id_taptest_table_p60'); SELECT has_table('partman_test', 'id_taptest_table_p60', 'Check id_taptest_table_p60 exists'); SELECT has_table('partman_test', 'id_taptest_table_p70', 'Check id_taptest_table_p70 exists'); SELECT has_table('partman_test', 'id_taptest_table_p80', 'Check id_taptest_table_p80 exists'); SELECT has_table('partman_test', 'id_taptest_table_p90', 'Check id_taptest_table_p90 exists'); SELECT has_table('partman_test', 'id_taptest_table_p100', 'Check id_taptest_table_p100 exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p110', 'Check id_taptest_table_p110 doesn''t exists yet'); SELECT col_is_pk('partman_test', 'id_taptest_table_p60', ARRAY['col1'], 'Check for primary key in id_taptest_table_p60'); SELECT col_is_pk('partman_test', 'id_taptest_table_p70', ARRAY['col1'], 'Check for primary key in id_taptest_table_p70'); SELECT col_is_pk('partman_test', 'id_taptest_table_p80', ARRAY['col1'], 'Check for primary key in id_taptest_table_p80'); SELECT col_is_pk('partman_test', 'id_taptest_table_p90', ARRAY['col1'], 'Check for primary key in id_taptest_table_p90'); SELECT col_is_pk('partman_test', 'id_taptest_table_p100', ARRAY['col1'], 'Check for primary key in id_taptest_table_p100'); SELECT col_is_fk('partman_test', 'id_taptest_table_p60', 'col2', 'Check that id_taptest_table_p60 inherited foreign key'); SELECT col_is_fk('partman_test', 'id_taptest_table_p70', 'col2', 'Check that id_taptest_table_p70 inherited foreign key'); SELECT col_is_fk('partman_test', 'id_taptest_table_p80', 'col2', 'Check that id_taptest_table_p80 inherited foreign key'); SELECT col_is_fk('partman_test', 'id_taptest_table_p90', 'col2', 'Check that id_taptest_table_p90 inherited foreign key'); SELECT col_is_fk('partman_test', 'id_taptest_table_p100', 'col2', 'Check that id_taptest_table_p100 inherited foreign key'); SELECT table_privs_are('partman_test', 'id_taptest_table_p0', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p0'); SELECT table_privs_are('partman_test', 'id_taptest_table_p10', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p10'); SELECT table_privs_are('partman_test', 'id_taptest_table_p20', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p20'); SELECT table_privs_are('partman_test', 'id_taptest_table_p30', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p30'); SELECT table_privs_are('partman_test', 'id_taptest_table_p40', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p40'); SELECT table_privs_are('partman_test', 'id_taptest_table_p50', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p50'); SELECT table_privs_are('partman_test', 'id_taptest_table_p50', 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of id_taptest_table_p50'); SELECT table_privs_are('partman_test', 'id_taptest_table_p60', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p60'); SELECT table_privs_are('partman_test', 'id_taptest_table_p60', 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of id_taptest_table_p60'); SELECT table_privs_are('partman_test', 'id_taptest_table_p70', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p70'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p70', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p70'); SELECT table_privs_are('partman_test', 'id_taptest_table_p70', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p70'); SELECT table_privs_are('partman_test', 'id_taptest_table_p80', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p80'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p80', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p80'); SELECT table_privs_are('partman_test', 'id_taptest_table_p80', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p80'); SELECT table_privs_are('partman_test', 'id_taptest_table_p90', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p90'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p90', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p90'); SELECT table_privs_are('partman_test', 'id_taptest_table_p90', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p90'); SELECT table_privs_are('partman_test', 'id_taptest_table_p100', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p100'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p100', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p100'); SELECT table_privs_are('partman_test', 'id_taptest_table_p100', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p100'); -- Test for dynamic fallback insertion (static test should now cover 60-110) INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(26,29)); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had no data inserted to it after dynamic fallback'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[60], 'Check count from id_taptest_table after dynamic fallback'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20', ARRAY[10], 'Check count from id_taptest_table_p20 after dynamic fallback'); INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(200,210)); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT reapply_privileges('partman_test.id_taptest_table'); SELECT table_privs_are('partman_test', 'id_taptest_table_p0', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p0'); SELECT table_privs_are('partman_test', 'id_taptest_table_p10', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p10'); SELECT table_privs_are('partman_test', 'id_taptest_table_p20', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p20'); SELECT table_privs_are('partman_test', 'id_taptest_table_p30', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p30'); SELECT table_privs_are('partman_test', 'id_taptest_table_p40', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p40'); SELECT table_privs_are('partman_test', 'id_taptest_table_p50', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p50'); SELECT table_privs_are('partman_test', 'id_taptest_table_p60', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p60'); SELECT table_privs_are('partman_test', 'id_taptest_table_p70', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p70'); SELECT table_privs_are('partman_test', 'id_taptest_table_p0', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p0'); SELECT table_privs_are('partman_test', 'id_taptest_table_p10', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p10'); SELECT table_privs_are('partman_test', 'id_taptest_table_p20', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p20'); SELECT table_privs_are('partman_test', 'id_taptest_table_p30', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p30'); SELECT table_privs_are('partman_test', 'id_taptest_table_p40', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p40'); SELECT table_privs_are('partman_test', 'id_taptest_table_p50', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p50'); SELECT table_privs_are('partman_test', 'id_taptest_table_p60', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p60'); SELECT table_privs_are('partman_test', 'id_taptest_table_p70', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p70'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p0', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p0'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p10', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p10'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p20', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p20'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p30', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p30'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p40', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p40'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p50', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p50'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p60', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p60'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p70', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p70'); SELECT undo_partition('partman_test.id_taptest_table', 10); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table', ARRAY[71], 'Check count from parent table after undo'); SELECT has_table('partman_test', 'id_taptest_table_p0', 'Check id_taptest_table_p0 still exists'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p0', 'Check child table had its data removed id_taptest_table_p0'); SELECT has_table('partman_test', 'id_taptest_table_p10', 'Check id_taptest_table_p10 still exists'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p10', 'Check child table had its data removed id_taptest_table_p10'); SELECT has_table('partman_test', 'id_taptest_table_p20', 'Check id_taptest_table_p20 still exists'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p20', 'Check child table had its data removed id_taptest_table_p20'); SELECT has_table('partman_test', 'id_taptest_table_p30', 'Check id_taptest_table_p30 still exists'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p30', 'Check child table had its data removed id_taptest_table_p30'); SELECT has_table('partman_test', 'id_taptest_table_p40', 'Check id_taptest_table_p40 still exists'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p40', 'Check child table had its data removed id_taptest_table_p40'); SELECT has_table('partman_test', 'id_taptest_table_p50', 'Check id_taptest_table_p50 still exists'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p50', 'Check child table had its data removed id_taptest_table_p50'); SELECT has_table('partman_test', 'id_taptest_table_p60', 'Check id_taptest_table_p60 still exists'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p60', 'Check child table had its data removed id_taptest_table_p60'); SELECT has_table('partman_test', 'id_taptest_table_p70', 'Check id_taptest_table_p70 still exists'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p70', 'Check child table had its data removed id_taptest_table_p70'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test-id-start-100.sql000066400000000000000000000310071412761706100203610ustar00rootroot00000000000000-- ########## ID TESTS ########## -- Other tests: additional constraints multi column with update, start @ value of 100, undo_partition() full return of values \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(88); CREATE SCHEMA partman_test; CREATE ROLE partman_basic; CREATE ROLE partman_owner; CREATE TABLE partman_test.id_taptest_table (col1 int primary key, col2 text, col3 timestamptz DEFAULT now()); INSERT INTO partman_test.id_taptest_table (col1, col2) VALUES (generate_series(100,109), 'stuff'||generate_series(100,109)); GRANT SELECT,INSERT,UPDATE ON partman_test.id_taptest_table TO partman_basic; SELECT create_parent('partman_test.id_taptest_table', 'col1', 'partman', '10'); -- Default optimize_constraint is 30, so set it equal to premake for when this test was originally written UPDATE partman.part_config SET constraint_cols = '{"col2", "col3"}', optimize_constraint = 4 WHERE parent_table = 'partman_test.id_taptest_table'; SELECT has_table('partman_test', 'id_taptest_table_p100', 'Check id_taptest_table_p100 exists'); SELECT has_table('partman_test', 'id_taptest_table_p110', 'Check id_taptest_table_p110 exists'); SELECT has_table('partman_test', 'id_taptest_table_p120', 'Check id_taptest_table_p120 exists'); SELECT has_table('partman_test', 'id_taptest_table_p130', 'Check id_taptest_table_p130 exists'); SELECT has_table('partman_test', 'id_taptest_table_p140', 'Check id_taptest_table_p140 exists'); SELECT has_table('partman_test', 'id_taptest_table_p90', 'Check id_taptest_table_p90 exists'); SELECT has_table('partman_test', 'id_taptest_table_p80', 'Check id_taptest_table_p80 exists'); SELECT has_table('partman_test', 'id_taptest_table_p70', 'Check id_taptest_table_p70 exists'); SELECT has_table('partman_test', 'id_taptest_table_p60', 'Check id_taptest_table_p60 exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50', 'Check id_taptest_table_p50 doesn''t exists yet'); SELECT hasnt_table('partman_test', 'id_taptest_table_p150', 'Check id_taptest_table_p150 doesn''t exists yet'); SELECT col_is_pk('partman_test', 'id_taptest_table_p100', ARRAY['col1'], 'Check for primary key in id_taptest_table_p100'); SELECT col_is_pk('partman_test', 'id_taptest_table_p110', ARRAY['col1'], 'Check for primary key in id_taptest_table_p110'); SELECT col_is_pk('partman_test', 'id_taptest_table_p120', ARRAY['col1'], 'Check for primary key in id_taptest_table_p120'); SELECT col_is_pk('partman_test', 'id_taptest_table_p130', ARRAY['col1'], 'Check for primary key in id_taptest_table_p130'); SELECT col_is_pk('partman_test', 'id_taptest_table_p140', ARRAY['col1'], 'Check for primary key in id_taptest_table_p140'); SELECT col_is_pk('partman_test', 'id_taptest_table_p90', ARRAY['col1'], 'Check for primary key in id_taptest_table_p90'); SELECT col_is_pk('partman_test', 'id_taptest_table_p80', ARRAY['col1'], 'Check for primary key in id_taptest_table_p80'); SELECT col_is_pk('partman_test', 'id_taptest_table_p70', ARRAY['col1'], 'Check for primary key in id_taptest_table_p70'); SELECT col_is_pk('partman_test', 'id_taptest_table_p60', ARRAY['col1'], 'Check for primary key in id_taptest_table_p60'); SELECT table_privs_are('partman_test', 'id_taptest_table_p100', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p100'); SELECT table_privs_are('partman_test', 'id_taptest_table_p110', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p110'); SELECT table_privs_are('partman_test', 'id_taptest_table_p120', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p120'); SELECT table_privs_are('partman_test', 'id_taptest_table_p130', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p130'); SELECT table_privs_are('partman_test', 'id_taptest_table_p140', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p140'); SELECT table_privs_are('partman_test', 'id_taptest_table_p90', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p90'); SELECT table_privs_are('partman_test', 'id_taptest_table_p80', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p80'); SELECT table_privs_are('partman_test', 'id_taptest_table_p70', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p70'); SELECT table_privs_are('partman_test', 'id_taptest_table_p60', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p60'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table'')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[10], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100', ARRAY[10], 'Check count from id_taptest_table_p100'); INSERT INTO partman_test.id_taptest_table (col1, col2) VALUES (generate_series(60,99), 'stuff'||generate_series(60,99)); SELECT run_maintenance(); INSERT INTO partman_test.id_taptest_table (col1, col2) VALUES (generate_series(110,145), 'stuff'||generate_series(110,145)); SELECT run_maintenance(); -- Check for additional constraints on text & date columns SELECT col_has_check('partman_test', 'id_taptest_table_p60', 'col2', 'Check for additional constraint on col2 on id_taptest_table_p60'); SELECT col_has_check('partman_test', 'id_taptest_table_p60', 'col3', 'Check for additional constraint on col3 on id_taptest_table_p60'); SELECT col_has_check('partman_test', 'id_taptest_table_p70', 'col2', 'Check for additional constraint on col2 on id_taptest_table_p70'); SELECT col_has_check('partman_test', 'id_taptest_table_p70', 'col3', 'Check for additional constraint on col3 on id_taptest_table_p70'); SELECT col_has_check('partman_test', 'id_taptest_table_p80', 'col2', 'Check for additional constraint on col2 on id_taptest_table_p80'); SELECT col_has_check('partman_test', 'id_taptest_table_p80', 'col3', 'Check for additional constraint on col3 on id_taptest_table_p80'); SELECT has_table('partman_test', 'id_taptest_table_p150', 'Check id_taptest_table_p150 exists'); SELECT has_table('partman_test', 'id_taptest_table_p160', 'Check id_taptest_table_p160 exists'); SELECT has_table('partman_test', 'id_taptest_table_p170', 'Check id_taptest_table_p170 exists'); SELECT has_table('partman_test', 'id_taptest_table_p180', 'Check id_taptest_table_p180 exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p190', 'Check id_taptest_table_p190 doesn''t exists yet'); SELECT col_is_pk('partman_test', 'id_taptest_table_p150', ARRAY['col1'], 'Check for primary key in id_taptest_table_p150'); SELECT col_is_pk('partman_test', 'id_taptest_table_p160', ARRAY['col1'], 'Check for primary key in id_taptest_table_p160'); SELECT col_is_pk('partman_test', 'id_taptest_table_p170', ARRAY['col1'], 'Check for primary key in id_taptest_table_p170'); SELECT col_is_pk('partman_test', 'id_taptest_table_p180', ARRAY['col1'], 'Check for primary key in id_taptest_table_p180'); SELECT table_privs_are('partman_test', 'id_taptest_table_p150', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p150'); SELECT table_privs_are('partman_test', 'id_taptest_table_p160', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p160'); SELECT table_privs_are('partman_test', 'id_taptest_table_p170', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p170'); SELECT table_privs_are('partman_test', 'id_taptest_table_p180', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p180'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[86], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60', ARRAY[10], 'Check count from id_taptest_table_p60'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70', ARRAY[10], 'Check count from id_taptest_table_p70'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80', ARRAY[10], 'Check count from id_taptest_table_p80'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90', ARRAY[10], 'Check count from id_taptest_table_p90'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100', ARRAY[10], 'Check count from id_taptest_table_p100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p110', ARRAY[10], 'Check count from id_taptest_table_p110'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p120', ARRAY[10], 'Check count from id_taptest_table_p120'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p130', ARRAY[10], 'Check count from id_taptest_table_p130'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p140', ARRAY[6], 'Check count from id_taptest_table_p140'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p150', 'Check that next is empty'); -- pgtap is weird and converts all returned values to text unless specifically cast SELECT results_eq('SELECT partitions_undone::int, rows_undone::int FROM undo_partition(''partman_test.id_taptest_table'', 20)' , $$VALUES (13, 86)$$ , 'Check that undo_partition() returns correct values'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table', ARRAY[86], 'Check count from parent table after undo'); SELECT has_table('partman_test', 'id_taptest_table_p60', 'Check id_taptest_table_p60 still exists'); SELECT has_table('partman_test', 'id_taptest_table_p70', 'Check id_taptest_table_p70 still exists'); SELECT has_table('partman_test', 'id_taptest_table_p80', 'Check id_taptest_table_p80 still exists'); SELECT has_table('partman_test', 'id_taptest_table_p90', 'Check id_taptest_table_p90 still exists'); SELECT has_table('partman_test', 'id_taptest_table_p100', 'Check id_taptest_table_p100 still exists'); SELECT has_table('partman_test', 'id_taptest_table_p110', 'Check id_taptest_table_p110 still exists'); SELECT has_table('partman_test', 'id_taptest_table_p120', 'Check id_taptest_table_p120 still exists'); SELECT has_table('partman_test', 'id_taptest_table_p130', 'Check id_taptest_table_p130 still exists'); SELECT has_table('partman_test', 'id_taptest_table_p140', 'Check id_taptest_table_p140 still exists'); SELECT has_table('partman_test', 'id_taptest_table_p150', 'Check id_taptest_table_p140 still exists'); SELECT has_table('partman_test', 'id_taptest_table_p160', 'Check id_taptest_table_p140 still exists'); SELECT has_table('partman_test', 'id_taptest_table_p170', 'Check id_taptest_table_p140 still exists'); SELECT has_table('partman_test', 'id_taptest_table_p180', 'Check id_taptest_table_p180 still exists'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60', ARRAY[0], 'Check count from id_taptest_table_p60'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70', ARRAY[0], 'Check count from id_taptest_table_p70'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80', ARRAY[0], 'Check count from id_taptest_table_p80'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90', ARRAY[0], 'Check count from id_taptest_table_p90'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100', ARRAY[0], 'Check count from id_taptest_table_p100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p110', ARRAY[0], 'Check count from id_taptest_table_p110'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p120', ARRAY[0], 'Check count from id_taptest_table_p120'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p130', ARRAY[0], 'Check count from id_taptest_table_p130'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p140', ARRAY[0], 'Check count from id_taptest_table_p140'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test-id-start-partition.sql000066400000000000000000000164721412761706100221030ustar00rootroot00000000000000-- ########## ID TESTS ########## -- Test p_start_partition parameter \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(51); CREATE SCHEMA partman_test; CREATE SCHEMA partman_retention_test; CREATE TABLE partman_test.id_taptest_table (col1 int primary key, col2 text, col3 timestamptz DEFAULT now()); INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(151,159)); SELECT create_parent('partman_test.id_taptest_table', 'col1', 'partman', '10', p_start_partition := '150'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0', 'Check id_taptest_table_p0 does not exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p140', 'Check id_taptest_table_p140 does not exists'); SELECT has_table('partman_test', 'id_taptest_table_p150', 'Check id_taptest_table_p150 exists'); SELECT has_table('partman_test', 'id_taptest_table_p160', 'Check id_taptest_table_p160 exists'); SELECT has_table('partman_test', 'id_taptest_table_p170', 'Check id_taptest_table_p170 exists'); SELECT has_table('partman_test', 'id_taptest_table_p180', 'Check id_taptest_table_p180 exists'); SELECT has_table('partman_test', 'id_taptest_table_p190', 'Check id_taptest_table_p190 exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p200', 'Check id_taptest_table_p200 doesn''t exists yet'); SELECT col_is_pk('partman_test', 'id_taptest_table_p150', ARRAY['col1'], 'Check for primary key in id_taptest_table_p150'); SELECT col_is_pk('partman_test', 'id_taptest_table_p160', ARRAY['col1'], 'Check for primary key in id_taptest_table_p160'); SELECT col_is_pk('partman_test', 'id_taptest_table_p170', ARRAY['col1'], 'Check for primary key in id_taptest_table_p170'); SELECT col_is_pk('partman_test', 'id_taptest_table_p180', ARRAY['col1'], 'Check for primary key in id_taptest_table_p180'); SELECT col_is_pk('partman_test', 'id_taptest_table_p190', ARRAY['col1'], 'Check for primary key in id_taptest_table_p190'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table'')::int', ARRAY[9], 'Check that partitioning function returns correct count of rows moved'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[9], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p150', ARRAY[9], 'Check count from id_taptest_table_p150'); INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(160,175)); SELECT run_maintenance(); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p160', ARRAY[10], 'Check count from id_taptest_table_p160'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p170', ARRAY[6], 'Check count from id_taptest_table_p170'); SELECT has_table('partman_test', 'id_taptest_table_p200', 'Check id_taptest_table_p200 exists'); SELECT has_table('partman_test', 'id_taptest_table_p210', 'Check id_taptest_table_p210 exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p220', 'Check id_taptest_table_p220 doesn''t exists yet'); SELECT col_is_pk('partman_test', 'id_taptest_table_p200', ARRAY['col1'], 'Check for primary key in id_taptest_table_p200'); SELECT col_is_pk('partman_test', 'id_taptest_table_p210', ARRAY['col1'], 'Check for primary key in id_taptest_table_p210'); INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(176,205)); SELECT run_maintenance(); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[55], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p170', ARRAY[10], 'Check count from id_taptest_table_p170'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p180', ARRAY[10], 'Check count from id_taptest_table_p180'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p190', ARRAY[10], 'Check count from id_taptest_table_p190'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p200', ARRAY[6], 'Check count from id_taptest_table_p200'); SELECT has_table('partman_test', 'id_taptest_table_p220', 'Check id_taptest_table_p220 exists'); SELECT has_table('partman_test', 'id_taptest_table_p230', 'Check id_taptest_table_p230 exists'); SELECT has_table('partman_test', 'id_taptest_table_p240', 'Check id_taptest_table_p240 exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p250', 'Check id_taptest_table_p250 doesn''t exists yet'); SELECT col_is_pk('partman_test', 'id_taptest_table_p220', ARRAY['col1'], 'Check for primary key in id_taptest_table_p220'); SELECT col_is_pk('partman_test', 'id_taptest_table_p230', ARRAY['col1'], 'Check for primary key in id_taptest_table_p230'); SELECT col_is_pk('partman_test', 'id_taptest_table_p240', ARRAY['col1'], 'Check for primary key in id_taptest_table_p240'); INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(300,310)); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table', ARRAY[11], 'Check that data outside trigger scope goes to parent'); -- Remove rows in parent so drop function test can work properly DELETE FROM ONLY partman_test.id_taptest_table; SELECT drop_partition_id('partman_test.id_taptest_table', '45', p_keep_table := false); SELECT hasnt_table('partman_test', 'id_taptest_table_p150', 'Check id_taptest_table_p150 doesn''t exists anymore'); UPDATE part_config SET retention = '35' WHERE parent_table = 'partman_test.id_taptest_table'; SELECT drop_partition_id('partman_test.id_taptest_table', p_retention_schema := 'partman_retention_test'); SELECT hasnt_table('partman_test', 'id_taptest_table_p160', 'Check id_taptest_table_p160 doesn''t exists anymore'); SELECT has_table('partman_retention_test', 'id_taptest_table_p160', 'Check id_taptest_table_p160 got moved to new schema'); SELECT undo_partition('partman_test.id_taptest_table', 10, p_keep_table := false); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table', ARRAY[36], 'Check count from parent table after undo'); SELECT hasnt_table('partman_test', 'id_taptest_table_p170', 'Check id_taptest_table_p170 doesn''t exists anymore'); SELECT hasnt_table('partman_test', 'id_taptest_table_p180', 'Check id_taptest_table_p180 doesn''t exists anymore'); SELECT hasnt_table('partman_test', 'id_taptest_table_p190', 'Check id_taptest_table_p190 doesn''t exists anymore'); SELECT hasnt_table('partman_test', 'id_taptest_table_p200', 'Check id_taptest_table_p200 doesn''t exists anymore'); SELECT hasnt_table('partman_test', 'id_taptest_table_p210', 'Check id_taptest_table_p210 doesn''t exists anymore'); SELECT hasnt_table('partman_test', 'id_taptest_table_p220', 'Check id_taptest_table_p220 doesn''t exists anymore'); SELECT hasnt_table('partman_test', 'id_taptest_table_p230', 'Check id_taptest_table_p230 doesn''t exists anymore'); SELECT hasnt_table('partman_test', 'id_taptest_table_p240', 'Check id_taptest_table_p240 doesn''t exists anymore'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test-id-time-subpart.sql000066400000000000000000001601571412761706100213530ustar00rootroot00000000000000-- ########## ID PARENT / TIME SUBPARENT TESTS ########## -- Additional test: no pg_jobmon \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(260); CREATE SCHEMA partman_test; CREATE TABLE partman_test.id_taptest_table (col1 int primary key, col2 text, col3 timestamptz NOT NULL DEFAULT now()); INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(1,9)); SELECT partman.create_parent('partman_test.id_taptest_table', 'col1', 'partman', '10', '{"col3"}', p_jobmon := false); SELECT has_table('partman_test', 'id_taptest_table_p0', 'Check id_taptest_table_p0 exists'); SELECT has_table('partman_test', 'id_taptest_table_p10', 'Check id_taptest_table_p10 exists'); SELECT has_table('partman_test', 'id_taptest_table_p20', 'Check id_taptest_table_p20 exists'); SELECT has_table('partman_test', 'id_taptest_table_p30', 'Check id_taptest_table_p30 exists'); SELECT has_table('partman_test', 'id_taptest_table_p40', 'Check id_taptest_table_p40 exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50', 'Check id_taptest_table_p50 doesn''t exists yet'); SELECT col_is_pk('partman_test', 'id_taptest_table_p0', ARRAY['col1'], 'Check for primary key in id_taptest_table_p0'); SELECT col_is_pk('partman_test', 'id_taptest_table_p10', ARRAY['col1'], 'Check for primary key in id_taptest_table_p10'); SELECT col_is_pk('partman_test', 'id_taptest_table_p20', ARRAY['col1'], 'Check for primary key in id_taptest_table_p20'); SELECT col_is_pk('partman_test', 'id_taptest_table_p30', ARRAY['col1'], 'Check for primary key in id_taptest_table_p30'); SELECT col_is_pk('partman_test', 'id_taptest_table_p40', ARRAY['col1'], 'Check for primary key in id_taptest_table_p40'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table'')::int', ARRAY[9], 'Check that partitioning function returns correct count of rows moved'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[9], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0', ARRAY[9], 'Check count from id_taptest_table_p0'); -- Create subpartition SELECT partman.create_sub_parent('partman_test.id_taptest_table', 'col3', 'partman', 'daily'); -- p0 SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in d_dynamic_table_p0_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); -- Move data to new subpartitions SELECT results_eq('SELECT partition_data_time(''partman_test.id_taptest_table_p0'', p_batch_count := 5)::int', ARRAY[9], 'Check that partitioning function returns correct count of rows moved'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had data moved to partition'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p0', 'Check that subparent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0', ARRAY[9], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY[9], 'Check count from id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check count from id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' (should be empty)'); -- p10 SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in d_dynamic_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10', 'Check that subparent table has had data moved to partition'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p10', 'Check count from parent table _p10 (should be empty)'); -- p20 SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in d_dynamic_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p20', 'Check that subparent table itself _p20 is empty'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p20', 'Check count from parent table _p20 (should be empty)'); -- p30 SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in d_dynamic_table_p30_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p30', 'Check that subparent table itself _p30 is empty'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p30', 'Check count from parent table _p30 (should be empty)'); -- p40 SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in d_dynamic_table_p40_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p40', 'Check that subparent table itself _p40 is empty'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p40', 'Check count from parent table _p40 (should be empty)'); -- insertion round 2 INSERT INTO partman_test.id_taptest_table (col1, col3) VALUES (generate_series(10,20), CURRENT_TIMESTAMP+'1 day'::interval); SELECT run_maintenance(); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had data moved to partition'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p0', 'Check that subparent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0', ARRAY[9], 'Check count from parent table partman_test.id_taptest_table_p0'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY[9], 'Check count from id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10', 'Check that subparent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10', ARRAY[10], 'Check count from parent table partman_test.id_taptest_table_p10'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY[10], 'Check count from id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p20', 'Check that subparent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20', ARRAY[1], 'Check count from parent table partman_test.id_taptest_table_p20'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY[1], 'Check count from id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); -- p50 SELECT has_table('partman_test', 'id_taptest_table_p50', 'Check id_taptest_table_p50 exists'); SELECT col_is_pk('partman_test', 'id_taptest_table_p50', ARRAY['col1'], 'Check for primary key in id_taptest_table_p50'); SELECT has_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT has_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in d_dynamic_table_p50_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had data moved to partition'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p50', 'Check that subparent table itself _p50 is empty'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p50', 'Check count from parent table _p50 (should be empty)'); -- p60 SELECT has_table('partman_test', 'id_taptest_table_p60', 'Check id_taptest_table_p60 exists'); SELECT col_is_pk('partman_test', 'id_taptest_table_p60', ARRAY['col1'], 'Check for primary key in id_taptest_table_p60'); SELECT has_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT has_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in d_dynamic_table_p60_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had data moved to partition'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p60', 'Check that subparent table itself _p60 is empty'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p60', 'Check count from parent table _p60 (should be empty)'); SELECT hasnt_table('partman_test', 'id_taptest_table_p70', 'Check id_taptest_table_p70 doesn''t exists yet'); -- make sure new data is where it should be SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10', 'Check count from only parent table _p10 (should be empty)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10', ARRAY[10], 'Check count from subparent table id_taptest_table_p10'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY[10], 'Check count from id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20', ARRAY[1], 'Check count from subparent table id_taptest_table_p20'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY[1], 'Check count from id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); -- Ensure time partitioning works for all sub partitions UPDATE part_config SET premake = 5, optimize_trigger = 5 WHERE parent_table ~ 'partman_test.id_taptest_table_p' AND partition_type = 'partman'; SELECT run_maintenance(); -- Check for new time sub-partitions -- Time data only exists for now() in _p0, so only one additional table will be created SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); -- Time data exists for now+1 day in p10, so 2 additional tables will be created SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); -- Time data exists for now+1 day in p20, so 2 additional tables will be created SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); -- No time data has been inserted for p30 and higher, so no more partitions should be created for them SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); -- Test dropping without retention set SELECT drop_partition_time ('partman_test.id_taptest_table_p0', '2 days', p_keep_table := false); SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' still exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); UPDATE part_config SET retention = '10', retention_keep_table = false WHERE parent_table = 'partman_test.id_taptest_table'; UPDATE part_config SET retention = '2 days', retention_keep_table = false WHERE parent_table = 'partman_test.id_taptest_table_p0'; UPDATE part_config SET retention = '2 days', retention_keep_table = false WHERE parent_table = 'partman_test.id_taptest_table_p10'; UPDATE part_config SET retention = '2 days', retention_keep_table = false WHERE parent_table = 'partman_test.id_taptest_table_p20'; UPDATE part_config SET retention = '2 days', retention_keep_table = false WHERE parent_table = 'partman_test.id_taptest_table_p30'; UPDATE part_config SET retention = '2 days', retention_keep_table = false WHERE parent_table = 'partman_test.id_taptest_table_p40'; UPDATE part_config SET retention = '2 days', retention_keep_table = false WHERE parent_table = 'partman_test.id_taptest_table_p50'; UPDATE part_config SET retention = '2 days', retention_keep_table = false WHERE parent_table = 'partman_test.id_taptest_table_p60'; -- Test dropping with it set SELECT run_maintenance(); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT undo_partition('partman_test.id_taptest_table_p10', 20, p_keep_table := false); SELECT undo_partition('partman_test.id_taptest_table_p20', 20, p_keep_table := false); SELECT undo_partition('partman_test.id_taptest_table_p30', 20, p_keep_table := false); SELECT undo_partition('partman_test.id_taptest_table_p40', 20, p_keep_table := false); SELECT undo_partition('partman_test.id_taptest_table_p50', 20, p_keep_table := false); SELECT undo_partition('partman_test.id_taptest_table_p60', 20, p_keep_table := false); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.id_taptest_table_p0''', 'Check that partman_test.id_taptest_table_p0 was removed from part_config'); SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.id_taptest_table_p10''', 'Check that partman_test.id_taptest_table_p10 was removed from part_config'); SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.id_taptest_table_p20''', 'Check that partman_test.id_taptest_table_p20 was removed from part_config'); SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.id_taptest_table_p30''', 'Check that partman_test.id_taptest_table_p30 was removed from part_config'); SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.id_taptest_table_p40''', 'Check that partman_test.id_taptest_table_p40 was removed from part_config'); SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.id_taptest_table_p50''', 'Check that partman_test.id_taptest_table_p50 was removed from part_config'); SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.id_taptest_table_p60''', 'Check that partman_test.id_taptest_table_p60 was removed from part_config'); -- Check top parent is still empty SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that top parent table has not had any data moved to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10', ARRAY[10], 'Check count from subparent table'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table'', 20, p_keep_table := false);', ARRAY[11], 'Check undo_partition returns correct number of rows'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0', 'Check id_taptest_table_p0 does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10', 'Check id_taptest_table_p10 does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20', 'Check id_taptest_table_p20 does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30', 'Check id_taptest_table_p30 does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40', 'Check id_taptest_table_p40 does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50', 'Check id_taptest_table_p50 does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60', 'Check id_taptest_table_p60 does not exist'); SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.id_taptest_table''', 'Check that partman_test.id_taptest_table was removed from part_config'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table', ARRAY[11], 'Check count from final unpartitioned table'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test-id-trunc.sql000066400000000000000000000625631412761706100200740ustar00rootroot00000000000000-- ########## ID TESTS ########## -- Other tests: Long name truncation \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(104); CREATE SCHEMA partman_test; CREATE ROLE partman_basic; CREATE ROLE partman_revoke; CREATE ROLE partman_owner; CREATE TABLE partman_test.id_taptest_table_123456789012345678901234567890123456789012345 (col1 int primary key, col2 text, col3 timestamptz DEFAULT now()); INSERT INTO partman_test.id_taptest_table_123456789012345678901234567890123456789012345 (col1) VALUES (generate_series(1,9)); GRANT SELECT,INSERT,UPDATE ON partman_test.id_taptest_table_123456789012345678901234567890123456789012345 TO partman_basic; GRANT ALL ON partman_test.id_taptest_table_123456789012345678901234567890123456789012345 TO partman_revoke; SELECT create_parent('partman_test.id_taptest_table_123456789012345678901234567890123456789012345', 'col1', 'partman', '10'); SELECT has_table('partman_test', 'id_taptest_table_1234567890123456789012345678901234567890123_p0', 'Check id_taptest_table_1234567890123456789012345678901234567890123_p0 exists'); SELECT has_table('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p10', 'Check id_taptest_table_123456789012345678901234567890123456789012_p10 exists'); SELECT has_table('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p20', 'Check id_taptest_table_123456789012345678901234567890123456789012_p20 exists'); SELECT has_table('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p30', 'Check id_taptest_table_123456789012345678901234567890123456789012_p30 exists'); SELECT has_table('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p40', 'Check id_taptest_table_123456789012345678901234567890123456789012_p40 exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p50', 'Check id_taptest_table_123456789012345678901234567890123456789012_p50 doesn''t exists yet'); SELECT col_is_pk('partman_test', 'id_taptest_table_1234567890123456789012345678901234567890123_p0', ARRAY['col1'], 'Check for primary key in id_taptest_table_1234567890123456789012345678901234567890123_p0'); SELECT col_is_pk('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p10', ARRAY['col1'], 'Check for primary key in id_taptest_table_123456789012345678901234567890123456789012_p10'); SELECT col_is_pk('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p20', ARRAY['col1'], 'Check for primary key in id_taptest_table_123456789012345678901234567890123456789012_p20'); SELECT col_is_pk('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p30', ARRAY['col1'], 'Check for primary key in id_taptest_table_123456789012345678901234567890123456789012_p30'); SELECT col_is_pk('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p40', ARRAY['col1'], 'Check for primary key in id_taptest_table_123456789012345678901234567890123456789012_p40'); SELECT table_privs_are('partman_test', 'id_taptest_table_1234567890123456789012345678901234567890123_p0', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_1234567890123456789012345678901234567890123_p0'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p10', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_123456789012345678901234567890123456789012_p10'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p20', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_123456789012345678901234567890123456789012_p20'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p30', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_123456789012345678901234567890123456789012_p30'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p40', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_123456789012345678901234567890123456789012_p40'); SELECT table_privs_are('partman_test', 'id_taptest_table_1234567890123456789012345678901234567890123_p0', 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of id_taptest_table_1234567890123456789012345678901234567890123_p0'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p10', 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of id_taptest_table_123456789012345678901234567890123456789012_p10'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p20', 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of id_taptest_table_123456789012345678901234567890123456789012_p20'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p30', 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of id_taptest_table_123456789012345678901234567890123456789012_p30'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p40', 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of id_taptest_table_123456789012345678901234567890123456789012_p40'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_123456789012345678901234567890123456789012345'')::int', ARRAY[9], 'Check that partitioning function returns correct count of rows moved'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_123456789012345678901234567890123456789012345', 'Check that parent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_123456789012345678901234567890123456789012345', ARRAY[9], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_1234567890123456789012345678901234567890123_p0', ARRAY[9], 'Check count from id_taptest_table_1234567890123456789012345678901234567890123_p0'); REVOKE INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON partman_test.id_taptest_table_123456789012345678901234567890123456789012345 FROM partman_revoke; INSERT INTO partman_test.id_taptest_table_123456789012345678901234567890123456789012345 (col1) VALUES (generate_series(10,25)); SELECT run_maintenance(); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_123456789012345678901234567890123456789012345', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_123456789012345678901234567890123456789012_p10', ARRAY[10], 'Check count from id_taptest_table_123456789012345678901234567890123456789012_p10'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_123456789012345678901234567890123456789012_p20', ARRAY[6], 'Check count from id_taptest_table_123456789012345678901234567890123456789012_p20'); SELECT has_table('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p50', 'Check id_taptest_table_123456789012345678901234567890123456789012_p50 exists'); SELECT has_table('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p60', 'Check id_taptest_table_123456789012345678901234567890123456789012_p60 exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p70', 'Check id_taptest_table_123456789012345678901234567890123456789012_p70 doesn''t exists yet'); SELECT col_is_pk('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p50', ARRAY['col1'], 'Check for primary key in id_taptest_table_123456789012345678901234567890123456789012_p50'); SELECT col_is_pk('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p60', ARRAY['col1'], 'Check for primary key in id_taptest_table_123456789012345678901234567890123456789012_p650'); SELECT table_privs_are('partman_test', 'id_taptest_table_1234567890123456789012345678901234567890123_p0', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_1234567890123456789012345678901234567890123_p0'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p10', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_123456789012345678901234567890123456789012_p10'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p20', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_123456789012345678901234567890123456789012_p20'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p30', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_123456789012345678901234567890123456789012_p30'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p40', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_123456789012345678901234567890123456789012_p40'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p50', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_123456789012345678901234567890123456789012_p50'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p60', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_123456789012345678901234567890123456789012_p60'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p50', 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of id_taptest_table_123456789012345678901234567890123456789012_p50'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p60', 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of id_taptest_table_123456789012345678901234567890123456789012_p60'); GRANT DELETE ON partman_test.id_taptest_table_123456789012345678901234567890123456789012345 TO partman_basic; REVOKE ALL ON partman_test.id_taptest_table_123456789012345678901234567890123456789012345 FROM partman_revoke; ALTER TABLE partman_test.id_taptest_table_123456789012345678901234567890123456789012345 OWNER TO partman_owner; INSERT INTO partman_test.id_taptest_table_123456789012345678901234567890123456789012345 (col1) VALUES (generate_series(26,38)); SELECT run_maintenance(); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_123456789012345678901234567890123456789012345', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_123456789012345678901234567890123456789012345', ARRAY[38], 'Check count from id_taptest_table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_123456789012345678901234567890123456789012_p20', ARRAY[10], 'Check count from id_taptest_table_123456789012345678901234567890123456789012_p20'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_123456789012345678901234567890123456789012_p30', ARRAY[9], 'Check count from id_taptest_table_123456789012345678901234567890123456789012_p30'); SELECT has_table('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p60', 'Check id_taptest_table_123456789012345678901234567890123456789012_p60 exists'); SELECT has_table('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p70', 'Check id_taptest_table_123456789012345678901234567890123456789012_p70 exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p80', 'Check id_taptest_table_123456789012345678901234567890123456789012_p80 doesn''t exists yet'); SELECT col_is_pk('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p60', ARRAY['col1'], 'Check for primary key in id_taptest_table_123456789012345678901234567890123456789012_p60'); SELECT col_is_pk('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p70', ARRAY['col1'], 'Check for primary key in id_taptest_table_123456789012345678901234567890123456789012_p70'); SELECT table_privs_are('partman_test', 'id_taptest_table_1234567890123456789012345678901234567890123_p0', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_1234567890123456789012345678901234567890123_p0'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p10', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_123456789012345678901234567890123456789012_p10'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p20', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_123456789012345678901234567890123456789012_p20'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p30', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_123456789012345678901234567890123456789012_p30'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p40', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_123456789012345678901234567890123456789012_p40'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p50', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_123456789012345678901234567890123456789012_p50'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p60', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_123456789012345678901234567890123456789012_p60'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p50', 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of id_taptest_table_123456789012345678901234567890123456789012_p50'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p70', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_123456789012345678901234567890123456789012_p70'); SELECT table_owner_is ('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p70', 'partman_owner', 'Check that ownership change worked for id_taptest_table_123456789012345678901234567890123456789012_p70'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p70', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_123456789012345678901234567890123456789012_p70'); INSERT INTO partman_test.id_taptest_table_123456789012345678901234567890123456789012345 (col1) VALUES (generate_series(200,210)); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_123456789012345678901234567890123456789012345', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT reapply_privileges('partman_test.id_taptest_table_123456789012345678901234567890123456789012345'); SELECT table_privs_are('partman_test', 'id_taptest_table_1234567890123456789012345678901234567890123_p0', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_1234567890123456789012345678901234567890123_p0'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p10', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_123456789012345678901234567890123456789012_p10'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p20', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_123456789012345678901234567890123456789012_p20'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p30', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_123456789012345678901234567890123456789012_p30'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p40', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_123456789012345678901234567890123456789012_p40'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p50', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_123456789012345678901234567890123456789012_p50'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p60', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_123456789012345678901234567890123456789012_p60'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p70', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_123456789012345678901234567890123456789012_p70'); SELECT table_privs_are('partman_test', 'id_taptest_table_1234567890123456789012345678901234567890123_p0', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_1234567890123456789012345678901234567890123_p0'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p10', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_123456789012345678901234567890123456789012_p10'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p20', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_123456789012345678901234567890123456789012_p20'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p30', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_123456789012345678901234567890123456789012_p30'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p40', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_123456789012345678901234567890123456789012_p40'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p50', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_123456789012345678901234567890123456789012_p50'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p60', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_123456789012345678901234567890123456789012_p60'); SELECT table_privs_are('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p70', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_123456789012345678901234567890123456789012_p70'); SELECT table_owner_is ('partman_test', 'id_taptest_table_1234567890123456789012345678901234567890123_p0', 'partman_owner', 'Check that ownership change worked for id_taptest_table_1234567890123456789012345678901234567890123_p0'); SELECT table_owner_is ('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p10', 'partman_owner', 'Check that ownership change worked for id_taptest_table_123456789012345678901234567890123456789012_p10'); SELECT table_owner_is ('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p20', 'partman_owner', 'Check that ownership change worked for id_taptest_table_123456789012345678901234567890123456789012_p20'); SELECT table_owner_is ('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p30', 'partman_owner', 'Check that ownership change worked for id_taptest_table_123456789012345678901234567890123456789012_p30'); SELECT table_owner_is ('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p40', 'partman_owner', 'Check that ownership change worked for id_taptest_table_123456789012345678901234567890123456789012_p40'); SELECT table_owner_is ('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p50', 'partman_owner', 'Check that ownership change worked for id_taptest_table_123456789012345678901234567890123456789012_p50'); SELECT table_owner_is ('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p60', 'partman_owner', 'Check that ownership change worked for id_taptest_table_123456789012345678901234567890123456789012_p60'); SELECT table_owner_is ('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p70', 'partman_owner', 'Check that ownership change worked for id_taptest_table_123456789012345678901234567890123456789012_p70'); SELECT undo_partition('partman_test.id_taptest_table_123456789012345678901234567890123456789012345', 10); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_123456789012345678901234567890123456789012345', ARRAY[49], 'Check count from parent table after undo'); SELECT has_table('partman_test', 'id_taptest_table_1234567890123456789012345678901234567890123_p0', 'Check id_taptest_table_1234567890123456789012345678901234567890123_p0 still exists'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_1234567890123456789012345678901234567890123_p0', 'Check child table had its data removed id_taptest_table_1234567890123456789012345678901234567890123_p0'); SELECT has_table('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p10', 'Check id_taptest_table_123456789012345678901234567890123456789012_p10 still exists'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_123456789012345678901234567890123456789012_p10', 'Check child table had its data removed id_taptest_table_123456789012345678901234567890123456789012_p10'); SELECT has_table('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p20', 'Check id_taptest_table_123456789012345678901234567890123456789012_p20 still exists'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_123456789012345678901234567890123456789012_p20', 'Check child table had its data removed id_taptest_table_123456789012345678901234567890123456789012_p20'); SELECT has_table('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p30', 'Check id_taptest_table_123456789012345678901234567890123456789012_p30 still exists'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_123456789012345678901234567890123456789012_p30', 'Check child table had its data removed id_taptest_table_123456789012345678901234567890123456789012_p30'); SELECT has_table('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p40', 'Check id_taptest_table_123456789012345678901234567890123456789012_p40 still exists'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_123456789012345678901234567890123456789012_p40', 'Check child table had its data removed id_taptest_table_123456789012345678901234567890123456789012_p40'); SELECT has_table('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p50', 'Check id_taptest_table_123456789012345678901234567890123456789012_p50 still exists'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_123456789012345678901234567890123456789012_p50', 'Check child table had its data removed id_taptest_table_123456789012345678901234567890123456789012_p50'); SELECT has_table('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p60', 'Check id_taptest_table_123456789012345678901234567890123456789012_p60 still exists'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_123456789012345678901234567890123456789012_p60', 'Check child table had its data removed id_taptest_table_123456789012345678901234567890123456789012_p60'); SELECT has_table('partman_test', 'id_taptest_table_123456789012345678901234567890123456789012_p70', 'Check id_taptest_table_123456789012345678901234567890123456789012_p70 still exists'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_123456789012345678901234567890123456789012_p70', 'Check child table had its data removed id_taptest_table_123456789012345678901234567890123456789012_p70'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test-id.sql000066400000000000000000000540721412761706100167370ustar00rootroot00000000000000-- ########## ID TESTS ########## -- Additional tests: turn off pg_jobmon logging, UNLOGGED, Make sure option to not inherit foreign keys works, larger than necessary p_batch_count to partition_data_id(), retention, PUBLIC role \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(121); CREATE SCHEMA partman_test; CREATE SCHEMA partman_retention_test; CREATE ROLE partman_basic; CREATE ROLE partman_revoke; CREATE ROLE partman_owner; CREATE TABLE partman_test.fk_test_reference (col2 text unique not null); INSERT INTO partman_test.fk_test_reference VALUES ('stuff'); CREATE UNLOGGED TABLE partman_test.id_taptest_table ( col1 bigint primary key , col2 text not null default 'stuff' references partman_test.fk_test_reference (col2) , col3 timestamptz DEFAULT now()); INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(3000000001,3000000009)); GRANT SELECT,INSERT,UPDATE ON partman_test.id_taptest_table TO partman_basic, PUBLIC; GRANT ALL ON partman_test.id_taptest_table TO partman_revoke; SELECT results_eq('SELECT create_parent(''partman_test.id_taptest_table'', ''col1'', ''partman'', ''10'', p_inherit_fk := false, p_jobmon := false)::text', ARRAY['true'], 'Check that create_parent() returns true'); SELECT has_table('partman_test', 'id_taptest_table_p3000000000', 'Check id_taptest_table_p3000000000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p3000000010', 'Check id_taptest_table_p3000000010 exists'); SELECT has_table('partman_test', 'id_taptest_table_p3000000020', 'Check id_taptest_table_p3000000020 exists'); SELECT has_table('partman_test', 'id_taptest_table_p3000000030', 'Check id_taptest_table_p3000000030 exists'); SELECT has_table('partman_test', 'id_taptest_table_p3000000040', 'Check id_taptest_table_p3000000040 exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p3000000050', 'Check id_taptest_table_p3000000050 doesn''t exists yet'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000000', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000000'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000010', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000010'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000020', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000020'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000030', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000030'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000040', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000040'); SELECT col_isnt_fk('partman_test', 'id_taptest_table_p3000000000', 'col2', 'Check that foreign key was NOT inherited to id_taptest_table_p3000000000'); SELECT col_isnt_fk('partman_test', 'id_taptest_table_p3000000010', 'col2', 'Check that foreign key was NOT inherited to id_taptest_table_p3000000010'); SELECT col_isnt_fk('partman_test', 'id_taptest_table_p3000000020', 'col2', 'Check that foreign key was NOT inherited to id_taptest_table_p3000000020'); SELECT col_isnt_fk('partman_test', 'id_taptest_table_p3000000030', 'col2', 'Check that foreign key was NOT inherited to id_taptest_table_p3000000030'); SELECT col_isnt_fk('partman_test', 'id_taptest_table_p3000000040', 'col2', 'Check that foreign key was NOT inherited to id_taptest_table_p3000000040'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000000', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000000'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000010', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000010'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000020', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000020'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000030', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000030'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000040', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000040'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000000', 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of id_taptest_table_p3000000000'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000010', 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of id_taptest_table_p3000000010'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000020', 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of id_taptest_table_p3000000020'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000030', 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of id_taptest_table_p3000000030'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000040', 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of id_taptest_table_p3000000040'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table''::regclass', ARRAY['u'], 'Check that parent table is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p3000000000''::regclass', ARRAY['u'], 'Check that id_taptest_table_p3000000000 is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p3000000010''::regclass', ARRAY['u'], 'Check that id_taptest_table_p3000000010 is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p3000000020''::regclass', ARRAY['u'], 'Check that id_taptest_table_p3000000020 is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p3000000030''::regclass', ARRAY['u'], 'Check that id_taptest_table_p3000000030 is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p3000000040''::regclass', ARRAY['u'], 'Check that id_taptest_table_p3000000040 is unlogged'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table'', p_batch_count := 5)::int', ARRAY[9], 'Check that partitioning function returns correct count of rows moved'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[9], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p3000000000', ARRAY[9], 'Check count from id_taptest_table_p3000000000'); REVOKE INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON partman_test.id_taptest_table FROM partman_revoke, PUBLIC; INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(3000000010,3000000025)); SELECT run_maintenance(p_analyze := false); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p3000000010', ARRAY[10], 'Check count from id_taptest_table_p3000000010'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p3000000020', ARRAY[6], 'Check count from id_taptest_table_p3000000020'); SELECT has_table('partman_test', 'id_taptest_table_p3000000050', 'Check id_taptest_table_p3000000050 exists'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p3000000050''::regclass', ARRAY['u'], 'Check that id_taptest_table_p3000000050 is unlogged'); SELECT has_table('partman_test', 'id_taptest_table_p3000000060', 'Check id_taptest_table_p3000000060 exists'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p3000000060''::regclass', ARRAY['u'], 'Check that id_taptest_table_p3000000060 is unlogged'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000050', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000050'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000060', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000060'); SELECT col_isnt_fk('partman_test', 'id_taptest_table_p3000000050', 'col2', 'Check that foreign key was NOT inherited to id_taptest_table_p3000000050'); SELECT col_isnt_fk('partman_test', 'id_taptest_table_p3000000060', 'col2', 'Check that foreign key was NOT inherited to id_taptest_table_p3000000060'); SELECT hasnt_table('partman_test', 'id_taptest_table_p3000000070', 'Check id_taptest_table_p3000000070 doesn''t exists yet'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000000', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000000'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000010', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000010'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000020', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000020'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000030', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000030'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000040', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000040'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000050', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000050'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000050', 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of id_taptest_table_p3000000050'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000060', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000060'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000060', 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of id_taptest_table_p3000000060'); GRANT DELETE ON partman_test.id_taptest_table TO partman_basic; REVOKE ALL ON partman_test.id_taptest_table FROM partman_revoke, PUBLIC; ALTER TABLE partman_test.id_taptest_table OWNER TO partman_owner; INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(3000000026,3000000038)); SELECT run_maintenance(p_analyze := false); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[38], 'Check count from id_taptest_table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p3000000020', ARRAY[10], 'Check count from id_taptest_table_p3000000020'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p3000000030', ARRAY[9], 'Check count from id_taptest_table_p3000000030'); SELECT has_table('partman_test', 'id_taptest_table_p3000000070', 'Check id_taptest_table_p3000000070 exists'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p3000000070''::regclass', ARRAY['u'], 'Check that id_taptest_table_p3000000070 is unlogged'); SELECT hasnt_table('partman_test', 'id_taptest_table_p3000000080', 'Check id_taptest_table_p3000000080 doesn''t exists yet'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000070', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000070'); SELECT col_isnt_fk('partman_test', 'id_taptest_table_p3000000070', 'col2', 'Check that foreign key was NOT inherited to id_taptest_table_p3000000070'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000000', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000000'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000010', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000010'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000020', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000020'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000030', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000030'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000040', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000040'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000050', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000050'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000050', 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of id_taptest_table_p3000000050'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000060', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000060'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000060', 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of id_taptest_table_p3000000060'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000070', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p3000000070'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p3000000070', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p3000000070'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000070', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p3000000070'); INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(3000000200,3000000210)); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT reapply_privileges('partman_test.id_taptest_table'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000000', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p3000000000'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000010', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p3000000010'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000020', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p3000000020'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000030', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p3000000030'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000040', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p3000000040'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000050', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p3000000050'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000060', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p3000000060'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000070', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p3000000070'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000000', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p3000000000'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000010', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p3000000010'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000020', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p3000000020'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000030', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p3000000030'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000040', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p3000000040'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000050', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p3000000050'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000060', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p3000000060'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000070', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p3000000070'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p3000000000', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p3000000000'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p3000000010', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p3000000010'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p3000000020', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p3000000020'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p3000000030', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p3000000030'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p3000000040', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p3000000040'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p3000000050', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p3000000050'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p3000000060', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p3000000060'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p3000000070', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p3000000070'); -- Max value is 38 above SELECT drop_partition_id('partman_test.id_taptest_table', '20', p_keep_table := false); SELECT hasnt_table('partman_test', 'id_taptest_table_p3000000000', 'Check id_taptest_table_p3000000000 doesn''t exists anymore'); UPDATE part_config SET retention = '10' WHERE parent_table = 'partman_test.id_taptest_table'; SELECT drop_partition_id('partman_test.id_taptest_table', p_retention_schema := 'partman_retention_test'); SELECT hasnt_table('partman_test', 'id_taptest_table_p3000000010', 'Check id_taptest_table_p3000000010 doesn''t exists anymore'); SELECT has_table('partman_retention_test', 'id_taptest_table_p3000000010', 'Check id_taptest_table_p3000000010 got moved to new schema'); SELECT results_eq('SELECT partitions_undone::int, rows_undone::int FROM undo_partition(''partman_test.id_taptest_table'', p_keep_table := false)' , $$VALUES(1, 10)$$ , 'Check that undo_partition() returns expected values for undoing single table (drop table)'); SELECT hasnt_table('partman_test', 'id_taptest_table_p3000000020', 'Check id_taptest_table_p3000000020 does not exist'); -- Test keeping the rest of the tables SELECT results_eq('SELECT partitions_undone::int, rows_undone::int FROM undo_partition(''partman_test.id_taptest_table'', 10)' , $$VALUES(5, 9)$$ , 'Check that undo_partition() returns expected values for undoing all tables (keep table)'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table', ARRAY[30], 'Check count from parent table after undo'); SELECT has_table('partman_test', 'id_taptest_table_p3000000030', 'Check id_taptest_table_p3000000030 still exists'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p3000000030', 'Check child table had its data removed id_taptest_table_p3000000030'); SELECT has_table('partman_test', 'id_taptest_table_p3000000040', 'Check id_taptest_table_p3000000040 still exists'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p3000000040', 'Check child table had its data removed id_taptest_table_p3000000040'); SELECT has_table('partman_test', 'id_taptest_table_p3000000050', 'Check id_taptest_table_p3000000050 still exists'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p3000000050', 'Check child table had its data removed id_taptest_table_p3000000050'); SELECT has_table('partman_test', 'id_taptest_table_p3000000060', 'Check id_taptest_table_p3000000060 still exists'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p3000000060', 'Check child table had its data removed id_taptest_table_p3000000060'); SELECT has_table('partman_test', 'id_taptest_table_p3000000070', 'Check id_taptest_table_p3000000070 still exists'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p3000000070', 'Check child table had its data removed id_taptest_table_p3000000070'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test-time-daily-returnnotnull.sql000066400000000000000000000311031412761706100233200ustar00rootroot00000000000000 -- ########## TIME DAILY TEST ########## -- Other tests: Ensure option to return not-null values in trigger is working; ensure drop column function is working \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(46); CREATE SCHEMA partman_test; CREATE TABLE partman_test.time_taptest_table (col1 int primary key, col2 text, col3 timestamptz NOT NULL DEFAULT now()); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); SELECT create_parent('partman_test.time_taptest_table', 'col3', 'partman', 'daily'); UPDATE part_config SET trigger_return_null = false WHERE parent_table = 'partman_test.time_taptest_table'; 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'); SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table'')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved'); 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')); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '1 day'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '2 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(26,30), CURRENT_TIMESTAMP + '3 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(31,37), CURRENT_TIMESTAMP + '4 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(40,49), CURRENT_TIMESTAMP - '1 day'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(50,70), CURRENT_TIMESTAMP - '2 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(71,85), CURRENT_TIMESTAMP - '3 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(86,100), CURRENT_TIMESTAMP - '4 days'::interval); -- Parent table will have gotten the same data inserted to it due to RETURN NEW SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[88], 'Check that parent table got same row count inserted as all child tables'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY[10], '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[5], '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[5], '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[7], '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[10], '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[21], '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[15], '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[15], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); SELECT run_maintenance(); SELECT has_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')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||' does not exist'); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(101,122), CURRENT_TIMESTAMP + '5 days'::interval); -- Parent table will have gotten the same data inserted to it due to RETURN NEW SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[110], 'Check that parent table got same row count inserted as all child tables'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY[22], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); -- Testing that trigger works when child table doesn't exist INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '20 days'::interval); -- Should only insert into parent once SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[121], 'Check that data outside trigger scope goes to parent'); -- Check that drop_column() function works and that column is gone from all children SELECT drop_partition_column('partman_test.time_taptest_table', 'col2'); SELECT hasnt_column('partman_test', 'time_taptest_table', 'col2', 'Check that col2 dropped from parent'); SELECT hasnt_column('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'col2', 'Check that col2 dropped from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT hasnt_column('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'col2', 'Check that col2 dropped from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); SELECT hasnt_column('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'col2', 'Check that col2 dropped from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')); SELECT hasnt_column('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'col2', 'Check that col2 dropped from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')); SELECT hasnt_column('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'col2', 'Check that col2 dropped from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT hasnt_column('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'col2', 'Check that col2 dropped from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT hasnt_column('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'col2', 'Check that col2 dropped from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT hasnt_column('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'col2', 'Check that col2 dropped from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT hasnt_column('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'col2', 'Check that col2 dropped from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT hasnt_column('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'col2', 'Check that col2 dropped from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT hasnt_column('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'col2', 'Check that col2 dropped from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT hasnt_column('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'col2', 'Check that col2 dropped from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT hasnt_column('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'col2', 'Check that col2 dropped from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test-time-daily-trunc.sql000066400000000000000000001124431412761706100215270ustar00rootroot00000000000000-- ########## TIME TESTS ########## -- Other tests: Long name truncation, privilege changes, undo partitioning but keep tables \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(108); CREATE SCHEMA partman_test; CREATE ROLE partman_basic; CREATE ROLE partman_revoke; CREATE ROLE partman_owner; CREATE TABLE partman_test.time_taptest_table_1234567890123456789012345678901234567890 (col1 int primary key, col2 text, col3 timestamptz NOT NULL DEFAULT now()); INSERT INTO partman_test.time_taptest_table_1234567890123456789012345678901234567890 (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); GRANT SELECT,INSERT,UPDATE ON partman_test.time_taptest_table_1234567890123456789012345678901234567890 TO partman_basic; GRANT ALL ON partman_test.time_taptest_table_1234567890123456789012345678901234567890 TO partman_revoke; SELECT create_parent('partman_test.time_taptest_table_1234567890123456789012345678901234567890', 'col3', 'partman', 'daily'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012_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_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table_1234567890123456789012345678901234567890'')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved'); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_1234567890123456789012345678901234567890', 'Check that parent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_1234567890123456789012345678901234567890', ARRAY[10], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY[10], 'Check count from time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); REVOKE INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON partman_test.time_taptest_table_1234567890123456789012345678901234567890 FROM partman_revoke; INSERT INTO partman_test.time_taptest_table_1234567890123456789012345678901234567890 (col1, col3) VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '1 day'::interval); INSERT INTO partman_test.time_taptest_table_1234567890123456789012345678901234567890 (col1, col3) VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '2 days'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_1234567890123456789012345678901234567890', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_1234567890123456789012345678901234567890', ARRAY[25], 'Check count from time_taptest_table_1234567890123456789012345678901234567890'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY[10], 'Check count from time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), ARRAY[5], 'Check count from time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); UPDATE part_config SET premake = 5, optimize_trigger = 5 WHERE parent_table = 'partman_test.time_taptest_table_1234567890123456789012345678901234567890'; SELECT run_maintenance(); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' does not exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); GRANT DELETE ON partman_test.time_taptest_table_1234567890123456789012345678901234567890 TO partman_basic; REVOKE ALL ON partman_test.time_taptest_table_1234567890123456789012345678901234567890 FROM partman_revoke; ALTER TABLE partman_test.time_taptest_table_1234567890123456789012345678901234567890 OWNER TO partman_owner; UPDATE part_config SET premake = 6, optimize_trigger = 6 WHERE parent_table = 'partman_test.time_taptest_table_1234567890123456789012345678901234567890'; SELECT run_maintenance(); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); INSERT INTO partman_test.time_taptest_table_1234567890123456789012345678901234567890 (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '20 days'::interval); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table_1234567890123456789012345678901234567890', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT reapply_privileges('partman_test.time_taptest_table_1234567890123456789012345678901234567890'); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT undo_partition('partman_test.time_taptest_table_1234567890123456789012345678901234567890', 20); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_12345678901234567890123456789012_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' is empty'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test-time-daily.sql000066400000000000000000001706531412761706100204050ustar00rootroot00000000000000-- ########## TIME DAILY TESTS ########## -- Other tests: run_maintenance(p_analyze := false), check that maintenance catches up if tables are missing, full return from undo_partition() \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(211); CREATE SCHEMA partman_test; CREATE SCHEMA partman_retention_test; CREATE ROLE partman_basic; CREATE ROLE partman_revoke; CREATE ROLE partman_owner; CREATE TABLE partman_test.time_taptest_table (col1 int primary key, col2 text, col3 timestamptz NOT NULL DEFAULT now()); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); GRANT SELECT,INSERT,UPDATE ON partman_test.time_taptest_table TO partman_basic; GRANT ALL ON partman_test.time_taptest_table TO partman_revoke; SELECT create_parent('partman_test.time_taptest_table', 'col3', 'partman', 'daily'); 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'); 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 table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table'')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved'); 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')); REVOKE INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON partman_test.time_taptest_table FROM partman_revoke; INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '1 day'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '2 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(26,30), CURRENT_TIMESTAMP + '3 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(31,37), CURRENT_TIMESTAMP + '4 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(40,49), CURRENT_TIMESTAMP - '1 day'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(50,70), CURRENT_TIMESTAMP - '2 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(71,85), CURRENT_TIMESTAMP - '3 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(86,100), CURRENT_TIMESTAMP - '4 days'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY[10], '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[5], '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[5], '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[7], '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[10], '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[21], '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[15], '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[15], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); UPDATE part_config SET premake = 5, optimize_trigger = 5 WHERE parent_table = 'partman_test.time_taptest_table'; -- Run to get +5 trigger in place SELECT run_maintenance(p_analyze := false); -- Insert after maintenance so new +5 day trigger is in place INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(101,122), CURRENT_TIMESTAMP + '5 days'::interval); -- Run again to create proper future partitions SELECT run_maintenance(p_analyze := false); -- Data exists for +5 days, with 5 premake so +10 day table should exist SELECT has_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')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY[22], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); GRANT DELETE ON partman_test.time_taptest_table TO partman_basic; REVOKE ALL ON partman_test.time_taptest_table FROM partman_revoke; ALTER TABLE partman_test.time_taptest_table OWNER TO partman_owner; UPDATE part_config SET premake = 6, optimize_trigger = 6 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(p_analyze := false); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(123,150), CURRENT_TIMESTAMP + '6 days'::interval); SELECT run_maintenance(p_analyze := false); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[148], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), ARRAY[28], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'13 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'13 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')); SELECT reapply_privileges('partman_test.time_taptest_table'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); -- Test that maintenance will catch up DO $$ BEGIN EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'); END $$; SELECT hasnt_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')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_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')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_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+'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); 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+'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); 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+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); 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+'6 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT run_maintenance(); 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')||' does exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD')||' does exist'); 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')||' does exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 days'::interval, 'YYYY_MM_DD')||' does exist'); 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')||' does exist'); 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')||' does exist'); 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')||' does not exist'); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '1 day'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '2 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(26,30), CURRENT_TIMESTAMP + '3 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(40,49), CURRENT_TIMESTAMP - '1 day'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(50,70), CURRENT_TIMESTAMP - '2 days'::interval); SELECT run_maintenance(); 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')||' does exist'); SELECT has_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 exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' does exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' does exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' does exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||' does exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||' does not exist'); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(31,37), CURRENT_TIMESTAMP + '4 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(101,122), CURRENT_TIMESTAMP + '5 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(123,150), CURRENT_TIMESTAMP + '6 days'::interval); SELECT run_maintenance(); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||' does exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' does exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')||' does exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'13 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'13 days'::interval, 'YYYY_MM_DD')||' does not exist'); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '20 days'::interval); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT drop_partition_time('partman_test.time_taptest_table', '3 days', p_keep_table := false); 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')||' does not exist'); UPDATE part_config SET retention = '2 days'::interval WHERE parent_table = 'partman_test.time_taptest_table'; SELECT drop_partition_time('partman_test.time_taptest_table', p_retention_schema := 'partman_retention_test'); 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')||' does not exist'); SELECT has_table('partman_retention_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')||' got moved to new schema'); SELECT results_eq('SELECT partitions_undone::int, rows_undone::int FROM undo_partition(''partman_test.time_taptest_table'', 20, p_keep_table := false)' , $$VALUES(15, 118)$$ , 'Check that undo_partition() returns expected values for undoing all tables'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[129], 'Check count from parent table after undo'); SELECT hasnt_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')||' does not exist'); SELECT hasnt_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')||' does not exist'); SELECT hasnt_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')||' does not exist'); 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')||' does not exist'); 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')||' does not exist'); 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 hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_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')||' does not exist'); SELECT hasnt_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')||' does not exist'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test-time-epoch-milliseconds-weekly.sql000066400000000000000000000523311412761706100243520ustar00rootroot00000000000000-- ########## TIME WEEKLY EPOCH TESTS ########## -- Other tests: combination of start_partition & constraint_cols/optimize_constraint. Requires manually running apply_constraint to set other old partitions -- Set optimize_trigger higher than premake. No real way to test that this works (due to hybrid dynamic function), but doing it to test that it makes a proper trigger without any issues. -- Test millisecond epoch time partitioning \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(68); CREATE SCHEMA partman_test; CREATE SCHEMA partman_retention_test; CREATE TABLE partman_test.time_taptest_table (col1 bigint primary key, col2 text, col3 bigint NOT NULL DEFAULT (extract('epoch' from CURRENT_TIMESTAMP)::bigint)*1000); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), (extract('epoch' from CURRENT_TIMESTAMP - '8 weeks'::interval)::bigint)*1000); SELECT create_parent('partman_test.time_taptest_table', 'col3', 'partman', 'weekly', '{"col1"}', p_epoch := 'milliseconds' , p_premake := 2, p_start_partition := to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'YYYY-MM-DD HH24:MI:SS')); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' exists (+1 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||' exists (+2 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW')||' does not exist (+3 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' exists (-1 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' exists (-2 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' exists (-3 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' exists (-4 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||' exists (-5 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||' exists (-6 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')||' exists (-7 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')||' exists (-8 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'9 weeks'::interval, 'IYYY"w"IW')||' does not exist (-9 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' (+1 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||' (+2 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' (-1 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' (-2 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' (-3 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' (-4 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||' (-5 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||' (-6 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')||' (-7 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')||' (-8 weeks)'); SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table'')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved'); 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 - '8 weeks'::interval, 'IYYY"w"IW'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')||' (-8 weeks)'); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), (extract('epoch' from CURRENT_TIMESTAMP - '7 weeks'::interval)::bigint)*1000); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), (extract('epoch' from CURRENT_TIMESTAMP - '6 weeks'::interval)::bigint)*1000); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(26,30), (extract('epoch' from CURRENT_TIMESTAMP - '5 weeks'::interval)::bigint)*1000); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(31,37), (extract('epoch' from CURRENT_TIMESTAMP - '4 week'::interval)::bigint)*1000); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(38,49), (extract('epoch' from CURRENT_TIMESTAMP - '3 week'::interval)::bigint)*1000); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(50,70), (extract('epoch' from CURRENT_TIMESTAMP - '2 weeks'::interval)::bigint)*1000); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(71,85), (extract('epoch' from CURRENT_TIMESTAMP - '1 week'::interval)::bigint)*1000); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(86,100), (extract('epoch' from CURRENT_TIMESTAMP + '1 week'::interval)::bigint)*1000); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(101,110), (extract('epoch' from CURRENT_TIMESTAMP + '2 weeks'::interval)::bigint)*1000); SELECT partition_data_time('partman_test.time_taptest_table', 20); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')||' (-7 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||' (-6 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||' (-5 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), ARRAY[7], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' (-4 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), ARRAY[12], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' (-3 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), ARRAY[21], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' (-2 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' (-1 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' (+1 weeks)'); -- Default optimize_constraint is 30, so set it to a value that will trigger it to work for given conditions of this partition set -- Set optimize_trigger higher than premake to ensure this works as intended UPDATE part_config SET premake = 3, optimize_constraint = 5, optimize_trigger = 8 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); -- Automatic run of apply_constraints due to run_maintenance will put constraint on "now() - 4 weeks" partition with an optimize_constraint value of 5 -- This is due to values "now() + 2 weeks" being inserted above. -- Ex: Current week is 39. +2 weeks is 41. Going back 5 weeks is 36, but constraint is applied to the table OLDER than that value. Hence constraint will be on week 35 SELECT col_has_check('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'col1' , 'Check for additional constraint on col1 on time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' (-4 weeks)'); -- Must run apply_constraints() to manually set the other older constraints SELECT apply_constraints('partman_test.time_taptest_table', 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')); SELECT apply_constraints('partman_test.time_taptest_table', 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')); SELECT apply_constraints('partman_test.time_taptest_table', 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')); SELECT apply_constraints('partman_test.time_taptest_table', 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')); SELECT col_has_check('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW'), 'col1' , 'Check for additional constraint on col1 on time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')); SELECT col_has_check('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW'), 'col1' , 'Check for additional constraint on col1 on time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')); SELECT col_has_check('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), 'col1' , 'Check for additional constraint on col1 on time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')); SELECT col_has_check('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), 'col1' , 'Check for additional constraint on col1 on time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(111,120), (extract('epoch' from CURRENT_TIMESTAMP + '3 weeks'::interval)::bigint)*1000); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' exists (+3 weeks'); -- Cannot test for next week not existing. Different lengths of months will sometimes cause an extra partition. SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' (+3 weeks)'); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' (+3 weeks)'); UPDATE part_config SET premake = 4 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(121,130), (extract('epoch' from CURRENT_TIMESTAMP + '4 weeks'::interval)::bigint)*1000); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[130], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' (+4 weeks)'); SELECT col_has_check('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'col1' , 'Check for additional constraint on col1 on time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' exists (+4 weeks)'); -- Cannot test for next week not existing. Different lengths of months will sometimes cause an extra partition. SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' (+4 weeks)'); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), (extract('epoch' from CURRENT_TIMESTAMP + '20 weeks'::interval)::bigint)*1000); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT drop_partition_time('partman_test.time_taptest_table', '3 weeks', p_keep_table := false); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' does not exist (-4 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||' does not exist (-5 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||' does not exist (-6 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')||' does not exist (-7 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')||' does not exist (-8 weeks)'); UPDATE part_config SET retention = '2 weeks'::interval WHERE parent_table = 'partman_test.time_taptest_table'; SELECT drop_partition_time('partman_test.time_taptest_table', p_retention_schema := 'partman_retention_test'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' does not exist (-3 weeks)'); SELECT has_table('partman_retention_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' got moved to new schema (-3 weeks)'); SELECT undo_partition('partman_test.time_taptest_table', 20, p_keep_table := false); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[92], 'Check count from parent table after undo'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||' does not exist (now)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' does not exist (+1 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||' does not exist (+2 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' does not exist (+3 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' does not exist (+4 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' does not exist (-1 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' does not exist (-2 weeks)'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test-time-epoch-time-time-subpart.sql000066400000000000000000000742121412761706100237410ustar00rootroot00000000000000-- ########## TIME EPOCH PARENT / TIME EPOCH SUBPARENT / TIME EPOCH SUB-SUB-PARENT ########## -- Currently tests 23, 39, 47 & 67 may fail around new years boundaries \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(72); CREATE SCHEMA partman_test; CREATE TABLE partman_test.time_taptest_table (col1 int primary key, col2 text, col3 bigint NOT NULL DEFAULT extract('epoch' from now())); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), extract('epoch' from CURRENT_TIMESTAMP)); -- yearly SELECT create_parent('partman_test.time_taptest_table', 'col3', 'partman', 'yearly', p_premake := 2, p_epoch := 'seconds'); -- Make sure optimize values can be different UPDATE part_config SET optimize_trigger = 5, optimize_constraint = 10 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||' does not exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY')||' does not exist'); -- Move data from parent SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table'')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved'); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that yearly 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'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')); -- monthly SELECT create_sub_parent('partman_test.time_taptest_table', 'col3', 'partman', 'monthly', p_premake := 2, p_epoch := 'seconds'); -- Make sure optimize values can be different UPDATE part_config_sub SET sub_optimize_trigger = 5, sub_optimize_constraint = 10, sub_retention_keep_table = false WHERE sub_parent = 'partman_test.time_taptest_table'; SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM')||' exists'); -- Near end of year (Oct) following may fail since next hear's minimum month table will be created SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM')||' does not exist (this test may fail around year boundary. See comment in test code)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM')||' does not exist'); -- Check that previous and future years had the minimal partition made -- year +/- 1 tests may fail around year boundary. Tables may or may not exist depending on premake. That's fine. Should be ok for further in the future. SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')||'_01', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')||'_01 exists (this test may fail around year boundary. See comment in test code)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')||'_02', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')||'_02 does not exists (this test may fail around year boundary. See comment in test code)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_01', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_01 exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_02', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_02 does not exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_01', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_01 exists (this test may fail around year boundary. See comment in test code)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_02', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_02 exists (this test may fail around year boundary. See comment in test code)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_01', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'2 year'::interval, 'YYYY')||'_01 exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_02', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'2 year'::interval, 'YYYY')||'_02 exists'); SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||''')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved (yearly subparent)'); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY'), 'Check data got moved out of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')); -- Check subpart config SELECT results_eq('SELECT sub_parent FROM part_config_sub ORDER BY sub_parent', ARRAY['partman_test.time_taptest_table'], 'Check that part_config_sub has all tables configured as needed'); -- daily SELECT results_eq('SELECT create_sub_parent(''partman_test.time_taptest_table_p''||to_char(CURRENT_TIMESTAMP, ''YYYY''), ''col3'', ''partman'', ''daily'', p_premake := 2, p_epoch := ''seconds'')', ARRAY[true], 'Subpartitioning partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||' should return true'); SELECT results_eq('SELECT create_sub_parent(''partman_test.time_taptest_table_p''||to_char(CURRENT_TIMESTAMP+''1 year''::interval, ''YYYY''), ''col3'', ''partman'', ''daily'', p_premake := 2, p_epoch := ''seconds'')', ARRAY[true], 'Subpartitioning partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')||' should return true'); SELECT results_eq('SELECT create_sub_parent(''partman_test.time_taptest_table_p''||to_char(CURRENT_TIMESTAMP+''2 years''::interval, ''YYYY''), ''col3'', ''partman'', ''daily'', p_premake := 2, p_epoch := ''seconds'')', ARRAY[true], 'Subpartitioning partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||' should return true'); SELECT results_eq('SELECT create_sub_parent(''partman_test.time_taptest_table_p''||to_char(CURRENT_TIMESTAMP-''1 year''::interval, ''YYYY''), ''col3'', ''partman'', ''daily'', p_premake := 2, p_epoch := ''seconds'')', ARRAY[true], 'Subpartitioning partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||' should return true'); SELECT results_eq('SELECT create_sub_parent(''partman_test.time_taptest_table_p''||to_char(CURRENT_TIMESTAMP-''2 years''::interval, ''YYYY''), ''col3'', ''partman'', ''daily'', p_premake := 2, p_epoch := ''seconds'')', ARRAY[true], 'Subpartitioning partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||' should return true'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')||' exists')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM')||'_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')||'_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' does not exists'); -- This test may fail around the end of the year or the end of some months since the minimal partition for the next year or next month was created. That's fine SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' does not exist (this test may fail around year or month boundaries. See comment in test code)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM')||'_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')||'_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); -- Check that previous and future years had the minimal partition made -- year +/- 1 tests may fail around year boundary. Tables may or may not exist depending on premake. That's fine. Should be ok for further in the future. SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 years'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP+'1 years'::interval, 'YYYY')||'_01_01', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 years'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP+'1 years'::interval, 'YYYY')||'_01_01 exists (this test may fail around year boundary. See comment in test code)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 years'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP+'1 years'::interval, 'YYYY')||'_01_02', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 years'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP+'1 years'::interval, 'YYYY')||'_01_02 does not exists (this test may fail around year boundary. See comment in test code)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_01_01', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_01_01 exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_01_02', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_01_02 does not exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_01_01', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_01_01 exists (this test may fail around year boundary. See comment in test code)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_01_02', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_01_02 exists (this test may fail around year boundary. See comment in test code)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_01_01', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'2 year'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_01_01 exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_01_02', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'2 year'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_01_02 does not exists'); SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')||''')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved (monthly subparent)'); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM'), 'Check data got moved out of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); -- Check subpart config SELECT results_eq('SELECT sub_parent FROM part_config_sub ORDER BY sub_parent', ARRAY['partman_test.time_taptest_table', 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY'), 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY'), 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY'), 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY'), 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')], 'Check that part_config_sub has all tables configured as needed'); INSERT INTO partman_test.time_taptest_table (col1, col2, col3) VALUES (generate_series(11,20), 'stuff', extract('epoch' from CURRENT_TIMESTAMP+'1 day'::interval)); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check new data did not go into parent time_taptest_table'); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day', 'YYYY'), 'Check new data did not go into subparent time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day', 'YYYY')); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day', 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 day', 'YYYY_MM'), 'Check new data did not go into subparent time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day', 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 day', 'YYYY_MM')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day', 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 day', 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP+'1 day', 'YYYY_MM_DD'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day', 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 day', 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP+'1 day', 'YYYY_MM_DD')); UPDATE part_config SET premake = 3, optimize_trigger = 3 WHERE parent_table LIKE 'partman_test.time_taptest_table%' AND partition_type = 'partman'; SELECT run_maintenance(); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||' exists'); -- This test may fail if the next year's subpartition was already made and +3 months is into the next year, which may have been made by the condition that guarantees there is always at least 1 child table in a subpartition. SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM')||' exists. This test may fail in months near the end of the year.'); -- This test may fail if the next month's subpartition was already made and +3 days is after the 1st of the month, which may have been made by the condition that guarantees there is always at least 1 child table in a subpartition. SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' exists. This test may fail depending on the day of the month it is run.'); -- Check that future year had the minimal partition made SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_01', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_01 exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_01_01', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_01_01 exists'); -- Check subpart config SELECT results_eq('SELECT sub_parent FROM part_config_sub ORDER BY sub_parent', ARRAY['partman_test.time_taptest_table', 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY'), 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY'), 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY'), 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY'), 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY'), 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')], 'Check that part_config_sub has all tables configured as needed'); INSERT INTO partman_test.time_taptest_table (col1, col2, col3) VALUES (generate_series(21,30), 'stuff', extract('epoch' from CURRENT_TIMESTAMP+'3 years'::interval)); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check new data did not go into parent time_taptest_table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY')||'. Data should have gone here since monthly subpartition for it does not exist. This test may fail in January since that monthly partition should exist.'); -- Move data from yearly parent table and create appropriate monthly child for it SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||''')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'. This test may fail in January or the first of any month since that monthly/daily partition should exist.'); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY'), 'Check new data did not go into subparent time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY_MM'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY_MM')||'. Data should have gone here since daily subpartition for it does not exist.'); -- Move data from monthly parent table and create appropriate daily child for it SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY_MM')||''')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY_MM')||'. This test may fail in January or the first of any month since that monthly/daily partition should exist.'); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY_MM'), 'Check new data did not go into subparent time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY_MM')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY_MM_DD'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY_MM_DD')); /* -- Disabled test for now. New years makes testing undo functions hard. Would be calling undo on year+1_01 twice and second one would fail. SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[30], 'Check count from top parent'); SELECT throws_ok('SELECT undo_partition_time(''partman_test.time_taptest_table_p''||to_char(CURRENT_TIMESTAMP+''3 years''::interval, ''YYYY''), 20, p_keep_table := false)', 'P0001', 'Child table for this parent has child table(s) itself (partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_01). Run undo partitioning on this table or remove inheritance first to ensure all data is properly moved to parent CONTEXT: SQL statement "SELECT undo_partition_time(''partman_test.time_taptest_table_p''||to_char(CURRENT_TIMESTAMP+''3 years''::interval, ''YYYY''), 20, p_keep_table := false)" PL/pgSQL function throws_ok(text,character,text,text) line 16 at EXECUTE statement DETAIL: HINT: ', 'Check that undoing partitions is prevented if subpartitions still exist'); */ SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test-time-epoch-weekly.sql000066400000000000000000000520261412761706100216700ustar00rootroot00000000000000-- ########## TIME WEEKLY EPOCH TESTS ########## -- Other tests: combination of start_partition & constraint_cols/optimize_constraint. Requires manually running apply_constraint to set other old partitions -- Set optimize_trigger higher than premake. No real way to test that this works (due to hybrid dynamic function), but doing it to test that it makes a proper trigger without any issues. \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(68); CREATE SCHEMA partman_test; CREATE SCHEMA partman_retention_test; CREATE TABLE partman_test.time_taptest_table (col1 int primary key, col2 text, col3 int NOT NULL DEFAULT extract('epoch' from CURRENT_TIMESTAMP)::int); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), extract('epoch' from CURRENT_TIMESTAMP - '8 weeks'::interval)::int); SELECT create_parent('partman_test.time_taptest_table', 'col3', 'partman', 'weekly', '{"col1"}', p_epoch := 'seconds' , p_premake := 2, p_start_partition := to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'YYYY-MM-DD HH24:MI:SS')); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' exists (+1 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||' exists (+2 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW')||' does not exist (+3 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' exists (-1 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' exists (-2 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' exists (-3 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' exists (-4 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||' exists (-5 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||' exists (-6 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')||' exists (-7 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')||' exists (-8 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'9 weeks'::interval, 'IYYY"w"IW')||' does not exist (-9 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' (+1 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||' (+2 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' (-1 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' (-2 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' (-3 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' (-4 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||' (-5 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||' (-6 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')||' (-7 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')||' (-8 weeks)'); SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table'')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved'); 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 - '8 weeks'::interval, 'IYYY"w"IW'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')||' (-8 weeks)'); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), extract('epoch' from CURRENT_TIMESTAMP - '7 weeks'::interval)::int); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), extract('epoch' from CURRENT_TIMESTAMP - '6 weeks'::interval)::int); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(26,30), extract('epoch' from CURRENT_TIMESTAMP - '5 weeks'::interval)::int); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(31,37), extract('epoch' from CURRENT_TIMESTAMP - '4 week'::interval)::int); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(38,49), extract('epoch' from CURRENT_TIMESTAMP - '3 week'::interval)::int); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(50,70), extract('epoch' from CURRENT_TIMESTAMP - '2 weeks'::interval)::int); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(71,85), extract('epoch' from CURRENT_TIMESTAMP - '1 week'::interval)::int); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(86,100), extract('epoch' from CURRENT_TIMESTAMP + '1 week'::interval)::int); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(101,110), extract('epoch' from CURRENT_TIMESTAMP + '2 weeks'::interval)::int); SELECT partition_data_time('partman_test.time_taptest_table', 20); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')||' (-7 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||' (-6 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||' (-5 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), ARRAY[7], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' (-4 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), ARRAY[12], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' (-3 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), ARRAY[21], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' (-2 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' (-1 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' (+1 weeks)'); -- Default optimize_constraint is 30, so set it to a value that will trigger it to work for given conditions of this partition set -- Set optimize_trigger higher than premake to ensure this works as intended UPDATE part_config SET premake = 3, optimize_constraint = 5, optimize_trigger = 8 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); -- Automatic run of apply_constraints due to run_maintenance will put constraint on "now() - 4 weeks" partition with an optimize_constraint value of 5 -- This is due to values "now() + 2 weeks" being inserted above. -- Ex: Current week is 39. +2 weeks is 41. Going back 5 weeks is 36, but constraint is applied to the table OLDER than that value. Hence constraint will be on week 35 SELECT col_has_check('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'col1' , 'Check for additional constraint on col1 on time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' (-4 weeks)'); -- Must run apply_constraints() to manually set the other older constraints SELECT apply_constraints('partman_test.time_taptest_table', 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')); SELECT apply_constraints('partman_test.time_taptest_table', 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')); SELECT apply_constraints('partman_test.time_taptest_table', 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')); SELECT apply_constraints('partman_test.time_taptest_table', 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')); SELECT col_has_check('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW'), 'col1' , 'Check for additional constraint on col1 on time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')); SELECT col_has_check('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW'), 'col1' , 'Check for additional constraint on col1 on time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')); SELECT col_has_check('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), 'col1' , 'Check for additional constraint on col1 on time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')); SELECT col_has_check('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), 'col1' , 'Check for additional constraint on col1 on time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(111,120), extract('epoch' from CURRENT_TIMESTAMP + '3 weeks'::interval)::int); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' exists (+3 weeks'); -- Cannot test for next week not existing. Different lengths of months will sometimes cause an extra partition. SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' (+3 weeks)'); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' (+3 weeks)'); UPDATE part_config SET premake = 4 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(121,130), extract('epoch' from CURRENT_TIMESTAMP + '4 weeks'::interval)::int); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[130], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' (+4 weeks)'); SELECT col_has_check('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'col1' , 'Check for additional constraint on col1 on time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' exists (+4 weeks)'); -- Cannot test for next week not existing. Different lengths of months will sometimes cause an extra partition. SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' (+4 weeks)'); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), extract('epoch' from CURRENT_TIMESTAMP + '20 weeks'::interval)::int); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT drop_partition_time('partman_test.time_taptest_table', '3 weeks', p_keep_table := false); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' does not exist (-4 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||' does not exist (-5 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||' does not exist (-6 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')||' does not exist (-7 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')||' does not exist (-8 weeks)'); UPDATE part_config SET retention = '2 weeks'::interval WHERE parent_table = 'partman_test.time_taptest_table'; SELECT drop_partition_time('partman_test.time_taptest_table', p_retention_schema := 'partman_retention_test'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' does not exist (-3 weeks)'); SELECT has_table('partman_retention_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' got moved to new schema (-3 weeks)'); SELECT undo_partition('partman_test.time_taptest_table', 20, p_keep_table := false); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[92], 'Check count from parent table after undo'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||' does not exist (now)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' does not exist (+1 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||' does not exist (+2 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' does not exist (+3 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' does not exist (+4 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' does not exist (-1 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' does not exist (-2 weeks)'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test-time-half-hour-trunc.sql000066400000000000000000002514011412761706100223100ustar00rootroot00000000000000-- ########## TIME HALF HOUR TESTS ########## -- Other test: Long name truncation \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(151); CREATE SCHEMA partman_test; CREATE SCHEMA partman_retention_test; CREATE ROLE partman_basic; CREATE ROLE partman_revoke; CREATE ROLE partman_owner; CREATE TABLE partman_test.time_taptest_table_123456789012345678901234567901234567890 (col1 int primary key, col2 text, col3 timestamptz NOT NULL DEFAULT now()); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567901234567890 (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); GRANT SELECT,INSERT,UPDATE ON partman_test.time_taptest_table_123456789012345678901234567901234567890 TO partman_basic; GRANT ALL ON partman_test.time_taptest_table_123456789012345678901234567901234567890 TO partman_revoke; SELECT create_parent('partman_test.time_taptest_table_123456789012345678901234567901234567890', 'col3', 'partman', 'half-hour'); -- Must run_maintenance because when interval time is between 1 hour and 1 minute, the first partition name done by above is always the nearest hour rounded down SELECT run_maintenance(); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exist'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); /* extra previous tables may exist due to new rounding down of the hour. Test left here for manual checking SELECT hasnt_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'150 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'150 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exist'); */ SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table_123456789012345678901234567901234567890'')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved'); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_123456789012345678901234567901234567890', 'Check that parent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_123456789012345678901234567901234567890', ARRAY[10], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI'), ARRAY[10], 'Check count from time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI')); REVOKE INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON partman_test.time_taptest_table_123456789012345678901234567901234567890 FROM partman_revoke; INSERT INTO partman_test.time_taptest_table_123456789012345678901234567901234567890 (col1, col3) VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '30 mins'::interval); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567901234567890 (col1, col3) VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '60 mins'::interval); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567901234567890 (col1, col3) VALUES (generate_series(26,30), CURRENT_TIMESTAMP + '90 mins'::interval); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567901234567890 (col1, col3) VALUES (generate_series(31,37), CURRENT_TIMESTAMP + '120 mins'::interval); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567901234567890 (col1, col3) VALUES (generate_series(40,49), CURRENT_TIMESTAMP - '30 mins'::interval); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567901234567890 (col1, col3) VALUES (generate_series(50,70), CURRENT_TIMESTAMP - '60 mins'::interval); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567901234567890 (col1, col3) VALUES (generate_series(71,85), CURRENT_TIMESTAMP - '90 mins'::interval); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567901234567890 (col1, col3) VALUES (generate_series(86,100), CURRENT_TIMESTAMP - '120 mins'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_123456789012345678901234567901234567890', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY[10], 'Check count from time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY[5], 'Check count from time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY[5], 'Check count from time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY[7], 'Check count from time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY[10], 'Check count from time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY[21], 'Check count from time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY[15], 'Check count from time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY[15], 'Check count from time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 mins'::interval, 'YYYY_MM_DD_HH24MI')); UPDATE part_config SET premake = 5, optimize_trigger = 5 WHERE parent_table = 'partman_test.time_taptest_table_123456789012345678901234567901234567890'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567901234567890 (col1, col3) VALUES (generate_series(101,122), CURRENT_TIMESTAMP + '150 mins'::interval); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exist'); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_123456789012345678901234567901234567890', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY[22], 'Check count from time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 mins'::interval, 'YYYY_MM_DD_HH24MI')); GRANT DELETE ON partman_test.time_taptest_table_123456789012345678901234567901234567890 TO partman_basic; REVOKE ALL ON partman_test.time_taptest_table_123456789012345678901234567901234567890 FROM partman_revoke; ALTER TABLE partman_test.time_taptest_table_123456789012345678901234567901234567890 OWNER TO partman_owner; UPDATE part_config SET premake = 6, optimize_trigger = 6 WHERE parent_table = 'partman_test.time_taptest_table_123456789012345678901234567901234567890'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567901234567890 (col1, col3) VALUES (generate_series(123,150), CURRENT_TIMESTAMP + '180 mins'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_123456789012345678901234567901234567890', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_123456789012345678901234567901234567890', ARRAY[148], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY[28], 'Check count from time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 mins'::interval, 'YYYY_MM_DD_HH24MI')); -- Inserted new data above, so 2 additional future partitions were made instead of just 1 SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'360 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'360 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 mins'::interval, 'YYYY_MM_DD_HH24MI')); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567901234567890 (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '600 mins'::interval); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table_123456789012345678901234567901234567890', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT reapply_privileges('partman_test.time_taptest_table_123456789012345678901234567901234567890'); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT drop_partition_time('partman_test.time_taptest_table_123456789012345678901234567901234567890', '90 mins', p_keep_table := false); SELECT hasnt_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exist'); UPDATE part_config SET retention = '60 mins'::interval WHERE parent_table = 'partman_test.time_taptest_table_123456789012345678901234567901234567890'; SELECT drop_partition_time('partman_test.time_taptest_table_123456789012345678901234567901234567890', p_retention_schema := 'partman_retention_test'); SELECT hasnt_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exist'); SELECT has_table('partman_retention_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 mins'::interval, 'YYYY_MM_DD_HH24MI')||' got moved to new schema'); SELECT undo_partition('partman_test.time_taptest_table_123456789012345678901234567901234567890', 20, p_keep_table := false); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table_123456789012345678901234567901234567890', ARRAY[129], 'Check count from parent table after undo'); SELECT hasnt_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exist'); SELECT * FROM finish(); --ROLLBACK; pg_partman-4.6.0/test/test-time-half-hour.sql000066400000000000000000001352331412761706100211630ustar00rootroot00000000000000-- ########## TIME HALF-HOUR TESTS ########## \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(94); CREATE SCHEMA partman_test; CREATE ROLE partman_basic; CREATE ROLE partman_revoke; CREATE ROLE partman_owner; CREATE TABLE partman_test.time_taptest_table (col1 int primary key, col2 text, col3 timestamptz NOT NULL DEFAULT now()); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); GRANT SELECT,INSERT,UPDATE ON partman_test.time_taptest_table TO partman_basic; GRANT ALL ON partman_test.time_taptest_table TO partman_revoke; SELECT create_parent('partman_test.time_taptest_table', 'col3', 'partman', 'half-hour'); -- Must run_maintenance because when interval time is between 1 hour and 1 minute, the first partition name done by above is always the nearest hour rounded down SELECT run_maintenance(); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exist'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table'')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved'); 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(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI')); REVOKE INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON partman_test.time_taptest_table FROM partman_revoke; INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '30 mins'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '60 mins'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[25], 'Check count from time_taptest_table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI')); UPDATE part_config SET premake = 5, optimize_trigger = 5 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exist'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 mins'::interval, 'YYYY_MM_DD_HH24MI')); GRANT DELETE ON partman_test.time_taptest_table TO partman_basic; REVOKE ALL ON partman_test.time_taptest_table FROM partman_revoke; ALTER TABLE partman_test.time_taptest_table OWNER TO partman_owner; UPDATE part_config SET premake = 6, optimize_trigger = 6 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 mins'::interval, 'YYYY_MM_DD_HH24MI')); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '600 mins'::interval); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT reapply_privileges('partman_test.time_taptest_table'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT undo_partition('partman_test.time_taptest_table', 20); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 mins'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 mins'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 mins'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 mins'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test-time-hourly-trunc.sql000066400000000000000000001344661412761706100217600ustar00rootroot00000000000000-- ########## TIME TESTS ########## -- Other tests: Long name truncation \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(122); CREATE SCHEMA partman_test; CREATE ROLE partman_basic; CREATE ROLE partman_revoke; CREATE ROLE partman_owner; CREATE TABLE partman_test.time_taptest_table_1234567890123456789012345678901234567890 (col1 int primary key, col2 text, col3 timestamptz NOT NULL DEFAULT now()); INSERT INTO partman_test.time_taptest_table_1234567890123456789012345678901234567890 (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); GRANT SELECT,INSERT,UPDATE ON partman_test.time_taptest_table_1234567890123456789012345678901234567890 TO partman_basic; GRANT ALL ON partman_test.time_taptest_table_1234567890123456789012345678901234567890 TO partman_revoke; SELECT create_parent('partman_test.time_taptest_table_1234567890123456789012345678901234567890', 'col3', 'partman', 'hourly'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table_1234567890123456789012345678901234567890'')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved'); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_1234567890123456789012345678901234567890', 'Check that parent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_1234567890123456789012345678901234567890', ARRAY[10], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI'), ARRAY[10], 'Check count from time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI')); REVOKE INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON partman_test.time_taptest_table_1234567890123456789012345678901234567890 FROM partman_revoke; INSERT INTO partman_test.time_taptest_table_1234567890123456789012345678901234567890 (col1, col3) VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '1 hour'::interval); INSERT INTO partman_test.time_taptest_table_1234567890123456789012345678901234567890 (col1, col3) VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '2 hours'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_1234567890123456789012345678901234567890', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_1234567890123456789012345678901234567890', ARRAY[25], 'Check count from time_taptest_table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY[10], 'Check count from time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY[5], 'Check count from time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI')); UPDATE part_config SET premake = 5, optimize_trigger = 5 WHERE parent_table = 'partman_test.time_taptest_table_1234567890123456789012345678901234567890'; SELECT run_maintenance(); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI')); GRANT DELETE ON partman_test.time_taptest_table_1234567890123456789012345678901234567890 TO partman_basic; REVOKE ALL ON partman_test.time_taptest_table_1234567890123456789012345678901234567890 FROM partman_revoke; ALTER TABLE partman_test.time_taptest_table_1234567890123456789012345678901234567890 OWNER TO partman_owner; UPDATE part_config SET premake = 6, optimize_trigger = 6 WHERE parent_table = 'partman_test.time_taptest_table_1234567890123456789012345678901234567890'; SELECT run_maintenance(); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'9 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'9 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')); INSERT INTO partman_test.time_taptest_table_1234567890123456789012345678901234567890 (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '20 hours'::interval); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table_1234567890123456789012345678901234567890', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT reapply_privileges('partman_test.time_taptest_table_1234567890123456789012345678901234567890'); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT undo_partition('partman_test.time_taptest_table_1234567890123456789012345678901234567890', 20); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_123456789012345678901234567_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test-time-hourly.sql000066400000000000000000001171111412761706100206130ustar00rootroot00000000000000-- ########## TIME HOURLY TESTS ########## -- Additional tests: Undo partitioning but keep tables. Timestamp without timezone \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(123); CREATE SCHEMA partman_test; CREATE ROLE partman_basic; CREATE ROLE partman_revoke; CREATE ROLE partman_owner; CREATE TABLE partman_test.time_taptest_table (col1 int primary key, col2 text, col3 timestamp NOT NULL DEFAULT now()); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); GRANT SELECT,INSERT,UPDATE ON partman_test.time_taptest_table TO partman_basic; GRANT ALL ON partman_test.time_taptest_table TO partman_revoke; SELECT create_parent('partman_test.time_taptest_table', 'col3', 'partman', 'hourly'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'5 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'5 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table'')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved'); 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(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI')); REVOKE INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON partman_test.time_taptest_table FROM partman_revoke; INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '1 hour'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '2 hours'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[25], 'Check count from time_taptest_table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI')); UPDATE part_config SET premake = 5, optimize_trigger = 5 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI')); GRANT DELETE ON partman_test.time_taptest_table TO partman_basic; REVOKE ALL ON partman_test.time_taptest_table FROM partman_revoke; ALTER TABLE partman_test.time_taptest_table OWNER TO partman_owner; UPDATE part_config SET premake = 6, optimize_trigger = 6 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'9 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'9 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '20 hours'::interval); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT reapply_privileges('partman_test.time_taptest_table'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT undo_partition('partman_test.time_taptest_table', 20); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test-time-id-subpart.sql000066400000000000000000002605161412761706100213530ustar00rootroot00000000000000-- ########## TIME PARENT / ID SUBPARENT STATIC TESTS ########## \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(325); CREATE SCHEMA partman_test; CREATE TABLE partman_test.time_taptest_table (col1 int primary key, col2 text, col3 timestamptz NOT NULL DEFAULT now()); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); SELECT create_parent('partman_test.time_taptest_table', 'col3', 'partman', 'daily'); 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'); 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 results_eq('SELECT partition_data_time(''partman_test.time_taptest_table'')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved'); 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', 'partman', '20'); -- Move data to new subpartitions SELECT results_eq('SELECT partition_data_id(''partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||''', p_batch_count := 5)::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved'); 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 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), CURRENT_TIMESTAMP); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,35), CURRENT_TIMESTAMP + '1 day'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,35), CURRENT_TIMESTAMP + '2 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,35), CURRENT_TIMESTAMP + '3 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,35), CURRENT_TIMESTAMP + '4 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,35), CURRENT_TIMESTAMP - '1 day'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,35), CURRENT_TIMESTAMP - '2 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,35), CURRENT_TIMESTAMP - '3 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,35), CURRENT_TIMESTAMP - '4 days'::interval); 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), CURRENT_TIMESTAMP); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(36,50), CURRENT_TIMESTAMP + '1 day'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(36,50), CURRENT_TIMESTAMP + '2 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(36,50), CURRENT_TIMESTAMP + '3 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(36,50), CURRENT_TIMESTAMP + '4 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,50), CURRENT_TIMESTAMP + '5 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(36,50), CURRENT_TIMESTAMP - '1 day'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(36,50), CURRENT_TIMESTAMP - '2 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(36,50), CURRENT_TIMESTAMP - '3 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(36,50), CURRENT_TIMESTAMP - '4 days'::interval); 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 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; -- 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_keep_table := false); SELECT undo_partition('partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 20, 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_keep_table := false); SELECT undo_partition('partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 20, 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_keep_table := false); SELECT undo_partition('partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 20, 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_keep_table := false); SELECT undo_partition('partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 20, 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_keep_table := false); SELECT undo_partition('partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 20, 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_keep_table := false); SELECT undo_partition('partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, '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 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 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') ); --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; pg_partman-4.6.0/test/test-time-monthly-trunc.sql000066400000000000000000001342111412761706100221140ustar00rootroot00000000000000-- ########## TIME MONTHLY TESTS ########## -- Other tests: Long name truncation \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(133); CREATE SCHEMA partman_test; CREATE ROLE partman_basic; CREATE ROLE partman_revoke; CREATE ROLE partman_owner; CREATE TABLE partman_test.time_taptest_table_123456789012345678901234567890123457890 (col1 int primary key, col2 text, col3 timestamptz NOT NULL DEFAULT now()); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567890123457890 (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); GRANT SELECT,INSERT,UPDATE ON partman_test.time_taptest_table_123456789012345678901234567890123457890 TO partman_basic; GRANT ALL ON partman_test.time_taptest_table_123456789012345678901234567890123457890 TO partman_revoke; SELECT create_parent('partman_test.time_taptest_table_123456789012345678901234567890123457890', 'col3', 'partman', 'monthly'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM'), 'Check time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM'), 'Check time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM')||' does not exist'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM'), 'Check time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'5 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'5 months'::interval, 'YYYY_MM')||' does not exist'); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM')); SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table_123456789012345678901234567890123457890'')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved'); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_123456789012345678901234567890123457890', 'Check that parent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_123456789012345678901234567890123457890', ARRAY[10], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM'), ARRAY[10], 'Check count from time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')); REVOKE INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON partman_test.time_taptest_table_123456789012345678901234567890123457890 FROM partman_revoke; INSERT INTO partman_test.time_taptest_table_123456789012345678901234567890123457890 (col1, col3) VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '1 month'::interval); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567890123457890 (col1, col3) VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '2 months'::interval); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567890123457890 (col1, col3) VALUES (generate_series(26,30), CURRENT_TIMESTAMP + '3 months'::interval); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567890123457890 (col1, col3) VALUES (generate_series(31,37), CURRENT_TIMESTAMP + '4 months'::interval); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567890123457890 (col1, col3) VALUES (generate_series(40,49), CURRENT_TIMESTAMP - '1 month'::interval); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567890123457890 (col1, col3) VALUES (generate_series(50,70), CURRENT_TIMESTAMP - '2 months'::interval); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567890123457890 (col1, col3) VALUES (generate_series(71,85), CURRENT_TIMESTAMP - '3 months'::interval); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567890123457890 (col1, col3) VALUES (generate_series(86,100), CURRENT_TIMESTAMP - '4 months'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_123456789012345678901234567890123457890', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM'), ARRAY[10], 'Check count from time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM'), ARRAY[5], 'Check count from time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM'), ARRAY[5], 'Check count from time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM'), ARRAY[7], 'Check count from time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM'), ARRAY[10], 'Check count from time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM'), ARRAY[21], 'Check count from time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM'), ARRAY[15], 'Check count from time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM'), ARRAY[15], 'Check count from time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM')); UPDATE part_config SET premake = 5, optimize_trigger = 5 WHERE parent_table = 'partman_test.time_taptest_table_123456789012345678901234567890123457890'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567890123457890 (col1, col3) VALUES (generate_series(101,122), CURRENT_TIMESTAMP + '5 months'::interval); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'7 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'7 months'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'8 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'8 months'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY_MM')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM')||' exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'7 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'7 months'::interval, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'8 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'8 months'::interval, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY_MM')); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_123456789012345678901234567890123457890', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM'), ARRAY[22], 'Check count from time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'5 month'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'5 month'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'6 month'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'6 month'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'7 month'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'7 month'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'8 month'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'8 month'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'9 month'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'9 month'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'7 months'::interval, 'YYYY_MM'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'7 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'8 months'::interval, 'YYYY_MM'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'8 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY_MM'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY_MM')); GRANT DELETE ON partman_test.time_taptest_table_123456789012345678901234567890123457890 TO partman_basic; REVOKE ALL ON partman_test.time_taptest_table_123456789012345678901234567890123457890 FROM partman_revoke; ALTER TABLE partman_test.time_taptest_table_123456789012345678901234567890123457890 OWNER TO partman_owner; UPDATE part_config SET premake = 6, optimize_trigger = 6 WHERE parent_table = 'partman_test.time_taptest_table_123456789012345678901234567890123457890'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567890123457890 (col1, col3) VALUES (generate_series(123,150), CURRENT_TIMESTAMP + '6 months'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_123456789012345678901234567890123457890', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_123456789012345678901234567890123457890', ARRAY[148], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM'), ARRAY[28], 'Check count from time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM')); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'11 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'11 months'::interval, 'YYYY_MM')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY_MM')||' exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'11 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'11 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM')); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567890123457890 (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '20 months'::interval); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table_123456789012345678901234567890123457890', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT reapply_privileges('partman_test.time_taptest_table_123456789012345678901234567890123457890'); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'7 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'7 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'8 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'8 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'11 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'11 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM')); -- Currently unable to do drop_partition test reliably for monthly due to differing month lengths (sometimes drops 2 partitions instead of 1) SELECT undo_partition('partman_test.time_taptest_table_123456789012345678901234567890123457890', 20, p_keep_table := false); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table_123456789012345678901234567890123457890', ARRAY[159], 'Check count from parent table after undo'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM')||' does not exist'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test-time-monthly.sql000066400000000000000000001336641412761706100207760ustar00rootroot00000000000000-- ########## TIME MONTHLY TESTS ########## -- Other tests: Make sure option to not inherit foreign keys works \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(166); CREATE SCHEMA partman_test; CREATE ROLE partman_basic; CREATE ROLE partman_revoke; CREATE ROLE partman_owner; CREATE TABLE partman_test.fk_test_reference (col2 text not null); CREATE UNIQUE INDEX ON partman_test.fk_test_reference(col2); INSERT INTO partman_test.fk_test_reference VALUES ('stuff'); CREATE TABLE partman_test.time_taptest_table ( col1 int primary key , col2 text not null default 'stuff' , col3 timestamptz NOT NULL DEFAULT now() , FOREIGN KEY (col2) REFERENCES partman_test.fk_test_reference(col2) MATCH SIMPLE NOT DEFERRABLE); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); GRANT SELECT,INSERT,UPDATE ON partman_test.time_taptest_table TO partman_basic; GRANT ALL ON partman_test.time_taptest_table TO partman_revoke; SELECT create_parent('partman_test.time_taptest_table', 'col3', 'partman', 'monthly', p_inherit_fk := false); --SELECT create_parent('partman_test.time_taptest_table', 'col3', 'time', 'monthly'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM')||' does not exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'5 months'::interval, 'YYYY_MM')||' does not exist'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM'), 'col2', 'Check that foreign key was NOT inherited for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM'), 'col2', 'Check that foreign key was NOT inherited for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM'), 'col2', 'Check that foreign key was NOT inherited for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM'), 'col2', 'Check that foreign key was NOT inherited for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM'), 'col2', 'Check that foreign key was NOT inherited for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM'), 'col2', 'Check that foreign key was NOT inherited for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM'), 'col2', 'Check that foreign key was NOT inherited for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM'), 'col2', 'Check that foreign key was NOT inherited for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM'), 'col2', 'Check that foreign key was NOT inherited for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM')); SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table'')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved'); 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'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')); REVOKE INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON partman_test.time_taptest_table FROM partman_revoke; INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '1 month'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '2 months'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(26,30), CURRENT_TIMESTAMP + '3 months'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(31,37), CURRENT_TIMESTAMP + '4 months'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(40,49), CURRENT_TIMESTAMP - '1 month'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(50,70), CURRENT_TIMESTAMP - '2 months'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(71,85), CURRENT_TIMESTAMP - '3 months'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(86,100), CURRENT_TIMESTAMP - '4 months'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM'), ARRAY[7], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM'), ARRAY[21], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM')); UPDATE part_config SET premake = 5, optimize_trigger = 5 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(101,122), CURRENT_TIMESTAMP + '5 months'::interval); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'7 months'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'8 months'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY_MM')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM')||' does not exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 months'::interval, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 months'::interval, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY_MM')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 months'::interval, 'YYYY_MM'), 'col2', 'Check that foreign key was NOT inherited for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 months'::interval, 'YYYY_MM')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 months'::interval, 'YYYY_MM'), 'col2', 'Check that foreign key was NOT inherited for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 months'::interval, 'YYYY_MM')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 months'::interval, 'YYYY_MM'), 'col2', 'Check that foreign key was NOT inherited for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 months'::interval, 'YYYY_MM')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 months'::interval, 'YYYY_MM'), 'col2', 'Check that foreign key was NOT inherited for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 months'::interval, 'YYYY_MM')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 months'::interval, 'YYYY_MM'), 'col2', 'Check that foreign key was NOT inherited for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 months'::interval, 'YYYY_MM')); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM'), ARRAY[22], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 months'::interval, 'YYYY_MM'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 months'::interval, 'YYYY_MM'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY_MM'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY_MM')); GRANT DELETE ON partman_test.time_taptest_table TO partman_basic; REVOKE ALL ON partman_test.time_taptest_table FROM partman_revoke; ALTER TABLE partman_test.time_taptest_table OWNER TO partman_owner; UPDATE part_config SET premake = 6, optimize_trigger = 6 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(123,150), CURRENT_TIMESTAMP + '6 months'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[148], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM'), ARRAY[28], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM')); -- +5 month data was inserted above so 2 children get made SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'11 months'::interval, 'YYYY_MM')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY_MM')||' does not exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 months'::interval, 'YYYY_MM')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM'), 'col2', 'Check that foreign key was NOT inherited for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 months'::interval, 'YYYY_MM'), 'col2', 'Check that foreign key was NOT inherited for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 months'::interval, 'YYYY_MM'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 months'::interval, 'YYYY_MM')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 months'::interval, 'YYYY_MM'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 months'::interval, 'YYYY_MM')); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '20 months'::interval); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT reapply_privileges('partman_test.time_taptest_table'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 months'::interval, 'YYYY_MM'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 months'::interval, 'YYYY_MM'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 months'::interval, 'YYYY_MM'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY_MM'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 months'::interval, 'YYYY_MM'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 months'::interval, 'YYYY_MM')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 months'::interval, 'YYYY_MM'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 months'::interval, 'YYYY_MM')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 months'::interval, 'YYYY_MM'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 months'::interval, 'YYYY_MM')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY_MM'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY_MM')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 months'::interval, 'YYYY_MM'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 months'::interval, 'YYYY_MM')); -- Currently unable to do drop_partition test reliably for monthly due to differing month lengths (sometimes drops 2 partitions instead of 1) SELECT undo_partition('partman_test.time_taptest_table', 20, p_keep_table := false); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[159], 'Check count from parent table after undo'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'7 months'::interval, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'8 months'::interval, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'11 months'::interval, 'YYYY_MM')||' does not exist'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test-time-nonsuperuser.sql000066400000000000000000000555661412761706100220600ustar00rootroot00000000000000-- ########## TIME MONTHLY TESTS ########## -- Other tests: nonsuperuser, Make sure option to not inherit foreign keys works \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(88); CREATE SCHEMA partman_test; CREATE TABLE partman_test.fk_test_reference (col2 text not null); CREATE UNIQUE INDEX ON partman_test.fk_test_reference(col2); INSERT INTO partman_test.fk_test_reference VALUES ('stuff'); CREATE TABLE partman_test.time_taptest_table ( col1 int primary key , col2 text not null default 'stuff' , col3 timestamptz NOT NULL DEFAULT now() , FOREIGN KEY (col2) REFERENCES partman_test.fk_test_reference(col2) MATCH SIMPLE NOT DEFERRABLE); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); SELECT create_parent('partman_test.time_taptest_table', 'col3', 'partman', 'monthly', p_inherit_fk := false); --SELECT create_parent('partman_test.time_taptest_table', 'col3', 'time', 'monthly'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM')||' does not exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'5 months'::interval, 'YYYY_MM')||' does not exist'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM'), 'col2', 'Check that foreign key was NOT inherited for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM'), 'col2', 'Check that foreign key was NOT inherited for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM'), 'col2', 'Check that foreign key was NOT inherited for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM'), 'col2', 'Check that foreign key was NOT inherited for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM'), 'col2', 'Check that foreign key was NOT inherited for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM'), 'col2', 'Check that foreign key was NOT inherited for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM'), 'col2', 'Check that foreign key was NOT inherited for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM'), 'col2', 'Check that foreign key was NOT inherited for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM'), 'col2', 'Check that foreign key was NOT inherited for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM')); SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table'')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved'); 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'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '1 month'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '2 months'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(26,30), CURRENT_TIMESTAMP + '3 months'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(31,37), CURRENT_TIMESTAMP + '4 months'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(40,49), CURRENT_TIMESTAMP - '1 month'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(50,70), CURRENT_TIMESTAMP - '2 months'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(71,85), CURRENT_TIMESTAMP - '3 months'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(86,100), CURRENT_TIMESTAMP - '4 months'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM'), ARRAY[7], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM'), ARRAY[21], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM')); UPDATE part_config SET premake = 5, optimize_trigger = 5 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(101,122), CURRENT_TIMESTAMP + '5 months'::interval); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'7 months'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'8 months'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY_MM')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM')||' does not exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 months'::interval, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 months'::interval, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY_MM')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 months'::interval, 'YYYY_MM'), 'col2', 'Check that foreign key was NOT inherited for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 months'::interval, 'YYYY_MM')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 months'::interval, 'YYYY_MM'), 'col2', 'Check that foreign key was NOT inherited for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 months'::interval, 'YYYY_MM')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 months'::interval, 'YYYY_MM'), 'col2', 'Check that foreign key was NOT inherited for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 months'::interval, 'YYYY_MM')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 months'::interval, 'YYYY_MM'), 'col2', 'Check that foreign key was NOT inherited for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 months'::interval, 'YYYY_MM')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 months'::interval, 'YYYY_MM'), 'col2', 'Check that foreign key was NOT inherited for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 months'::interval, 'YYYY_MM')); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM'), ARRAY[22], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM')); UPDATE part_config SET premake = 6, optimize_trigger = 6 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(123,150), CURRENT_TIMESTAMP + '6 months'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[148], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM'), ARRAY[28], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM')); -- +5 month data was inserted above so 2 children get made SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'11 months'::interval, 'YYYY_MM')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY_MM')||' does not exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 months'::interval, 'YYYY_MM'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 months'::interval, 'YYYY_MM')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM'), 'col2', 'Check that foreign key was NOT inherited for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 months'::interval, 'YYYY_MM'), 'col2', 'Check that foreign key was NOT inherited for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 months'::interval, 'YYYY_MM')); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '20 months'::interval); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT reapply_privileges('partman_test.time_taptest_table'); -- Currently unable to do drop_partition test reliably for monthly due to differing month lengths (sometimes drops 2 partitions instead of 1) SELECT undo_partition('partman_test.time_taptest_table', 20, p_keep_table := false); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[159], 'Check count from parent table after undo'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'4 months'::interval, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'4 months'::interval, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'5 months'::interval, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'7 months'::interval, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'8 months'::interval, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'10 months'::interval, 'YYYY_MM')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'11 months'::interval, 'YYYY_MM')||' does not exist'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test-time-quarter-hour.sql000066400000000000000000002302061412761706100217300ustar00rootroot00000000000000-- ########## TIME QUARTER-HOUR TESTS ########## \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(151); CREATE SCHEMA partman_test; CREATE SCHEMA partman_retention_test; CREATE ROLE partman_basic; CREATE ROLE partman_revoke; CREATE ROLE partman_owner; CREATE TABLE partman_test.time_taptest_table (col1 int primary key, col2 text, col3 timestamptz NOT NULL DEFAULT now()); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); GRANT SELECT,INSERT,UPDATE ON partman_test.time_taptest_table TO partman_basic; GRANT ALL ON partman_test.time_taptest_table TO partman_revoke; SELECT create_parent('partman_test.time_taptest_table', 'col3', 'partman', 'quarter-hour'); -- Must run_maintenance because when interval time is between 1 hour and 1 minute, the first partition name done by above is always the nearest hour rounded down SELECT run_maintenance(); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0), 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0), 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'15 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'15 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'45 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'45 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'75 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'75 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'15 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'15 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'30 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'45 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'45 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'60 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); /* extra previous tables may exist due to new rounding down of the hour. Test left here for manual checking SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'75 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'75 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exist'); */ SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0), 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0), 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'15 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'15 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'45 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'45 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0), 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0), 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'15 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'15 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'30 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'45 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'45 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'60 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0), 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0), 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'15 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'15 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'45 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'45 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'15 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'15 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'30 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'45 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'45 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'60 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0), 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0), 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'15 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'15 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'45 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'45 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'15 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'15 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'30 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'45 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'45 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'60 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table'')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved'); 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(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0), 'YYYY_MM_DD_HH24MI'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0), 'YYYY_MM_DD_HH24MI')); REVOKE INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON partman_test.time_taptest_table FROM partman_revoke; INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '15 mins'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '30 mins'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(26,30), CURRENT_TIMESTAMP + '45 mins'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(31,37), CURRENT_TIMESTAMP + '60 mins'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(40,49), CURRENT_TIMESTAMP - '15 mins'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(50,70), CURRENT_TIMESTAMP - '30 mins'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(71,85), CURRENT_TIMESTAMP - '45 mins'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(86,100), CURRENT_TIMESTAMP - '60 mins'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'15 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'15 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'45 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'45 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY[7], 'Check count from time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'15 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'15 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY[21], 'Check count from time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'30 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'45 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'45 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'60 mins'::interval, 'YYYY_MM_DD_HH24MI')); UPDATE part_config SET premake = 5, optimize_trigger = 5 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(101,122), CURRENT_TIMESTAMP + '75 mins'::interval); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'75 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'75 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'105 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'105 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'135 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'135 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'75 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'75 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'105 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'105 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'135 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'135 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'75 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY[22], 'Check count from time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'75 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'75 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'75 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'105 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'105 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'135 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'135 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'75 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'75 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'105 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'105 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'135 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'135 mins'::interval, 'YYYY_MM_DD_HH24MI')); GRANT DELETE ON partman_test.time_taptest_table TO partman_basic; REVOKE ALL ON partman_test.time_taptest_table FROM partman_revoke; ALTER TABLE partman_test.time_taptest_table OWNER TO partman_owner; UPDATE part_config SET premake = 6, optimize_trigger = 6 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(123,150), CURRENT_TIMESTAMP + '90 mins'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[148], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY[28], 'Check count from time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'165 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'165 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'180 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'180 mins'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'165 mins'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'165 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'165 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'165 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'165 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'165 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'165 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'165 mins'::interval, 'YYYY_MM_DD_HH24MI')); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '300 mins'::interval); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT reapply_privileges('partman_test.time_taptest_table'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0), 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0), 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'15 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'15 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'30 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'45 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'45 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'60 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'15 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'15 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'45 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'45 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'75 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'75 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'105 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'105 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'135 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'135 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'165 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'165 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0), 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0), 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'15 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'15 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'45 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'45 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'75 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'75 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'105 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'105 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'135 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'135 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'165 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'165 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0), 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0), 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'15 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'15 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'30 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'45 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'45 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'60 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'15 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'15 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'45 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'45 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'75 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'75 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'105 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'105 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'135 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'135 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'165 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'165 mins'::interval, 'YYYY_MM_DD_HH24MI')); SELECT drop_partition_time('partman_test.time_taptest_table', '45 mins', p_keep_table := false); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'60 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exist'); UPDATE part_config SET retention = '30 mins'::interval WHERE parent_table = 'partman_test.time_taptest_table'; SELECT drop_partition_time('partman_test.time_taptest_table', p_retention_schema := 'partman_retention_test'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'45 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'45 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exist'); SELECT has_table('partman_retention_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'45 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'45 mins'::interval, 'YYYY_MM_DD_HH24MI')||' got moved to new schema'); SELECT undo_partition('partman_test.time_taptest_table', 20, p_keep_table := false); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[129], 'Check count from parent table after undo'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0), 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0), 'YYYY_MM_DD_HH24MI')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'15 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'15 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)-'30 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'15 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'15 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'30 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'45 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'45 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'60 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'75 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'75 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'90 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'105 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'105 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'120 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'135 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'135 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'150 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'165 mins'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_'||to_char(date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0)+'165 mins'::interval, 'YYYY_MM_DD_HH24MI')||' does not exist'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test-time-quarterly-trunc.sql000066400000000000000000001261051412761706100224550ustar00rootroot00000000000000-- ########## TIME QUARTERLY TESTS ########## -- Other tests: Long name truncation, undo partitioning but keep tables \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(124); CREATE SCHEMA partman_test; CREATE ROLE partman_basic; CREATE ROLE partman_revoke; CREATE ROLE partman_owner; CREATE TABLE partman_test.time_taptest_table_1234567890123456789012345678901234567890 (col1 int primary key, col2 text, col3 timestamptz NOT NULL DEFAULT now()); INSERT INTO partman_test.time_taptest_table_1234567890123456789012345678901234567890 (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); GRANT SELECT,INSERT,UPDATE ON partman_test.time_taptest_table_1234567890123456789012345678901234567890 TO partman_basic; GRANT ALL ON partman_test.time_taptest_table_1234567890123456789012345678901234567890 TO partman_revoke; SELECT create_parent('partman_test.time_taptest_table_1234567890123456789012345678901234567890', 'col3', 'partman', 'quarterly'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP, 'YYYY"q"Q')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY"q"Q')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY"q"Q')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q')||' does not exists'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY"q"Q')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'6 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'6 months'::interval, 'YYYY"q"Q')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'9 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'9 months'::interval, 'YYYY"q"Q')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'12 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'12 months'::interval, 'YYYY"q"Q')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'15 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'15 months'::interval, 'YYYY"q"Q')||' does not exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP, 'YYYY"q"Q'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP, 'YYYY"q"Q')); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q')); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q')); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY"q"Q'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY"q"Q')); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY"q"Q'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY"q"Q')); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY"q"Q'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY"q"Q')); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'6 months'::interval, 'YYYY"q"Q'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'6 months'::interval, 'YYYY"q"Q')); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'9 months'::interval, 'YYYY"q"Q'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'9 months'::interval, 'YYYY"q"Q')); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'12 months'::interval, 'YYYY"q"Q'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'12 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'6 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'6 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'9 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'9 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'12 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'12 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP, 'YYYY"q"Q'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY"q"Q'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY"q"Q'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY"q"Q'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'6 months'::interval, 'YYYY"q"Q'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'6 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'9 months'::interval, 'YYYY"q"Q'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'9 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'12 months'::interval, 'YYYY"q"Q'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'12 months'::interval, 'YYYY"q"Q')); SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table_1234567890123456789012345678901234567890'')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved'); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_1234567890123456789012345678901234567890', 'Check that parent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_1234567890123456789012345678901234567890', ARRAY[10], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP, 'YYYY"q"Q'), ARRAY[10], 'Check count from time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP, 'YYYY"q"Q')); REVOKE INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON partman_test.time_taptest_table_1234567890123456789012345678901234567890 FROM partman_revoke; INSERT INTO partman_test.time_taptest_table_1234567890123456789012345678901234567890 (col1, col3) VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '3 months'::interval); INSERT INTO partman_test.time_taptest_table_1234567890123456789012345678901234567890 (col1, col3) VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '6 months'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_1234567890123456789012345678901234567890', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_1234567890123456789012345678901234567890', ARRAY[25], 'Check count from time_taptest_table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q'), ARRAY[10], 'Check count from time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q'), ARRAY[5], 'Check count from time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q')); UPDATE part_config SET premake = 5, optimize_trigger = 5 WHERE parent_table = 'partman_test.time_taptest_table_1234567890123456789012345678901234567890'; SELECT run_maintenance(); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'18 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'18 months'::interval, 'YYYY"q"Q')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'21 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'21 months'::interval, 'YYYY"q"Q')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'24 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'24 months'::interval, 'YYYY"q"Q')||' does not exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q')); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'18 months'::interval, 'YYYY"q"Q'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'18 months'::interval, 'YYYY"q"Q')); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'21 months'::interval, 'YYYY"q"Q'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'21 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'18 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'18 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'21 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'21 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'18 months'::interval, 'YYYY"q"Q'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'18 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'21 months'::interval, 'YYYY"q"Q'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'21 months'::interval, 'YYYY"q"Q')); GRANT DELETE ON partman_test.time_taptest_table_1234567890123456789012345678901234567890 TO partman_basic; REVOKE ALL ON partman_test.time_taptest_table_1234567890123456789012345678901234567890 FROM partman_revoke; ALTER TABLE partman_test.time_taptest_table_1234567890123456789012345678901234567890 OWNER TO partman_owner; UPDATE part_config SET premake = 6, optimize_trigger = 6 WHERE parent_table = 'partman_test.time_taptest_table_1234567890123456789012345678901234567890'; SELECT run_maintenance(); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'24 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'24 months'::interval, 'YYYY"q"Q')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'27 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'27 months'::interval, 'YYYY"q"Q')||' does not exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'24 months'::interval, 'YYYY"q"Q'), ARRAY['col1'], 'Check for primary key in time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'24 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'24 months'::interval, 'YYYY"q"Q'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'24 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'24 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'24 months'::interval, 'YYYY"q"Q')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'24 months'::interval, 'YYYY"q"Q'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'24 months'::interval, 'YYYY"q"Q')); INSERT INTO partman_test.time_taptest_table_1234567890123456789012345678901234567890 (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '60 months'::interval); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table_1234567890123456789012345678901234567890', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT reapply_privileges('partman_test.time_taptest_table_1234567890123456789012345678901234567890'); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'18 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'18 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'21 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'21 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'24 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'24 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP, 'YYYY"q"Q'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY"q"Q'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'6 months'::interval, 'YYYY"q"Q'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'6 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'9 months'::interval, 'YYYY"q"Q'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'9 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'12 months'::interval, 'YYYY"q"Q'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'12 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY"q"Q'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY"q"Q'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'18 months'::interval, 'YYYY"q"Q'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'18 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'21 months'::interval, 'YYYY"q"Q'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'21 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'24 months'::interval, 'YYYY"q"Q'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'24 months'::interval, 'YYYY"q"Q')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP, 'YYYY"q"Q'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP, 'YYYY"q"Q')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY"q"Q'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY"q"Q')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'6 months'::interval, 'YYYY"q"Q'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'6 months'::interval, 'YYYY"q"Q')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'9 months'::interval, 'YYYY"q"Q'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'9 months'::interval, 'YYYY"q"Q')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'12 months'::interval, 'YYYY"q"Q'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'12 months'::interval, 'YYYY"q"Q')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY"q"Q'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY"q"Q')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY"q"Q'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY"q"Q')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'18 months'::interval, 'YYYY"q"Q'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'18 months'::interval, 'YYYY"q"Q')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'21 months'::interval, 'YYYY"q"Q'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'21 months'::interval, 'YYYY"q"Q')); SELECT table_owner_is ('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'24 months'::interval, 'YYYY"q"Q'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'24 months'::interval, 'YYYY"q"Q')); SELECT undo_partition('partman_test.time_taptest_table_1234567890123456789012345678901234567890', 20); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY"q"Q')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY"q"Q')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'6 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'6 months'::interval, 'YYYY"q"Q')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'6 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'6 months'::interval, 'YYYY"q"Q')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'9 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'9 months'::interval, 'YYYY"q"Q')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'9 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'9 months'::interval, 'YYYY"q"Q')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'12 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'12 months'::interval, 'YYYY"q"Q')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'12 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP-'12 months'::interval, 'YYYY"q"Q')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY"q"Q')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY"q"Q')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY"q"Q')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY"q"Q')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'18 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'18 months'::interval, 'YYYY"q"Q')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'18 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'18 months'::interval, 'YYYY"q"Q')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'21 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'21 months'::interval, 'YYYY"q"Q')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'21 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'21 months'::interval, 'YYYY"q"Q')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'24 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'24 months'::interval, 'YYYY"q"Q')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'24 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_123456789012345678901234567890123456_p'||to_char(CURRENT_TIMESTAMP+'24 months'::interval, 'YYYY"q"Q')||' is empty'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test-time-quarterly.sql000066400000000000000000001163471412761706100213330ustar00rootroot00000000000000-- ########## TIME QUARTERLY TESTS ########## \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(141); CREATE SCHEMA partman_test; CREATE ROLE partman_basic; CREATE ROLE partman_revoke; CREATE ROLE partman_owner; CREATE TABLE partman_test.time_taptest_table (col1 int primary key, col2 text, col3 timestamptz NOT NULL DEFAULT now()); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); GRANT SELECT,INSERT,UPDATE ON partman_test.time_taptest_table TO partman_basic; GRANT ALL ON partman_test.time_taptest_table TO partman_revoke; SELECT create_parent('partman_test.time_taptest_table', 'col3', 'partman', 'quarterly'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY"q"Q'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP, 'YYYY"q"Q')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY"q"Q')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY"q"Q')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q')||' does not exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY"q"Q')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'6 months'::interval, 'YYYY"q"Q')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'9 months'::interval, 'YYYY"q"Q')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'12 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'12 months'::interval, 'YYYY"q"Q')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'15 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'15 months'::interval, 'YYYY"q"Q')||' does not exist'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY"q"Q'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY"q"Q')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY"q"Q'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY"q"Q')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY"q"Q'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY"q"Q')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY"q"Q'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY"q"Q')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY"q"Q'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY"q"Q')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 months'::interval, 'YYYY"q"Q'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 months'::interval, 'YYYY"q"Q')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 months'::interval, 'YYYY"q"Q'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 months'::interval, 'YYYY"q"Q')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'12 months'::interval, 'YYYY"q"Q'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'12 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'12 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'12 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY"q"Q'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_'||to_char(CURRENT_TIMESTAMP, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY"q"Q'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY"q"Q'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY"q"Q'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 months'::interval, 'YYYY"q"Q'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 months'::interval, 'YYYY"q"Q'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'12 months'::interval, 'YYYY"q"Q'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'12 months'::interval, 'YYYY"q"Q')); SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table'')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved'); 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"q"Q'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY"q"Q')); REVOKE INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON partman_test.time_taptest_table FROM partman_revoke; INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '3 months'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '6 months'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(26,30), CURRENT_TIMESTAMP + '9 months'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(31,37), CURRENT_TIMESTAMP + '12 months'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(40,49), CURRENT_TIMESTAMP - '3 months'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(50,70), CURRENT_TIMESTAMP - '6 months'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(71,85), CURRENT_TIMESTAMP - '9 months'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(86,100), CURRENT_TIMESTAMP - '12 months'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY"q"Q'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY"q"Q')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY"q"Q'), ARRAY[7], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY"q"Q')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY"q"Q'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY"q"Q')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 months'::interval, 'YYYY"q"Q'), ARRAY[21], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 months'::interval, 'YYYY"q"Q')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 months'::interval, 'YYYY"q"Q'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 months'::interval, 'YYYY"q"Q')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'12 months'::interval, 'YYYY"q"Q'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'12 months'::interval, 'YYYY"q"Q')); UPDATE part_config SET premake = 5, optimize_trigger = 5 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(101,122), CURRENT_TIMESTAMP + '15 months'::interval); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'18 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'18 months'::interval, 'YYYY"q"Q')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'21 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'21 months'::interval, 'YYYY"q"Q')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'24 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'24 months'::interval, 'YYYY"q"Q')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'27 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'27 months'::interval, 'YYYY"q"Q')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'30 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'30 months'::interval, 'YYYY"q"Q')||' does not exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'18 months'::interval, 'YYYY"q"Q'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'18 months'::interval, 'YYYY"q"Q')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'21 months'::interval, 'YYYY"q"Q'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'21 months'::interval, 'YYYY"q"Q')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'24 months'::interval, 'YYYY"q"Q'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'24 months'::interval, 'YYYY"q"Q')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'27 months'::interval, 'YYYY"q"Q'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'27 months'::interval, 'YYYY"q"Q')); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q'), ARRAY[22], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'18 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'18 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'21 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'21 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'24 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'24 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'27 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'27 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'18 months'::interval, 'YYYY"q"Q'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'18 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'21 months'::interval, 'YYYY"q"Q'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'21 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'24 months'::interval, 'YYYY"q"Q'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'24 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'27 months'::interval, 'YYYY"q"Q'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'27 months'::interval, 'YYYY"q"Q')); GRANT DELETE ON partman_test.time_taptest_table TO partman_basic; REVOKE ALL ON partman_test.time_taptest_table FROM partman_revoke; ALTER TABLE partman_test.time_taptest_table OWNER TO partman_owner; UPDATE part_config SET premake = 6, optimize_trigger = 6 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(123,150), CURRENT_TIMESTAMP + '18 months'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[148], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'18 months'::interval, 'YYYY"q"Q'), ARRAY[28], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'18 months'::interval, 'YYYY"q"Q')); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'30 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'30 months'::interval, 'YYYY"q"Q')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'33 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'33 months'::interval, 'YYYY"q"Q')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'36 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'36 months'::interval, 'YYYY"q"Q')||' does not exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'30 months'::interval, 'YYYY"q"Q'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'30 months'::interval, 'YYYY"q"Q')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'33 months'::interval, 'YYYY"q"Q'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'33 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'30 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'30 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'33 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'33 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'30 months'::interval, 'YYYY"q"Q'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'30 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'33 months'::interval, 'YYYY"q"Q'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'33 months'::interval, 'YYYY"q"Q')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'30 months'::interval, 'YYYY"q"Q'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'30 months'::interval, 'YYYY"q"Q')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'33 months'::interval, 'YYYY"q"Q'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'33 months'::interval, 'YYYY"q"Q')); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '60 months'::interval); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT reapply_privileges('partman_test.time_taptest_table'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'12 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'12 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'18 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'18 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'21 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'21 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'24 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'24 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'27 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'27 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'30 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'30 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'33 months'::interval, 'YYYY"q"Q'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'33 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY"q"Q'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY"q"Q'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY"q"Q'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'18 months'::interval, 'YYYY"q"Q'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'18 months'::interval, 'YYYY"q"Q')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY"q"Q'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY"q"Q')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY"q"Q'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY"q"Q')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY"q"Q'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY"q"Q')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'18 months'::interval, 'YYYY"q"Q'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'18 months'::interval, 'YYYY"q"Q')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY"q"Q'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY"q"Q')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 months'::interval, 'YYYY"q"Q'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 months'::interval, 'YYYY"q"Q')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 months'::interval, 'YYYY"q"Q'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 months'::interval, 'YYYY"q"Q')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'12 months'::interval, 'YYYY"q"Q'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'12 months'::interval, 'YYYY"q"Q')); -- Currently unable to do drop_partition test reliably for quarterly due to differing month lengths (sometimes drops 2 partitions instead of 1) SELECT undo_partition('partman_test.time_taptest_table', 20, p_keep_table := false); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[159], 'Check count from parent table after undo'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY"q"Q'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP, 'YYYY"q"Q')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY"q"Q')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'6 months'::interval, 'YYYY"q"Q')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'9 months'::interval, 'YYYY"q"Q')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'12 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'12 months'::interval, 'YYYY"q"Q')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY"q"Q')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'6 months'::interval, 'YYYY"q"Q')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'9 months'::interval, 'YYYY"q"Q')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'12 months'::interval, 'YYYY"q"Q')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'15 months'::interval, 'YYYY"q"Q')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'18 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'18 months'::interval, 'YYYY"q"Q')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'21 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'21 months'::interval, 'YYYY"q"Q')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'24 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'24 months'::interval, 'YYYY"q"Q')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'27 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'27 months'::interval, 'YYYY"q"Q')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'30 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'30 months'::interval, 'YYYY"q"Q')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'33 months'::interval, 'YYYY"q"Q'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'33 months'::interval, 'YYYY"q"Q')||' does not exist'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test-time-start-partition.sql000066400000000000000000000604741412761706100224460ustar00rootroot00000000000000-- ########## TIME STATIC TESTS ########## -- Test p_start_partition parameter \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(92); CREATE SCHEMA partman_test; CREATE SCHEMA partman_retention_test; CREATE TABLE partman_test.time_taptest_table (col1 int primary key, col2 text, col3 timestamptz NOT NULL DEFAULT now()); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); SELECT create_parent('partman_test.time_taptest_table', 'col3', 'partman', 'daily', p_start_partition := (CURRENT_TIMESTAMP - '10 days'::interval)::text); 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 has_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 exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 days'::interval, 'YYYY_MM_DD')||' does exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 days'::interval, 'YYYY_MM_DD')||' does exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 days'::interval, 'YYYY_MM_DD')||' does exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 days'::interval, 'YYYY_MM_DD')||' does exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'10 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'10 days'::interval, 'YYYY_MM_DD')||' does exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'11 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'11 days'::interval, 'YYYY_MM_DD')||' does not exist'); 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 col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'10 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'10 days'::interval, 'YYYY_MM_DD')); SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table'')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved'); 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')); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '1 day'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '2 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(26,30), CURRENT_TIMESTAMP + '3 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(31,37), CURRENT_TIMESTAMP + '4 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(40,49), CURRENT_TIMESTAMP - '1 day'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(50,70), CURRENT_TIMESTAMP - '2 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(71,85), CURRENT_TIMESTAMP - '3 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(86,100), CURRENT_TIMESTAMP - '4 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(101,110), CURRENT_TIMESTAMP - '5 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(111,120), CURRENT_TIMESTAMP - '6 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(121,130), CURRENT_TIMESTAMP - '7 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(131,140), CURRENT_TIMESTAMP - '8 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(141,150), CURRENT_TIMESTAMP - '9 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(151,160), CURRENT_TIMESTAMP - '10 days'::interval); SELECT partition_data_time('partman_test.time_taptest_table', p_batch_count := 20); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY[10], '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[5], '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[5], '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[7], '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[10], '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[21], '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[15], '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[15], '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[10], '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-'6 days'::interval, 'YYYY_MM_DD'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 days'::interval, 'YYYY_MM_DD')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 days'::interval, 'YYYY_MM_DD'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 days'::interval, 'YYYY_MM_DD')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 days'::interval, 'YYYY_MM_DD'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 days'::interval, 'YYYY_MM_DD')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 days'::interval, 'YYYY_MM_DD'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 days'::interval, 'YYYY_MM_DD')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'10 days'::interval, 'YYYY_MM_DD'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'10 days'::interval, 'YYYY_MM_DD')); UPDATE part_config SET premake = 5, optimize_trigger = 5 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(161,170), CURRENT_TIMESTAMP + '5 days'::interval); SELECT has_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')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); UPDATE part_config SET premake = 6, optimize_trigger = 6 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(171,180), CURRENT_TIMESTAMP + '6 days'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[178], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')||' does not exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '20 days'::interval); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT drop_partition_time('partman_test.time_taptest_table', '3 days', p_keep_table := false); 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')||' does not exist'); UPDATE part_config SET retention = '2 days'::interval WHERE parent_table = 'partman_test.time_taptest_table'; SELECT drop_partition_time('partman_test.time_taptest_table', p_retention_schema := 'partman_retention_test'); 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')||' does not exist'); SELECT has_table('partman_retention_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')||' got moved to new schema'); SELECT undo_partition('partman_test.time_taptest_table', 20, p_keep_table := false); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[99], 'Check count from parent table after undo'); SELECT hasnt_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')||' does not exist'); SELECT hasnt_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')||' does not exist'); SELECT hasnt_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')||' does not exist'); SELECT hasnt_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')||' does not exist'); SELECT hasnt_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')||' does not exist'); 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')||' does not exist'); 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')||' does not exist'); 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 hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test-time-time-time-subpart.sql000066400000000000000000001020331412761706100226360ustar00rootroot00000000000000-- ########## TIME PARENT / TIME SUBPARENT / TIME SUB-SUB-PARENT DYNAMIC ########## -- Currently tests 23, 39, 47 & 67 may fail around new years boundaries -- Additional tests: Make sure option for handling exceptions in triggers works, test stop_sub_partition() \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(79); CREATE SCHEMA partman_test; CREATE TABLE partman_test.time_taptest_table (col1 int primary key, col2 text, col3 timestamptz NOT NULL DEFAULT now()); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); -- yearly SELECT create_parent('partman_test.time_taptest_table', 'col3', 'partman', 'yearly', p_premake := 2); -- Make sure optimize values can be different UPDATE part_config SET optimize_trigger = 5, optimize_constraint = 10, trigger_exception_handling = true WHERE parent_table = 'partman_test.time_taptest_table'; SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||' does not exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY')||' does not exist'); -- Move data from parent SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table'')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved'); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that yearly 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'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')); -- monthly SELECT create_sub_parent('partman_test.time_taptest_table', 'col3', 'partman', 'monthly', p_premake := 2); UPDATE part_config_sub SET sub_trigger_exception_handling = true WHERE sub_parent = 'partman_test.time_taptest_table'; -- Make sure optimize values can be different UPDATE part_config_sub SET sub_optimize_trigger = 5, sub_optimize_constraint = 10, sub_retention_keep_table = false WHERE sub_parent = 'partman_test.time_taptest_table'; SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM')||' exists'); -- Next test starts failing in Oct since the next year's partition was made and must contain one child table (january) SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM')||' does not exist (this test may fail around year boundary. See comment in test code)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM')||' does not exist'); -- Check that previous and future years had the minimal partition made -- year +/- 1 tests may fail around year boundary. Tables may or may not exist depending on premake. That's fine. Should be ok for further in the future. SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')||'_01', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')||'_01 exists (this test may fail around year boundary. See comment in test code)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')||'_02', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')||'_02 does not exists (this test may fail around year boundary. See comment in test code)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_01', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_01 exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_02', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_02 does not exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_01', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_01 exists (this test may fail around year boundary. See comment in test code)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_02', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_02 exists (this test may fail around year boundary. See comment in test code)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_01', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'2 year'::interval, 'YYYY')||'_01 exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_02', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'2 year'::interval, 'YYYY')||'_02 exists'); SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||''')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved (yearly subparent)'); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY'), 'Check data got moved out of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')); -- Check subpart config SELECT results_eq('SELECT sub_parent FROM part_config_sub ORDER BY sub_parent', ARRAY['partman_test.time_taptest_table'], 'Check that part_config_sub has all tables configured as needed'); -- daily SELECT results_eq('SELECT create_sub_parent(''partman_test.time_taptest_table_p''||to_char(CURRENT_TIMESTAMP, ''YYYY''), ''col3'', ''partman'', ''daily'', p_premake := 2)', ARRAY[true], 'Subpartitioning partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||' should return true'); UPDATE part_config_sub SET sub_trigger_exception_handling = true WHERE sub_parent = 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY'); SELECT results_eq('SELECT create_sub_parent(''partman_test.time_taptest_table_p''||to_char(CURRENT_TIMESTAMP+''1 year''::interval, ''YYYY''), ''col3'', ''partman'', ''daily'', p_premake := 2)', ARRAY[true], 'Subpartitioning partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')||' should return true'); UPDATE part_config_sub SET sub_trigger_exception_handling = true WHERE sub_parent = 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY'); SELECT results_eq('SELECT create_sub_parent(''partman_test.time_taptest_table_p''||to_char(CURRENT_TIMESTAMP+''2 years''::interval, ''YYYY''), ''col3'', ''partman'', ''daily'', p_premake := 2)', ARRAY[true], 'Subpartitioning partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||' should return true'); UPDATE part_config_sub SET sub_trigger_exception_handling = true WHERE sub_parent = 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY'); SELECT results_eq('SELECT create_sub_parent(''partman_test.time_taptest_table_p''||to_char(CURRENT_TIMESTAMP-''1 year''::interval, ''YYYY''), ''col3'', ''partman'', ''daily'', p_premake := 2)', ARRAY[true], 'Subpartitioning partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||' should return true'); UPDATE part_config_sub SET sub_trigger_exception_handling = true WHERE sub_parent = 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY'); SELECT results_eq('SELECT create_sub_parent(''partman_test.time_taptest_table_p''||to_char(CURRENT_TIMESTAMP-''2 years''::interval, ''YYYY''), ''col3'', ''partman'', ''daily'', p_premake := 2)', ARRAY[true], 'Subpartitioning partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||' should return true'); UPDATE part_config_sub SET sub_trigger_exception_handling = true WHERE sub_parent = 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')||' exists')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM')||'_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')||'_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' does not exists'); -- This test may fail around the end of the year or the end of some months since the minimal partition for the next year or next month was created. That's fine SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' does not exist (this test may fail around year or month boundaries. See comment in test code)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM')||'_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')||'_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); -- Check that previous and future years had the minimal partition made -- year +/- 1 tests may fail around year boundary. Tables may or may not exist depending on premake. That's fine. Should be ok for further in the future. SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 years'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP+'1 years'::interval, 'YYYY')||'_01_01', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 years'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP+'1 years'::interval, 'YYYY')||'_01_01 exists (this test may fail around year boundary. See comment in test code)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 years'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP+'1 years'::interval, 'YYYY')||'_01_02', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 years'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP+'1 years'::interval, 'YYYY')||'_01_02 does not exists (this test may fail around year boundary. See comment in test code)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_01_01', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_01_01 exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_01_02', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_01_02 does not exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_01_01', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_01_01 exists (this test may fail around year boundary. See comment in test code)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_01_02', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_01_02 exists (this test may fail around year boundary. See comment in test code)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_01_01', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'2 year'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_01_01 exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_01_02', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'2 year'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_01_02 does not exists'); SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')||''')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved (monthly subparent)'); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM'), 'Check data got moved out of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); -- Check subpart config SELECT results_eq('SELECT sub_parent FROM part_config_sub ORDER BY sub_parent', ARRAY['partman_test.time_taptest_table', 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY'), 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY'), 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY'), 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY'), 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')], 'Check that part_config_sub has all tables configured as needed'); INSERT INTO partman_test.time_taptest_table (col1, col2, col3) VALUES (generate_series(11,20), 'stuff', CURRENT_TIMESTAMP+'1 day'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check new data did not go into parent time_taptest_table'); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day', 'YYYY'), 'Check new data did not go into subparent time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day', 'YYYY')); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day', 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 day', 'YYYY_MM'), 'Check new data did not go into subparent time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day', 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 day', 'YYYY_MM')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day', 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 day', 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP+'1 day', 'YYYY_MM_DD'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day', 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 day', 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP+'1 day', 'YYYY_MM_DD')); UPDATE part_config SET premake = 3 WHERE parent_table LIKE 'partman_test.time_taptest_table%' AND partition_type = 'partman'; SELECT run_maintenance(); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||' exists'); -- This test may fail if the next year's subpartition was already made and +3 months is into the next year, which may have been made by the condition that guarantees there is always at least 1 child table in a subpartition. SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM')||' exists. This test may fail in months near the end of the year.'); -- This test may fail if the next month's subpartition was already made and +3 days is after the 1st of the month, which may have been made by the condition that guarantees there is always at least 1 child table in a subpartition. SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' exists. This test may fail depending on the day of the month it is run.'); -- Check that future year had the minimal partition made SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_01', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_01 exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_01_01', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_01_01 exists'); -- Check subpart config SELECT results_eq('SELECT sub_parent FROM part_config_sub ORDER BY sub_parent', ARRAY['partman_test.time_taptest_table', 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY'), 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY'), 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY'), 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY'), 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY'), 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')], 'Check that part_config_sub has all tables configured as needed'); INSERT INTO partman_test.time_taptest_table (col1, col2, col3) VALUES (generate_series(21,30), 'stuff', CURRENT_TIMESTAMP+'3 years'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check new data did not go into parent time_taptest_table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY')||'. Data should have gone here since monthly subpartition for it does not exist. This test may fail in January since that monthly partition should exist.'); -- Move data from yearly parent table and create appropriate monthly child for it SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||''')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'. This test may fail in January or the first of any month since that monthly/daily partition should exist.'); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY'), 'Check new data did not go into subparent time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY_MM'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY_MM')||'. Data should have gone here since daily subpartition for it does not exist.'); -- Move data from monthly parent table and create appropriate daily child for it SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY_MM')||''')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY_MM')||'. This test may fail in January or the first of any month since that monthly/daily partition should exist.'); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY_MM'), 'Check new data did not go into subparent time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY_MM')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY_MM_DD'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY_MM_DD')); -- Check that stopping subpartitioning works SELECT stop_sub_partition('partman_test.time_taptest_table'); SELECT run_maintenance(); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY')||' does exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'4 year'::interval, 'YYYY')||'_01', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'4 year'::interval, 'YYYY')||'_01 does not exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY')||' does exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'5 year'::interval, 'YYYY')||'_01', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'5 year'::interval, 'YYYY')||'_01 does not exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY')||' does exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'6 year'::interval, 'YYYY')||'_01', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'6 year'::interval, 'YYYY')||'_01 does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY')||' does not exist'); /* -- Disabled test for now. New years makes testing undo functions hard. Would be calling undo on year+1_01 twice and second one would fail. SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[30], 'Check count from top parent'); SELECT throws_ok('SELECT undo_partition_time(''partman_test.time_taptest_table_p''||to_char(CURRENT_TIMESTAMP+''3 years''::interval, ''YYYY''), 20, p_keep_table := false)', 'P0001', 'Child table for this parent has child table(s) itself (partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_01). Run undo partitioning on this table or remove inheritance first to ensure all data is properly moved to parent CONTEXT: SQL statement "SELECT undo_partition_time(''partman_test.time_taptest_table_p''||to_char(CURRENT_TIMESTAMP+''3 years''::interval, ''YYYY''), 20, p_keep_table := false)" PL/pgSQL function throws_ok(text,character,text,text) line 16 at EXECUTE statement DETAIL: HINT: ', 'Check that undoing partitions is prevented if subpartitions still exist'); */ SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test-time-weekly-daily-subpart.sql000066400000000000000000003305651412761706100233610ustar00rootroot00000000000000-- ########## TIME WEEKLY-DAILY SUBPARTITION TESTS ########## -- Other tests: Mixed case & special characters, extra constraints, hybrid trigger puts data outside premake into proper tables, dropping only indexes in retention, grants/revokes, ensure retention doesn't drop final table -- Also tests that maintenance catches up on all subpartition tables as long as data exists in them -- NOTE Some tests failing on mondays. Commented them out with notes below. Thanks Garfield. BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(277); CREATE SCHEMA "Partman_test"; CREATE ROLE "partman-basic"; CREATE ROLE "Partman_Revoke"; CREATE ROLE partman_owner; CREATE TABLE "Partman_test"."Time-taptest-Table" ("COL1" int primary key, col2 text, "Col-3" timestamptz NOT NULL DEFAULT now()); -- Time data being inserted is truncated to the beginning of the week to avoid missing partitions INSERT INTO "Partman_test"."Time-taptest-Table" ("COL1", "Col-3") VALUES (generate_series(1,10), date_trunc('week', CURRENT_TIMESTAMP)); GRANT SELECT,INSERT,UPDATE ON "Partman_test"."Time-taptest-Table" TO "partman-basic"; GRANT ALL ON "Partman_test"."Time-taptest-Table" TO "Partman_Revoke"; SELECT create_parent('Partman_test.Time-taptest-Table', 'Col-3', 'partman', 'weekly', '{"COL1"}', p_premake := 4, p_start_partition := to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'YYYY-MM-DD HH24:MI:SS')); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||' exists (this week)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' exists (+1 weeks)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||' exists (+2 weeks)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' exists (+3 weeks)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' exists (+4 weeks)'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW'), 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW')||' does not exist (+5 weeks)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' exists (-1 weeks)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' exists (-2 weeks)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' exists (-3 weeks)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' exists (-4 weeks)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||' exists (-5 weeks)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||' exists (-6 weeks)'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW'), 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')||' does not exist (-7 weeks)'); SELECT col_is_pk('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), ARRAY['COL1'], 'Check for primary key in Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||' (this week)'); SELECT col_is_pk('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), ARRAY['COL1'], 'Check for primary key in Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' (+1 week)'); SELECT col_is_pk('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), ARRAY['COL1'], 'Check for primary key in Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||' (+2 week)'); SELECT col_is_pk('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), ARRAY['COL1'], 'Check for primary key in Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' (+3 week)'); SELECT col_is_pk('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), ARRAY['COL1'], 'Check for primary key in Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' (+4 week)'); SELECT col_is_pk('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), ARRAY['COL1'], 'Check for primary key in Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' (-1 week)'); SELECT col_is_pk('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), ARRAY['COL1'], 'Check for primary key in Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' (-2 week)'); SELECT col_is_pk('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), ARRAY['COL1'], 'Check for primary key in Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' (-3 week)'); SELECT col_is_pk('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), ARRAY['COL1'], 'Check for primary key in Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' (-4 week)'); SELECT col_is_pk('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), ARRAY['COL1'], 'Check for primary key in Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||' (-5 week)'); SELECT col_is_pk('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), ARRAY['COL1'], 'Check for primary key in Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||' (-6 week)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), 'partman-basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman-basic privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||' (this week)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), 'partman-basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman-basic privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' (+1 week)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), 'partman-basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman-basic privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||' (+2 week)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'partman-basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman-basic privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' (+3 week)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), 'partman-basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman-basic privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' (+4 week)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), 'partman-basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman-basic privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' (-1 week)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), 'partman-basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman-basic privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' (-2 week)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'partman-basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman-basic privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' (-3 week)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'partman-basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman-basic privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' (-4 week)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), 'partman-basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman-basic privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||' (-5 week)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), 'partman-basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman-basic privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||' (-6 week)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), 'Partman_Revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check Partman_Revoke privileges of Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||' (this week)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), 'Partman_Revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check Partman_Revoke privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' (+1 week)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), 'Partman_Revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check Partman_Revoke privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||' (+2 week)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'Partman_Revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check Partman_Revoke privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' (+3 week)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), 'Partman_Revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check Partman_Revoke privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' (+4 week)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), 'Partman_Revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check Partman_Revoke privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' (-1 week)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), 'Partman_Revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check Partman_Revoke privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' (-2 week)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'Partman_Revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check Partman_Revoke privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' (-3 week)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'Partman_Revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check Partman_Revoke privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' (-4 week)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), 'Partman_Revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check Partman_Revoke privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||' (-5 week)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), 'Partman_Revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check Partman_Revoke privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||' (-6 week)'); SELECT results_eq('SELECT partition_data_time(''Partman_test.Time-taptest-Table'')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved'); 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, 'IYYY"w"IW')||'"', ARRAY[10], 'Check count from Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||' (this week)'); REVOKE INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON "Partman_test"."Time-taptest-Table" FROM "Partman_Revoke"; INSERT INTO "Partman_test"."Time-taptest-Table" ("COL1", "Col-3") VALUES (generate_series(11,20), date_trunc('week', CURRENT_TIMESTAMP - '4 week'::interval)); INSERT INTO "Partman_test"."Time-taptest-Table" ("COL1", "Col-3") VALUES (generate_series(21,25), date_trunc('week', CURRENT_TIMESTAMP - '3 week'::interval)); INSERT INTO "Partman_test"."Time-taptest-Table" ("COL1", "Col-3") VALUES (generate_series(26,30), date_trunc('week', CURRENT_TIMESTAMP - '2 weeks'::interval)); INSERT INTO "Partman_test"."Time-taptest-Table" ("COL1", "Col-3") VALUES (generate_series(31,37), date_trunc('week', CURRENT_TIMESTAMP - '1 week'::interval)); INSERT INTO "Partman_test"."Time-taptest-Table" ("COL1", "Col-3") VALUES (generate_series(38,49), date_trunc('week', CURRENT_TIMESTAMP + '1 week'::interval)); INSERT INTO "Partman_test"."Time-taptest-Table" ("COL1", "Col-3") VALUES (generate_series(50,70), date_trunc('week', CURRENT_TIMESTAMP + '2 weeks'::interval)); INSERT INTO "Partman_test"."Time-taptest-Table" ("COL1", "Col-3") VALUES (generate_series(71,85), date_trunc('week', CURRENT_TIMESTAMP + '3 weeks'::interval)); INSERT INTO "Partman_test"."Time-taptest-Table" ("COL1", "Col-3") VALUES (generate_series(86,100), date_trunc('week', CURRENT_TIMESTAMP + '4 weeks'::interval)); -- Test hybrid trigger puts data in tables outside premake range INSERT INTO "Partman_test"."Time-taptest-Table" ("COL1", "Col-3") VALUES (generate_series(101,110), date_trunc('week', CURRENT_TIMESTAMP - '5 weeks'::interval)); INSERT INTO "Partman_test"."Time-taptest-Table" ("COL1", "Col-3") VALUES (generate_series(111,130), date_trunc('week', CURRENT_TIMESTAMP - '6 weeks'::interval)); -- Add data for checking that daily subpartition constraint management is working (8 days is before new premake value below). -- This isn't currently working, but leaving this here for future testing if I can get this working -- Also, when this test script is run on a monday, 8 days ago is 2 weeks ago, not 1 week ago, so it throws some of the rest of the tests off. -- INSERT INTO "Partman_test"."Time-taptest-Table" ("COL1", "Col-3") VALUES (generate_series(131,140), CURRENT_TIMESTAMP - '8 days'::interval); SELECT is_empty('SELECT * FROM ONLY "Partman_test"."Time-taptest-Table"', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||'"', ARRAY[10], 'Check count from Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||' (this week)'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||'"', ARRAY[7], 'Check count from Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' (-1 week)'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||'"', ARRAY[20], 'Check count from Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' (-6 week)'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||'"', ARRAY[10], 'Check count from Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' (-5 week)'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||'"', ARRAY[10], 'Check count from Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' (-4 week)'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||'"', ARRAY[5], 'Check count from Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' (-3 week)'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||'"', ARRAY[5], 'Check count from Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' (-2 week)'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||'"', ARRAY[12], 'Check count from Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' (+1 week)'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'2 week'::interval, 'IYYY"w"IW')||'"', ARRAY[21], 'Check count from Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'2 week'::interval, 'IYYY"w"IW')||' (+2 week)'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'3 week'::interval, 'IYYY"w"IW')||'"', ARRAY[15], 'Check count from Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'3 week'::interval, 'IYYY"w"IW')||' (+3 week)'); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'4 week'::interval, 'IYYY"w"IW')||'"', ARRAY[15], 'Check count from Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'4 week'::interval, 'IYYY"w"IW')||' (+4 week)'); -- Ensure all the current child tables get the new privileges given above so they propagate properly when subpartitions are created SELECT reapply_privileges('Partman_test.Time-taptest-Table'); SELECT create_sub_parent('Partman_test.Time-taptest-Table', 'Col-3', 'partman', 'daily', '{"COL1"}', p_premake := 4); -- Check daily partitions SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' exists (current day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' exists (+1 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' exists (+2 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' exists (+3 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' exists (+4 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||' exists (-1 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' exists (-2 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' exists (-3 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' exists (-4 day)'); SELECT col_is_pk('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['COL1'], 'Check for primary key in Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' (current day)'); SELECT col_is_pk('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'IYYY"w"IW')||'_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, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' (+1 day)'); SELECT col_is_pk('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'2 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'2 day'::interval, 'YYYY_MM_DD'), ARRAY['COL1'], 'Check for primary key in Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'2 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'2 day'::interval, 'YYYY_MM_DD')||' (+2 day)'); SELECT col_is_pk('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'3 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'3 day'::interval, 'YYYY_MM_DD'), ARRAY['COL1'], 'Check for primary key in Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'3 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'3 day'::interval, 'YYYY_MM_DD')||' (+3 day)'); SELECT col_is_pk('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'4 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'4 day'::interval, 'YYYY_MM_DD'), ARRAY['COL1'], 'Check for primary key in Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'4 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'4 day'::interval, 'YYYY_MM_DD')||' (+4 day)'); SELECT col_is_pk('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'IYYY"w"IW')||'_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, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||' (-1 day)'); SELECT col_is_pk('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'2 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'2 day'::interval, 'YYYY_MM_DD'), ARRAY['COL1'], 'Check for primary key in Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'2 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'2 day'::interval, 'YYYY_MM_DD')||' (-2 day)'); SELECT col_is_pk('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'3 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'3 day'::interval, 'YYYY_MM_DD'), ARRAY['COL1'], 'Check for primary key in Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'3 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'3 day'::interval, 'YYYY_MM_DD')||' (-3 day)'); SELECT col_is_pk('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'4 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'4 day'::interval, 'YYYY_MM_DD'), ARRAY['COL1'], 'Check for primary key in Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'4 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'4 day'::interval, 'YYYY_MM_DD')||' (-4 day)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD') , 'partman-basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman-basic privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' (now)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD') , 'partman-basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman-basic privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' (+1 day)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'2 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'2 day'::interval, 'YYYY_MM_DD') , 'partman-basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman-basic privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'2 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'2 day'::interval, 'YYYY_MM_DD')||' (+2 day)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'3 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'3 day'::interval, 'YYYY_MM_DD') , 'partman-basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman-basic privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'3 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'3 day'::interval, 'YYYY_MM_DD')||' (+3 day)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'4 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'4 day'::interval, 'YYYY_MM_DD') , 'partman-basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman-basic privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'4 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'4 day'::interval, 'YYYY_MM_DD')||' (+4 day)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD') , 'partman-basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman-basic privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||' (-1 day)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'2 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'2 day'::interval, 'YYYY_MM_DD') , 'partman-basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman-basic privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'2 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'2 day'::interval, 'YYYY_MM_DD')||' (-2 day)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'3 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'3 day'::interval, 'YYYY_MM_DD') , 'partman-basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman-basic privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'3 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'3 day'::interval, 'YYYY_MM_DD')||' (-3 day)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'4 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'4 day'::interval, 'YYYY_MM_DD') , 'partman-basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman-basic privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'4 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'4 day'::interval, 'YYYY_MM_DD')||' (-4 day)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD') , 'Partman_Revoke', ARRAY['SELECT'], 'Check Partman_Revoke privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' (now)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD') , 'Partman_Revoke', ARRAY['SELECT'], 'Check Partman_Revoke privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' (+1 day)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'2 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'2 day'::interval, 'YYYY_MM_DD') , 'Partman_Revoke', ARRAY['SELECT'], 'Check Partman_Revoke privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'2 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'2 day'::interval, 'YYYY_MM_DD')||' (+2 day)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'3 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'3 day'::interval, 'YYYY_MM_DD') , 'Partman_Revoke', ARRAY['SELECT'], 'Check Partman_Revoke privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'3 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'3 day'::interval, 'YYYY_MM_DD')||' (+3 day)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'4 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'4 day'::interval, 'YYYY_MM_DD') , 'Partman_Revoke', ARRAY['SELECT'], 'Check Partman_Revoke privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'4 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'4 day'::interval, 'YYYY_MM_DD')||' (+4 day)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD') , 'Partman_Revoke', ARRAY['SELECT'], 'Check Partman_Revoke privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||' (-1 day)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'2 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'2 day'::interval, 'YYYY_MM_DD') , 'Partman_Revoke', ARRAY['SELECT'], 'Check Partman_Revoke privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'2 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'2 day'::interval, 'YYYY_MM_DD')||' (-2 day)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'3 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'3 day'::interval, 'YYYY_MM_DD') , 'Partman_Revoke', ARRAY['SELECT'], 'Check Partman_Revoke privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'3 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'3 day'::interval, 'YYYY_MM_DD')||' (-3 day)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'4 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'4 day'::interval, 'YYYY_MM_DD') , 'Partman_Revoke', ARRAY['SELECT'], 'Check Partman_Revoke privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'4 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'4 day'::interval, 'YYYY_MM_DD')||' (-4 day)'); -- Check for future weekly partitions and their minimal daily. Not testing pk & privileges. If above is fine, these should be fine SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'1 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'1 weeks'::interval), 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'1 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'1 weeks'::interval), 'YYYY_MM_DD')||' exists (+1 weeks)'); -- Cannot reliably test for +1week+1day not existing since near the end of the week, the premake days go into the following week SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'2 weeks'::interval), 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'2 weeks'::interval), 'YYYY_MM_DD')||' exists (+2 weeks)'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'2 weeks'::interval)+'1 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'2 weeks'::interval)+'1 day'::interval, 'YYYY_MM_DD')||' does not exist (+2 weeks +1 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'3 weeks'::interval), 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'3 weeks'::interval), 'YYYY_MM_DD')||' exists (+3 weeks)'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'3 weeks'::interval)+'1 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'3 weeks'::interval)+'1 day'::interval, 'YYYY_MM_DD')||' does not exist (+3 weeks +1 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'4 weeks'::interval), 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'4 weeks'::interval), 'YYYY_MM_DD')||' exists (+4 weeks)'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'4 weeks'::interval)+'1 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'4 weeks'::interval)+'1 day'::interval, 'YYYY_MM_DD')||' does not exist (+8 weeks +1 day)'); SELECT results_eq('SELECT partition_data_time(''Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||''', 10)::int' , ARRAY[10] , 'Check that partitioning function returns correct count of rows moved for Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||' (now)'); SELECT results_eq('SELECT partition_data_time(''Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||''', 10)::int' , ARRAY[20], 'Check that partitioning function returns correct count of rows moved for Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||' (-6 weeks)'); SELECT results_eq('SELECT partition_data_time(''Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||''', 10)::int' , ARRAY[10], 'Check that partitioning function returns correct count of rows moved for Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||' (-5 weeks)'); SELECT results_eq('SELECT partition_data_time(''Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||''', 10)::int' , ARRAY[10], 'Check that partitioning function returns correct count of rows moved for Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' (-4 weeks)'); SELECT results_eq('SELECT partition_data_time(''Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||''', 10)::int' , ARRAY[5], 'Check that partitioning function returns correct count of rows moved for Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' (-3 weeks)'); SELECT results_eq('SELECT partition_data_time(''Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||''', 10)::int' , ARRAY[5], 'Check that partitioning function returns correct count of rows moved for Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' (-2 weeks)'); SELECT results_eq('SELECT partition_data_time(''Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||''', 10)::int' , ARRAY[7], 'Check that partitioning function returns correct count of rows moved for Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' (-1 weeks)'); SELECT results_eq('SELECT partition_data_time(''Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||''', 10)::int' , ARRAY[12], 'Check that partitioning function returns correct count of rows moved for Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' (+1 weeks)'); SELECT results_eq('SELECT partition_data_time(''Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||''', 10)::int' , ARRAY[21], 'Check that partitioning function returns correct count of rows moved for Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||' (+2 weeks)'); SELECT results_eq('SELECT partition_data_time(''Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||''', 10)::int' , ARRAY[15], 'Check that partitioning function returns correct count of rows moved for Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' (+3 weeks)'); SELECT results_eq('SELECT partition_data_time(''Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||''', 10)::int' , ARRAY[15], 'Check that partitioning function returns correct count of rows moved for Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' (+4 weeks)'); -- This should cause all subpartition sets to catch up and premake 4 child tables SELECT run_maintenance(); -- Data at time of run_maintenance() call exists for +4 weeks, premake is 4, so should be partitions for 4 weeks after that (+8 weeks)) SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW'), 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW')||' exists (now)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW'), 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW')||' exists (+6 weeks)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'7 weeks'::interval, 'IYYY"w"IW'), 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'7 weeks'::interval, 'IYYY"w"IW')||' exists (+7 weeks)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'8 weeks'::interval, 'IYYY"w"IW'), 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'8 weeks'::interval, 'IYYY"w"IW')||' exists (+8 weeks)'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'9 weeks'::interval, 'IYYY"w"IW'), 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'9 weeks'::interval, 'IYYY"w"IW')||' does not exist (+9 weeks)'); SELECT col_is_pk('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW'), ARRAY['COL1'], 'Check for primary key in Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW')||' exists (+5 weeks)'); SELECT col_is_pk('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW'), ARRAY['COL1'], 'Check for primary key in Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW')||' exists (+6 weeks)'); SELECT col_is_pk('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'7 weeks'::interval, 'IYYY"w"IW'), ARRAY['COL1'], 'Check for primary key in Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'7 weeks'::interval, 'IYYY"w"IW')||' exists (+7 weeks)'); SELECT col_is_pk('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'8 weeks'::interval, 'IYYY"w"IW'), ARRAY['COL1'], 'Check for primary key in Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'8 weeks'::interval, 'IYYY"w"IW')||' exists (+8 weeks)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW'), 'partman-basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman-basic privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW')||' (+5 weeks)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW'), 'partman-basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman-basic privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW')||' (+6 weeks)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'7 weeks'::interval, 'IYYY"w"IW'), 'partman-basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman-basic privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'7 weeks'::interval, 'IYYY"w"IW')||' (+7 weeks)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'8 weeks'::interval, 'IYYY"w"IW'), 'partman-basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman-basic privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'8 weeks'::interval, 'IYYY"w"IW')||' (+8 weeks)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW'), 'Partman_Revoke', ARRAY['SELECT'], 'Check Partman_Revoke privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW')||' (+5 weeks)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW'), 'Partman_Revoke', ARRAY['SELECT'], 'Check Partman_Revoke privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW')||' (+6 weeks)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'7 weeks'::interval, 'IYYY"w"IW'), 'Partman_Revoke', ARRAY['SELECT'], 'Check Partman_Revoke privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'7 weeks'::interval, 'IYYY"w"IW')||' (+7 weeks)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'8 weeks'::interval, 'IYYY"w"IW'), 'Partman_Revoke', ARRAY['SELECT'], 'Check Partman_Revoke privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'8 weeks'::interval, 'IYYY"w"IW')||' (+8 weeks)'); 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-'4 weeks'::interval, 'IYYY"w"IW')||'"' , 'Check that parent table Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' has had no data inserted to it'); SELECT is_empty('SELECT * FROM ONLY "Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||'"' , 'Check that parent table Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' has had no data inserted to it'); SELECT is_empty('SELECT * FROM ONLY "Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||'"' , 'Check that parent table Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' has had no data inserted to it'); SELECT is_empty('SELECT * FROM ONLY "Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||'"' , 'Check that parent table Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' has had no data inserted to it'); SELECT is_empty('SELECT * FROM ONLY "Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||'"' , 'Check that parent table Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' has had no data inserted to it'); SELECT is_empty('SELECT * FROM ONLY "Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'2 week'::interval, 'IYYY"w"IW')||'"' , 'Check that parent table Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'2 week'::interval, 'IYYY"w"IW')||' has had no data inserted to it'); SELECT is_empty('SELECT * FROM ONLY "Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'3 week'::interval, 'IYYY"w"IW')||'"' , 'Check that parent table Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'3 week'::interval, 'IYYY"w"IW')||' has had no data inserted to it'); SELECT is_empty('SELECT * FROM ONLY "Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'4 week'::interval, 'IYYY"w"IW')||'"' , 'Check that parent table Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'4 week'::interval, 'IYYY"w"IW')||' has had no data inserted to it'); SELECT is_empty('SELECT * FROM ONLY "Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'5 week'::interval, 'IYYY"w"IW')||'"' , 'Check that parent table Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'5 week'::interval, 'IYYY"w"IW')||' has had no data inserted to it'); -- Check that all subpartitions have their child tables +4 days after the minimum (minimal partition was already checked above) -- Since they have data, maintenance should work properly and catch up all the missing child tables SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'6 weeks'::interval)+'1 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'6 weeks'::interval)+'1 day'::interval, 'YYYY_MM_DD')||' exists (-6 weeks +1 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'6 weeks'::interval)+'2 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'6 weeks'::interval)+'2 day'::interval, 'YYYY_MM_DD')||' exists (-6 weeks +2 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'6 weeks'::interval)+'3 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'6 weeks'::interval)+'3 day'::interval, 'YYYY_MM_DD')||' exists (-6 weeks +3 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'6 weeks'::interval)+'4 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'6 weeks'::interval)+'4 day'::interval, 'YYYY_MM_DD')||' exists (-6 weeks +4 day)'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'6 weeks'::interval)+'5 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'6 weeks'::interval)+'5 day'::interval, 'YYYY_MM_DD')||'does not exist (-6 weeks +5 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'5 weeks'::interval)+'1 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'5 weeks'::interval)+'1 day'::interval, 'YYYY_MM_DD')||' exists (-5 weeks +1 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'5 weeks'::interval)+'2 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'5 weeks'::interval)+'2 day'::interval, 'YYYY_MM_DD')||' exists (-5 weeks +2 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'5 weeks'::interval)+'3 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'5 weeks'::interval)+'3 day'::interval, 'YYYY_MM_DD')||' exists (-5 weeks +3 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'5 weeks'::interval)+'4 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'5 weeks'::interval)+'4 day'::interval, 'YYYY_MM_DD')||' exists (-5 weeks +4 day)'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'5 weeks'::interval)+'5 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'5 weeks'::interval)+'5 day'::interval, 'YYYY_MM_DD')||' does not exist (-5 weeks +5 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'4 weeks'::interval)+'1 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'4 weeks'::interval)+'1 day'::interval, 'YYYY_MM_DD')||' exists (-4 weeks +1 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'4 weeks'::interval)+'2 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'4 weeks'::interval)+'2 day'::interval, 'YYYY_MM_DD')||' exists (-4 weeks +2 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'4 weeks'::interval)+'3 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'4 weeks'::interval)+'3 day'::interval, 'YYYY_MM_DD')||' exists (-4 weeks +3 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'4 weeks'::interval)+'4 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'4 weeks'::interval)+'4 day'::interval, 'YYYY_MM_DD')||' exists (-4 weeks +4 day)'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'4 weeks'::interval)+'5 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'4 weeks'::interval)+'5 day'::interval, 'YYYY_MM_DD')||' does not exist (-4 weeks +5 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'3 weeks'::interval)+'1 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'3 weeks'::interval)+'1 day'::interval, 'YYYY_MM_DD')||' exists (-3 weeks +1 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'3 weeks'::interval)+'2 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'3 weeks'::interval)+'2 day'::interval, 'YYYY_MM_DD')||' exists (-3 weeks +2 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'3 weeks'::interval)+'3 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'3 weeks'::interval)+'3 day'::interval, 'YYYY_MM_DD')||' exists (-3 weeks +3 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'3 weeks'::interval)+'4 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'3 weeks'::interval)+'4 day'::interval, 'YYYY_MM_DD')||' exists (-3 weeks +4 day)'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'3 weeks'::interval)+'5 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'3 weeks'::interval)+'5 day'::interval, 'YYYY_MM_DD')||' does not exist (-3 weeks +5 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'2 weeks'::interval)+'1 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'2 weeks'::interval)+'1 day'::interval, 'YYYY_MM_DD')||' exists (-2 weeks +1 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'2 weeks'::interval)+'2 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'2 weeks'::interval)+'2 day'::interval, 'YYYY_MM_DD')||' exists (-2 weeks +2 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'2 weeks'::interval)+'3 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'2 weeks'::interval)+'3 day'::interval, 'YYYY_MM_DD')||' exists (-2 weeks +3 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'2 weeks'::interval)+'4 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'2 weeks'::interval)+'4 day'::interval, 'YYYY_MM_DD')||' exists (-2 weeks +4 day)'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'2 weeks'::interval)+'5 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'2 weeks'::interval)+'5 day'::interval, 'YYYY_MM_DD')||' does not exist (-2 weeks +5 day)'); -- These tests may fail depending on the day of the week due to previous partitions being created my create_parent(). Example: Monday will cause failures because previous Friday table is created. SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'1 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'1 weeks'::interval)+'1 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'1 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'1 weeks'::interval)+'1 day'::interval, 'YYYY_MM_DD')||' exists (-1 weeks +1 day). This test may fail depending on day of the week.'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'1 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'1 weeks'::interval)+'2 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'1 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'1 weeks'::interval)+'2 day'::interval, 'YYYY_MM_DD')||' exists (-1 weeks +2 day) This test may fail depending on day of the week.'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'1 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'1 weeks'::interval)+'3 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'1 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'1 weeks'::interval)+'3 day'::interval, 'YYYY_MM_DD')||' exists (-1 weeks +3 day) This test may fail depending on day of the week.'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'1 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'1 weeks'::interval)+'4 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'1 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'1 weeks'::interval)+'4 day'::interval, 'YYYY_MM_DD')||' exists (-1 weeks +4 day) This test may fail depending on day of the week.'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'1 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'1 weeks'::interval)+'5 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'1 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'1 weeks'::interval)+'5 day'::interval, 'YYYY_MM_DD')||' does not exist (-1 weeks +5 day) This test may fail depending on day of the week.'); -- No need to test for this week's table. Should already have been tested elsewhere SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'1 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'1 weeks'::interval)+'1 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'1 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'1 weeks'::interval)+'1 day'::interval, 'YYYY_MM_DD')||' exists (+1 weeks +1 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'1 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'1 weeks'::interval)+'2 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'1 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'1 weeks'::interval)+'2 day'::interval, 'YYYY_MM_DD')||' exists (+1 weeks +2 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'1 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'1 weeks'::interval)+'3 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'1 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'1 weeks'::interval)+'3 day'::interval, 'YYYY_MM_DD')||' exists (+1 weeks +3 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'1 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'1 weeks'::interval)+'4 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'1 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'1 weeks'::interval)+'4 day'::interval, 'YYYY_MM_DD')||' exists (+1 weeks +4 day)'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'1 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'1 weeks'::interval)+'5 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'1 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'1 weeks'::interval)+'5 day'::interval, 'YYYY_MM_DD')||' does not exist (+1 weeks +5 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'2 weeks'::interval)+'1 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'2 weeks'::interval)+'1 day'::interval, 'YYYY_MM_DD')||' exists (+2 weeks +1 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'2 weeks'::interval)+'2 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'2 weeks'::interval)+'2 day'::interval, 'YYYY_MM_DD')||' exists (+2 weeks +2 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'2 weeks'::interval)+'3 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'2 weeks'::interval)+'3 day'::interval, 'YYYY_MM_DD')||' exists (+2 weeks +3 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'2 weeks'::interval)+'4 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'2 weeks'::interval)+'4 day'::interval, 'YYYY_MM_DD')||' exists (+2 weeks +4 day)'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'2 weeks'::interval)+'5 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'2 weeks'::interval)+'5 day'::interval, 'YYYY_MM_DD')||' does not exist (+2 weeks +5 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'3 weeks'::interval)+'1 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'3 weeks'::interval)+'1 day'::interval, 'YYYY_MM_DD')||' exists (+3 weeks +1 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'3 weeks'::interval)+'2 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'3 weeks'::interval)+'2 day'::interval, 'YYYY_MM_DD')||' exists (+3 weeks +2 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'3 weeks'::interval)+'3 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'3 weeks'::interval)+'3 day'::interval, 'YYYY_MM_DD')||' exists (+3 weeks +3 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'3 weeks'::interval)+'4 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'3 weeks'::interval)+'4 day'::interval, 'YYYY_MM_DD')||' exists (+3 weeks +4 day)'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'3 weeks'::interval)+'5 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'3 weeks'::interval)+'5 day'::interval, 'YYYY_MM_DD')||' does not exist (+3 weeks +5 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'4 weeks'::interval)+'1 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'4 weeks'::interval)+'1 day'::interval, 'YYYY_MM_DD')||' exists (+4 weeks +1 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'4 weeks'::interval)+'2 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'4 weeks'::interval)+'2 day'::interval, 'YYYY_MM_DD')||' exists (+4 weeks +2 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'4 weeks'::interval)+'3 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'4 weeks'::interval)+'3 day'::interval, 'YYYY_MM_DD')||' exists (+4 weeks +3 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'4 weeks'::interval)+'4 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'4 weeks'::interval)+'4 day'::interval, 'YYYY_MM_DD')||' exists (+4 weeks +4 day)'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'4 weeks'::interval)+'5 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'4 weeks'::interval)+'5 day'::interval, 'YYYY_MM_DD')||' does not exist (+4 weeks +5 day)'); -- Only minimum partition should be in the following sub partition sets SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'5 weeks'::interval), 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'5 weeks'::interval), 'YYYY_MM_DD')||' exists (+5 weeks)'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'36 days'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'5 weeks'::interval)+'1 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'36 days'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'5 weeks'::interval)+'1 day'::interval, 'YYYY_MM_DD')||' does not exist (+5 weeks +1 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'6 weeks'::interval), 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'6 weeks'::interval), 'YYYY_MM_DD')||' exists (+6 weeks)'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'43 days'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'6 weeks'::interval)+'1 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'43 days'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'6 weeks'::interval)+'1 day'::interval, 'YYYY_MM_DD')||' does not exist (+6 weeks +1 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'7 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'7 weeks'::interval), 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'7 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'7 weeks'::interval), 'YYYY_MM_DD')||' exists (+7 weeks)'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'50 days'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'7 weeks'::interval)+'1 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'50 days'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'7 weeks'::interval)+'1 day'::interval, 'YYYY_MM_DD')||' does not exist (+7 weeks +1 day)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'8 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'8 weeks'::interval), 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'8 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'8 weeks'::interval), 'YYYY_MM_DD')||' exists (+8 weeks)'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'57 days'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'8 weeks'::interval)+'1 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'57 days'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'8 weeks'::interval)+'1 day'::interval, 'YYYY_MM_DD')||' does not exist (+8 weeks +1 day)'); -- Default optimize_constraint is 30, so set it equal to premake for when this test was originally written -- This should then cause all the subpartition sets to create the full week of child tables and mark the sets as full UPDATE part_config SET premake = 5, optimize_constraint = 5 WHERE parent_table = 'Partman_test.Time-taptest-Table'; UPDATE part_config SET premake = 7, optimize_constraint = 7 WHERE parent_table LIKE 'Partman_test.Time-taptest-Table_p%'; SELECT run_maintenance(); -- Should automatically put additional constraint on 6 weeks prior to the current max value (+4 weeks) in the partition set. So 2 weeks ago. SELECT col_has_check('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), 'COL1' , 'Check for additional constraint on col1 on Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' (-2 weeks)'); -- Data at time of run_maintenance() call exists for +4 weeks, premake is now 5, so should be partitions for 5 weeks after that (+9 weeks)) SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'9 weeks'::interval, 'IYYY"w"IW'), 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'9 weeks'::interval, 'IYYY"w"IW')||' exists (+9 weeks)'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'10 weeks'::interval, 'IYYY"w"IW'), 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'10 weeks'::interval, 'IYYY"w"IW')||' does not exist (+10 weeks)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'9 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'9 weeks'::interval), 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'9 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'9 weeks'::interval), 'YYYY_MM_DD')||' exists (+9 weeks)'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'64 days'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'9 weeks'::interval)+'1 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'64 days'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'9 weeks'::interval)+'1 day'::interval, 'YYYY_MM_DD')||' does not exist (+9 weeks +1 day)'); SELECT col_is_pk('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'9 weeks'::interval, 'IYYY"w"IW'), ARRAY['COL1'], 'Check for primary key in Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'9 weeks'::interval, 'IYYY"w"IW')||' exists (+9 weeks)'); SELECT col_is_pk('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'9 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'9 weeks'::interval), 'YYYY_MM_DD') , ARRAY['COL1'], 'Check for primary key in Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'9 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'9 weeks'::interval), 'YYYY_MM_DD')||' exists (+9 weeks)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'9 weeks'::interval, 'IYYY"w"IW'), 'partman-basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman-basic privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'9 weeks'::interval, 'IYYY"w"IW')||' (+9 weeks)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'9 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'9 weeks'::interval), 'YYYY_MM_DD') , 'partman-basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman-basic privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'9 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'9 weeks'::interval), 'YYYY_MM_DD')||' (+9 weeks)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'9 weeks'::interval, 'IYYY"w"IW'), 'Partman_Revoke', ARRAY['SELECT'], 'Check Partman_Revoke privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'9 weeks'::interval, 'IYYY"w"IW')||' (+9 weeks)'); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'9 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'9 weeks'::interval), 'YYYY_MM_DD') , 'Partman_Revoke', ARRAY['SELECT'], 'Check Partman_Revoke privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'9 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'9 weeks'::interval), 'YYYY_MM_DD')||' (+9 weeks)'); -- This should properly go into the child table of the +5 week set INSERT INTO "Partman_test"."Time-taptest-Table" ("COL1", "Col-3") VALUES (generate_series(131,140), date_trunc('week', CURRENT_TIMESTAMP + '5 weeks'::interval)); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'5 week'::interval, 'IYYY"w"IW')||'"', ARRAY[10], 'Check count from Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'5 week'::interval, 'IYYY"w"IW')||' (+5 weeks)'); -- Run maintenance again and check for constraint at -1 week SELECT run_maintenance(); SELECT col_has_check('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'1 weeks'::interval, 'IYYY"w"IW'), 'COL1' , 'Check for additional constraint on col1 on Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'1 weeks'::interval, 'IYYY"w"IW')||' (-1 weeks)'); -- Also +10 week tables should be made since newest data is +5 weeks and premake is +5 SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'10 weeks'::interval, 'IYYY"w"IW'), 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'10 weeks'::interval, 'IYYY"w"IW')||' exists (+10 weeks)'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'11 weeks'::interval, 'IYYY"w"IW'), 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'11 weeks'::interval, 'IYYY"w"IW')||' does not exist (+11 weeks)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'10 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'10 weeks'::interval), 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'10 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'10 weeks'::interval), 'YYYY_MM_DD')||' exists (+10 weeks)'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'71 days'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'10 weeks'::interval)+'1 day'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'71 days'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'10 days'::interval)+'1 day'::interval, 'YYYY_MM_DD')||' does not exist (+10 weeks +1 day)'); -- Check for next 3 days stuff being created (changed from 4 to 7) SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' exists (+5 days)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' exists (+6 days)'); SELECT has_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD') , 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' exists (+7 days)'); SELECT col_is_pk('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'5 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'5 day'::interval, 'YYYY_MM_DD'), ARRAY['COL1'], 'Check for primary key in Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'5 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'5 day'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'6 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'6 day'::interval, 'YYYY_MM_DD'), ARRAY['COL1'], 'Check for primary key in Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'6 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'6 day'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'7 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'7 day'::interval, 'YYYY_MM_DD'), ARRAY['COL1'], 'Check for primary key in Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'7 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'7 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'5 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'5 day'::interval, 'YYYY_MM_DD') , 'partman-basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman-basic privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'5 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'5 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'6 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'6 day'::interval, 'YYYY_MM_DD') , 'partman-basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman-basic privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'6 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'6 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'7 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'7 day'::interval, 'YYYY_MM_DD') , 'partman-basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman-basic privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'7 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'7 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'5 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'5 day'::interval, 'YYYY_MM_DD') , 'Partman_Revoke', ARRAY['SELECT'], 'Check Partman_Revoke privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'5 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'5 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'6 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'6 day'::interval, 'YYYY_MM_DD') , 'Partman_Revoke', ARRAY['SELECT'], 'Check Partman_Revoke privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'6 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'6 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'7 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'7 day'::interval, 'YYYY_MM_DD') , 'Partman_Revoke', ARRAY['SELECT'], 'Check Partman_Revoke privileges of Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'7 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP+'7 day'::interval, 'YYYY_MM_DD')); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'4 weeks'::interval), 'YYYY_MM_DD')||'"', ARRAY[10], 'Check count from Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'4 weeks'::interval), 'YYYY_MM_DD')); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'3 weeks'::interval), 'YYYY_MM_DD')||'"', ARRAY[5], 'Check count from Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'3 weeks'::interval), 'YYYY_MM_DD')); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'2 weeks'::interval), 'YYYY_MM_DD')||'"', ARRAY[5], 'Check count from Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'2 weeks'::interval), 'YYYY_MM_DD')); -- Part of the 8 day constraint test mentioned above that I can't get working. See previous comment for why this causes count tests to fail on mondays --SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'8 days'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'8 days'::interval, 'YYYY_MM_DD')||'"', -- ARRAY[10], 'Check count from Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'8 days'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'8 days'::interval, 'YYYY_MM_DD')); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'1 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'1 weeks'::interval), 'YYYY_MM_DD')||'"', ARRAY[7], 'Check count from Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'1 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP-'1 weeks'::interval), 'YYYY_MM_DD')); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'1 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'1 weeks'::interval), 'YYYY_MM_DD')||'"', ARRAY[12], 'Check count from Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'1 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'1 weeks'::interval), 'YYYY_MM_DD')); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'2 weeks'::interval), 'YYYY_MM_DD')||'"', ARRAY[21], 'Check count from Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'2 weeks'::interval), 'YYYY_MM_DD')); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'3 weeks'::interval), 'YYYY_MM_DD')||'"', ARRAY[15], 'Check count from Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'3 weeks'::interval), 'YYYY_MM_DD')); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'4 weeks'::interval), 'YYYY_MM_DD')||'"', ARRAY[15], 'Check count from Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'4 weeks'::interval), 'YYYY_MM_DD')); SELECT results_eq('SELECT count(*)::int FROM "Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'5 weeks'::interval), 'YYYY_MM_DD')||'"', ARRAY[10], 'Check count from Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(date_trunc('week', CURRENT_TIMESTAMP+'5 weeks'::interval), 'YYYY_MM_DD')); -- Testing retention for daily -- All daily tables older than 2 weeks. Should only drop the daily subpartitions, not the weekly parents yet UPDATE part_config SET retention = '2 weeks', retention_keep_table = false WHERE parent_table LIKE 'Partman_test.Time-taptest-Table_p%' AND partition_interval = '1 day'; SELECT run_maintenance(); SELECT results_eq('SELECT count(*)::int FROM show_partitions(''Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||''')' , ARRAY[1] , 'Check that 6 week old parent Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||' only has one child remaining. Should also kick out warning about attempted drop of last child table. '); SELECT results_eq('SELECT count(*)::int FROM show_partitions(''Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||''')' , ARRAY[1] , 'Check that 5 week old parent Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||' only has one child remaining. Should also kick out warning about attempted drop of last child table. '); SELECT results_eq('SELECT count(*)::int FROM show_partitions(''Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||''')' , ARRAY[1] , 'Check that 4 week old parent Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' only has one child remaining. Should also kick out warning about attempted drop of last child table. '); SELECT results_eq('SELECT count(*)::int FROM show_partitions(''Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||''')' , ARRAY[1] , 'Check that 3 week old parent Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' only has one child remaining. Should also kick out warning about attempted drop of last child table. '); SELECT results_eq('SELECT CASE WHEN count(*) >= 1 THEN 1 ELSE 0 END FROM show_partitions(''Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||''')' , ARRAY[1] , 'Check that 2 week old parent Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' only has at least one child remaining. May or may not kick out warning about attempted drop of last child table. '); -- Now indexes on table olders than 3 days UPDATE part_config SET retention = '3 days', retention_keep_table = true, retention_keep_index = false WHERE parent_table LIKE 'Partman_test.Time-taptest-Table_p%' AND partition_interval = '1 day'; SELECT run_maintenance(); -- This test is failing because the 3 day retention is causing its parent to drop all child tables but it. This means the index drop never runs -- Commenting out for now --SELECT col_isnt_pk('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'YYYY_MM_DD'), ARRAY['COL1'], -- 'Check that primary key was dropped (2 weeks ago) in Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'YYYY_MM_DD')||'. This test may fail if given table is the final child table in the set.'); SELECT col_isnt_pk('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'1 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'1 weeks'::interval, 'YYYY_MM_DD'), ARRAY['COL1'], 'Check that primary key was dropped (1 week ago) in Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'1 weeks'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'1 weeks'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'4 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'4 day'::interval, 'YYYY_MM_DD'), ARRAY['COL1'], 'Check that primary key was dropped (4 days ago) in Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'4 day'::interval, 'IYYY"w"IW')||'_p'||to_char(CURRENT_TIMESTAMP-'4 day'::interval, 'YYYY_MM_DD')); --NOTE The tables with the above indexes won't get dropped below because they're no longer part of the inheritance tree. No big deal. They'll be gone in the end. -- Now do retention on top parent table to get rid of everything not needed anymore. Don't set shorter than 7 days otherwise "now()-1 week" table may get dropped. UPDATE part_config SET retention = '7 days', retention_keep_table = false WHERE parent_table = 'Partman_test.Time-taptest-Table'; -- Have to do all subpartitions before parent, otherwise it errors out if the top parent gets maintenance run first then non-existent children try to run UPDATE part_config SET retention = NULL, retention_keep_table = false WHERE parent_table LIKE 'Partman_test.Time-taptest-Table_p%'; SELECT run_maintenance(); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), 'After retention Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||' does not exist'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), 'After retention Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||' does not exist'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'After retention Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' does not exist'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'After retention Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' does not exist'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), 'After retention Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' does not exist'); -- Undo daily for each week SELECT undo_partition('Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'1 weeks'::interval, 'IYYY"w"IW'), 20, p_keep_table := false, p_drop_cascade := true); SELECT undo_partition('Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), 20, p_keep_table := false, p_drop_cascade := true); SELECT undo_partition('Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'1 weeks'::interval, 'IYYY"w"IW'), 20, p_keep_table := false, p_drop_cascade := true); SELECT undo_partition('Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), 20, p_keep_table := false, p_drop_cascade := true); SELECT undo_partition('Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 20, p_keep_table := false, p_drop_cascade := true); SELECT undo_partition('Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), 20, p_keep_table := false, p_drop_cascade := true); SELECT undo_partition('Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW'), 20, p_keep_table := false, p_drop_cascade := true); SELECT undo_partition('Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW'), 20, p_keep_table := false, p_drop_cascade := true); SELECT undo_partition('Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'7 weeks'::interval, 'IYYY"w"IW'), 20, p_keep_table := false, p_drop_cascade := true); SELECT undo_partition('Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'8 weeks'::interval, 'IYYY"w"IW'), 20, p_keep_table := false, p_drop_cascade := true); SELECT undo_partition('Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'9 weeks'::interval, 'IYYY"w"IW'), 20, p_keep_table := false, p_drop_cascade := true); SELECT undo_partition('Partman_test.Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'10 weeks'::interval, 'IYYY"w"IW'), 20, p_keep_table := false, p_drop_cascade := true); SELECT is_empty('SELECT inhrelid FROM pg_catalog.pg_inherits WHERE inhparent::regclass = ''"Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'1 weeks'::interval, 'IYYY"w"IW')||'"''::regclass', 'Check that 1 week old parent Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'1 weeks'::interval, 'IYYY"w"IW')||' table has no children'); SELECT is_empty('SELECT inhrelid FROM pg_catalog.pg_inherits WHERE inhparent::regclass = ''"Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||'"''::regclass', 'Check that current parent Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||' table has no children'); SELECT is_empty('SELECT inhrelid FROM pg_catalog.pg_inherits WHERE inhparent::regclass = ''"Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'1 weeks'::interval, 'IYYY"w"IW')||'"''::regclass', 'Check that 1 week future parent Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'1 weeks'::interval, 'IYYY"w"IW')||' table has no children'); SELECT is_empty('SELECT inhrelid FROM pg_catalog.pg_inherits WHERE inhparent::regclass = ''"Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||'"''::regclass', 'Check that 2 week future parent Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||' table has no children'); SELECT is_empty('SELECT inhrelid FROM pg_catalog.pg_inherits WHERE inhparent::regclass = ''"Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||'"''::regclass', 'Check that 3 week future parent Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' table has no children'); SELECT is_empty('SELECT inhrelid FROM pg_catalog.pg_inherits WHERE inhparent::regclass = ''"Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||'"''::regclass', 'Check that 4 week future parent Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' table has no children'); SELECT is_empty('SELECT inhrelid FROM pg_catalog.pg_inherits WHERE inhparent::regclass = ''"Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW')||'"''::regclass', 'Check that 5 week future parent Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW')||' table has no children'); SELECT is_empty('SELECT inhrelid FROM pg_catalog.pg_inherits WHERE inhparent::regclass = ''"Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW')||'"''::regclass', 'Check that 6 week future parent Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW')||' table has no children'); SELECT is_empty('SELECT inhrelid FROM pg_catalog.pg_inherits WHERE inhparent::regclass = ''"Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'7 weeks'::interval, 'IYYY"w"IW')||'"''::regclass', 'Check that 7 week future parent Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'7 weeks'::interval, 'IYYY"w"IW')||' table has no children'); SELECT is_empty('SELECT inhrelid FROM pg_catalog.pg_inherits WHERE inhparent::regclass = ''"Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'8 weeks'::interval, 'IYYY"w"IW')||'"''::regclass', 'Check that 8 week future parent Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'8 weeks'::interval, 'IYYY"w"IW')||' table has no children'); SELECT is_empty('SELECT inhrelid FROM pg_catalog.pg_inherits WHERE inhparent::regclass = ''"Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'9 weeks'::interval, 'IYYY"w"IW')||'"''::regclass', 'Check that 9 week future parent Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'9 weeks'::interval, 'IYYY"w"IW')||' table has no children'); SELECT is_empty('SELECT inhrelid FROM pg_catalog.pg_inherits WHERE inhparent::regclass = ''"Partman_test"."Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'10 weeks'::interval, 'IYYY"w"IW')||'"''::regclass', 'Check that 10 week future parent Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'10 weeks'::interval, 'IYYY"w"IW')||' table has no children'); -- Undo weekly SELECT undo_partition('Partman_test.Time-taptest-Table', 20, p_keep_table := false, p_drop_cascade := true); SELECT is_empty('SELECT inhrelid FROM pg_catalog.pg_inherits WHERE inhparent::regclass = ''"Partman_test"."Time-taptest-Table"''::regclass', 'Check that parent Time-taptest-Table table has no children'); SELECT is_empty('SELECT * FROM part_config WHERE parent_table LIKE ''Partman_test.Time-taptest-Table''', 'Check that part_config table is empty'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP-'1 weeks'::interval, 'IYYY"w"IW'), 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP-'1 weeks'::interval, 'IYYY"w"IW')||' does not exist'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||' does not exist'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'1 weeks'::interval, 'IYYY"w"IW'), 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'1 weeks'::interval, 'IYYY"w"IW')||' does not exist'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||' does not exist'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' does not exist'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' does not exist'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW'), 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW')||' does not exist'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW'), 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW')||' does not exist'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'7 weeks'::interval, 'IYYY"w"IW'), 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'7 weeks'::interval, 'IYYY"w"IW')||' does not exist'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'8 weeks'::interval, 'IYYY"w"IW'), 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'8 weeks'::interval, 'IYYY"w"IW')||' does not exist'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'9 weeks'::interval, 'IYYY"w"IW'), 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'9 weeks'::interval, 'IYYY"w"IW')||' does not exist'); SELECT hasnt_table('Partman_test', 'Time-taptest-Table_p'||to_char(CURRENT_TIMESTAMP+'10 weeks'::interval, 'IYYY"w"IW'), 'Check Time-taptest-Table_'||to_char(CURRENT_TIMESTAMP+'10 weeks'::interval, 'IYYY"w"IW')||' does not exist'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test-time-weekly-trunc.sql000066400000000000000000001300171412761706100217220ustar00rootroot00000000000000-- ########## TIME WEEKLY TESTS ########## -- Other tests: Long name truncation \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(122); CREATE SCHEMA partman_test; CREATE SCHEMA partman_retention_test; CREATE ROLE partman_basic; CREATE ROLE partman_revoke; CREATE ROLE partman_owner; CREATE TABLE partman_test.time_taptest_table_123456789012345678901234567890123457890 (col1 int primary key, col2 text, col3 timestamptz NOT NULL DEFAULT now()); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567890123457890 (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); GRANT SELECT,INSERT,UPDATE ON partman_test.time_taptest_table_123456789012345678901234567890123457890 TO partman_basic; GRANT ALL ON partman_test.time_taptest_table_123456789012345678901234567890123457890 TO partman_revoke; SELECT create_parent('partman_test.time_taptest_table_123456789012345678901234567890123457890', 'col3', 'partman', 'weekly'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), 'Check time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW')||' does not exist'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||' does not exist'); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')); SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table_123456789012345678901234567890123457890'')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved'); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_123456789012345678901234567890123457890', 'Check that parent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_123456789012345678901234567890123457890', ARRAY[10], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), ARRAY[10], 'Check count from time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')); REVOKE INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON partman_test.time_taptest_table_123456789012345678901234567890123457890 FROM partman_revoke; INSERT INTO partman_test.time_taptest_table_123456789012345678901234567890123457890 (col1, col3) VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '1 week'::interval); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567890123457890 (col1, col3) VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '2 weeks'::interval); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567890123457890 (col1, col3) VALUES (generate_series(26,30), CURRENT_TIMESTAMP + '3 weeks'::interval); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567890123457890 (col1, col3) VALUES (generate_series(31,37), CURRENT_TIMESTAMP + '4 weeks'::interval); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567890123457890 (col1, col3) VALUES (generate_series(40,49), CURRENT_TIMESTAMP - '1 week'::interval); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567890123457890 (col1, col3) VALUES (generate_series(50,70), CURRENT_TIMESTAMP - '2 weeks'::interval); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567890123457890 (col1, col3) VALUES (generate_series(71,85), CURRENT_TIMESTAMP - '3 weeks'::interval); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567890123457890 (col1, col3) VALUES (generate_series(86,100), CURRENT_TIMESTAMP - '4 weeks'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_123456789012345678901234567890123457890', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), ARRAY[10], 'Check count from time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), ARRAY[5], 'Check count from time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), ARRAY[5], 'Check count from time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), ARRAY[7], 'Check count from time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), ARRAY[10], 'Check count from time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), ARRAY[21], 'Check count from time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), ARRAY[15], 'Check count from time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), ARRAY[15], 'Check count from time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')); UPDATE part_config SET premake = 5 WHERE parent_table = 'partman_test.time_taptest_table_123456789012345678901234567890123457890'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567890123457890 (col1, col3) VALUES (generate_series(101,122), CURRENT_TIMESTAMP + '5 weeks'::interval); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW')||' exists'); -- Cannot test for next week not existing. Different lengths of months will sometimes cause an extra partition. SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW')); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_123456789012345678901234567890123457890', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW'), ARRAY[22], 'Check count from time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')); -- Cannot test next partitions privileges. Different lengths of months will sometimes cause an extra partition. GRANT DELETE ON partman_test.time_taptest_table_123456789012345678901234567890123457890 TO partman_basic; REVOKE ALL ON partman_test.time_taptest_table_123456789012345678901234567890123457890 FROM partman_revoke; ALTER TABLE partman_test.time_taptest_table_123456789012345678901234567890123457890 OWNER TO partman_owner; UPDATE part_config SET premake = 6 WHERE parent_table = 'partman_test.time_taptest_table_123456789012345678901234567890123457890'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567890123457890 (col1, col3) VALUES (generate_series(123,150), CURRENT_TIMESTAMP + '6 weeks'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_123456789012345678901234567890123457890', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_123456789012345678901234567890123457890', ARRAY[148], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW'), ARRAY[28], 'Check count from time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW')); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW')||' exists'); -- Cannot test for next week not existing. Different lengths of months will sometimes cause an extra partition. SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')); -- Cannot test next partitions privileges. Different lengths of months will sometimes cause an extra partition. INSERT INTO partman_test.time_taptest_table_123456789012345678901234567890123457890 (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '20 weeks'::interval); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table_123456789012345678901234567890123457890', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT reapply_privileges('partman_test.time_taptest_table_123456789012345678901234567890123457890'); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')); SELECT drop_partition_time('partman_test.time_taptest_table_123456789012345678901234567890123457890', '3 weeks', p_keep_table := false); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' does not exist'); UPDATE part_config SET retention = '2 weeks'::interval WHERE parent_table = 'partman_test.time_taptest_table_123456789012345678901234567890123457890'; SELECT drop_partition_time('partman_test.time_taptest_table_123456789012345678901234567890123457890', p_retention_schema := 'partman_retention_test'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' does not exist'); SELECT has_table('partman_retention_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' got moved to new schema'); SELECT undo_partition('partman_test.time_taptest_table_123456789012345678901234567890123457890', 20, p_keep_table := false); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table_123456789012345678901234567890123457890', ARRAY[129], 'Check count from parent table after undo'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' does not exist'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test-time-weekly.sql000066400000000000000000001317351412761706100206010ustar00rootroot00000000000000-- ########## TIME WEEKLY TESTS ########## -- Other tests: combination of start_partition & constraint_cols/optimize_constraint. Requires manually running apply_constraint to set other old partitions \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(146); CREATE SCHEMA partman_test; CREATE SCHEMA partman_retention_test; CREATE ROLE partman_basic; CREATE ROLE partman_revoke; CREATE ROLE partman_owner; CREATE TABLE partman_test.time_taptest_table (col1 int primary key, col2 text, col3 timestamptz NOT NULL DEFAULT now()); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP - '8 weeks'::interval); GRANT SELECT,INSERT,UPDATE ON partman_test.time_taptest_table TO partman_basic; GRANT ALL ON partman_test.time_taptest_table TO partman_revoke; -- Test that premake can be less than optimize_constraint without issues SELECT create_parent('partman_test.time_taptest_table', 'col3', 'partman', 'weekly', '{"col1"}', p_premake := 2, p_start_partition := to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'YYYY-MM-DD HH24:MI:SS')); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' exists (+1 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||' exists (+2 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW')||' does not exist (+3 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' exists (-1 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' exists (-2 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' exists (-3 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' exists (-4 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||' exists (-5 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||' exists (-6 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')||' exists (-7 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')||' exists (-8 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'9 weeks'::interval, 'IYYY"w"IW')||' does not exist (-9 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' (+1 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||' (+2 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' (-1 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' (-2 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' (-3 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' (-4 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||' (-5 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||' (-6 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')||' (-7 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')||' (-8 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' (+1 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||' (+2 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' (-1 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' (-2 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' (-3 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' (-4 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||' (-5 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||' (-6 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')||' (-7 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')||' (-8 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' (+1 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||' (+2 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' (-1 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' (-2 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' (-3 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' (-4 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||' (-5 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||' (-6 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')||' (-7 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')||' (-8 weeks)'); SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table'')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved'); 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 - '8 weeks'::interval, 'IYYY"w"IW'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')||' (-8 weeks)'); REVOKE INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON partman_test.time_taptest_table FROM partman_revoke; INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), CURRENT_TIMESTAMP - '7 weeks'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), CURRENT_TIMESTAMP - '6 weeks'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(26,30), CURRENT_TIMESTAMP - '5 weeks'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(31,37), CURRENT_TIMESTAMP - '4 week'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(38,49), CURRENT_TIMESTAMP - '3 week'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(50,70), CURRENT_TIMESTAMP - '2 weeks'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(71,85), CURRENT_TIMESTAMP - '1 week'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(86,100), CURRENT_TIMESTAMP + '1 week'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(101,110), CURRENT_TIMESTAMP + '2 weeks'::interval); SELECT partition_data_time('partman_test.time_taptest_table', 20); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')||' (-7 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||' (-6 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||' (-5 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), ARRAY[7], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' (-4 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), ARRAY[12], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' (-3 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), ARRAY[21], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' (-2 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' (-1 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' (+1 weeks)'); -- Default optimize_constraint is 30, so set it to a value that will trigger it to work for given conditions of this partition set UPDATE part_config SET premake = 3, optimize_constraint = 5 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); -- Automatic run of apply_constraints due to run_maintenance will put constraint on "now() - 4 weeks" partition with an optimize_constraint value of 5 -- This is due to values "now() + 2 weeks" being inserted above. -- Ex: Current week is 39. +2 weeks is 41. Going back 5 weeks is 36, but constraint is applied to the table OLDER than that value. Hence constraint will be on week 35 SELECT col_has_check('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'col1' , 'Check for additional constraint on col1 on time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' (-4 weeks)'); -- Must run apply_constraints() to manually set the other older constraints SELECT apply_constraints('partman_test.time_taptest_table', 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')); SELECT apply_constraints('partman_test.time_taptest_table', 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')); SELECT apply_constraints('partman_test.time_taptest_table', 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')); SELECT apply_constraints('partman_test.time_taptest_table', 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')); SELECT col_has_check('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW'), 'col1' , 'Check for additional constraint on col1 on time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')); SELECT col_has_check('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW'), 'col1' , 'Check for additional constraint on col1 on time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')); SELECT col_has_check('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), 'col1' , 'Check for additional constraint on col1 on time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')); SELECT col_has_check('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), 'col1' , 'Check for additional constraint on col1 on time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(111,120), CURRENT_TIMESTAMP + '3 weeks'::interval); -- +2 weeks data was inserted above, so it premakes +3 weeks after that SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' exists (+3 weeks'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' exists (+4 weeks'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW')||' exists (+5 weeks'); -- Cannot test for next week not existing. Different lengths of months will sometimes cause an extra partition. SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' (+3 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' (+4 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW')||' (+5 weeks)'); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' (+3 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||' (now)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' (+1 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||' (+2 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' (+3 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' (+4 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW')||' (+5 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' (-1 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' (-2 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' (-3 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' (-4 weeks)'); GRANT DELETE ON partman_test.time_taptest_table TO partman_basic; REVOKE ALL ON partman_test.time_taptest_table FROM partman_revoke; ALTER TABLE partman_test.time_taptest_table OWNER TO partman_owner; UPDATE part_config SET premake = 4 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(121,130), CURRENT_TIMESTAMP + '4 weeks'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[130], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' (+4 weeks)'); SELECT col_has_check('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'col1' , 'Check for additional constraint on col1 on time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW')||' exists (+6 weeks)'); -- Cannot test for next week not existing. Different lengths of months will sometimes cause an extra partition. SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW')||' (+6 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||' (now)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' (+1 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||' (+2 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' (+3 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' (+4 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW')||' (+5 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW')||' (+6 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' (-1 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' (-2 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' (-3 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' (-4 weeks)'); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '20 weeks'::interval); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT reapply_privileges('partman_test.time_taptest_table'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||' (now)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' (+1 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||' (+2 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' (+3 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' (+4 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW')||' (+5 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW')||' (+6 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' (-1 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' (-2 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' (-3 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' (-4 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||' (now)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' (+1 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||' (+2 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' (+3 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' (+4 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW')||' (+5 weeks)'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW')||' (+6 weeks)'); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||' (now)'); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' (+1 weeks)'); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||' (+2 weeks)'); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' (+3 weeks)'); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' (+4 weeks)'); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW')||' (+5 weeks)'); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW')||' (+6 weeks)'); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' (-1 weeks)'); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' (-2 weeks)'); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' (-3 weeks)'); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' (-4 weeks)'); SELECT drop_partition_time('partman_test.time_taptest_table', '3 weeks', p_keep_table := false); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' does not exist (-4 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||' does not exist (-5 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||' does not exist (-6 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')||' does not exist (-7 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')||' does not exist (-8 weeks)'); UPDATE part_config SET retention = '2 weeks'::interval, retention_schema = 'partman_retention_test' WHERE parent_table = 'partman_test.time_taptest_table'; --SELECT drop_partition_time('partman_test.time_taptest_table', p_retention_schema := 'partman_retention_test'); -- Use maintenance instead of direct call SELECT run_maintenance(); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' does not exist (-3 weeks)'); SELECT has_table('partman_retention_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' got moved to new schema (-3 weeks)'); SELECT undo_partition('partman_test.time_taptest_table', 20, p_keep_table := false); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[92], 'Check count from parent table after undo'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||' does not exist (now)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' does not exist (+1 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||' does not exist (+2 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' does not exist (+3 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' does not exist (+4 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW')||' does not exist (+5 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW')||' does not exist (+6 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' does not exist (-1 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' does not exist (-2 weeks)'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test-time-yearly-trunc.sql000066400000000000000000001557141412761706100217420ustar00rootroot00000000000000-- ########## TIME YEARLY TESTS ########## -- Other tests: Ensure name truncation works, make sure dynamic trigger is working \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(156); CREATE SCHEMA partman_test; CREATE SCHEMA partman_retention_test; CREATE ROLE partman_basic; CREATE ROLE partman_revoke; CREATE ROLE partman_owner; CREATE TABLE partman_test.time_taptest_table_123456789012345678901234567890123456780 (col1 int primary key, col2 text, col3 timestamptz NOT NULL DEFAULT now()); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567890123456780 (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); GRANT SELECT,INSERT,UPDATE ON partman_test.time_taptest_table_123456789012345678901234567890123456780 TO partman_basic; GRANT ALL ON partman_test.time_taptest_table_123456789012345678901234567890123456780 TO partman_revoke; SELECT create_parent('partman_test.time_taptest_table_123456789012345678901234567890123456780', 'col3', 'partman', 'yearly'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP, 'YYYY'), 'Check time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY'), 'Check time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY'), 'Check time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY'), 'Check time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY'), 'Check time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY')||' does not exist'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY'), 'Check time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY'), 'Check time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY'), 'Check time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'4 years'::interval, 'YYYY'), 'Check time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'4 years'::interval, 'YYYY')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'5 years'::interval, 'YYYY'), 'Check time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'5 years'::interval, 'YYYY')||' does not exist'); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'4 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'4 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'4 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'4 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_'||to_char(CURRENT_TIMESTAMP, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'4 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'4 years'::interval, 'YYYY')); SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table_123456789012345678901234567890123456780'')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved'); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_123456789012345678901234567890123456780', 'Check that parent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_123456789012345678901234567890123456780', ARRAY[10], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP, 'YYYY'), ARRAY[10], 'Check count from time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')); REVOKE INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON partman_test.time_taptest_table_123456789012345678901234567890123456780 FROM partman_revoke; INSERT INTO partman_test.time_taptest_table_123456789012345678901234567890123456780 (col1, col3) VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '1 year'::interval); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567890123456780 (col1, col3) VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '2 years'::interval); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567890123456780 (col1, col3) VALUES (generate_series(26,30), CURRENT_TIMESTAMP + '3 years'::interval); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567890123456780 (col1, col3) VALUES (generate_series(31,37), CURRENT_TIMESTAMP + '4 years'::interval); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567890123456780 (col1, col3) VALUES (generate_series(40,49), CURRENT_TIMESTAMP - '1 years'::interval); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567890123456780 (col1, col3) VALUES (generate_series(50,70), CURRENT_TIMESTAMP - '2 years'::interval); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567890123456780 (col1, col3) VALUES (generate_series(71,85), CURRENT_TIMESTAMP - '3 years'::interval); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567890123456780 (col1, col3) VALUES (generate_series(86,100), CURRENT_TIMESTAMP - '4 years'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_123456789012345678901234567890123456780', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY'), ARRAY[10], 'Check count from time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY'), ARRAY[5], 'Check count from time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY'), ARRAY[5], 'Check count from time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY'), ARRAY[7], 'Check count from time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY'), ARRAY[10], 'Check count from time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY'), ARRAY[21], 'Check count from time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY'), ARRAY[15], 'Check count from time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'4 years'::interval, 'YYYY'), ARRAY[15], 'Check count from time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'4 years'::interval, 'YYYY')); UPDATE part_config SET premake = 5, optimize_trigger = 5 WHERE parent_table = 'partman_test.time_taptest_table_123456789012345678901234567890123456780'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567890123456780 (col1, col3) VALUES (generate_series(101,122), CURRENT_TIMESTAMP + '5 years'::interval); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'8 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'8 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'9 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'9 years'::interval, 'YYYY')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY')||' does not exist'); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'8 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'8 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'9 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'9 years'::interval, 'YYYY')); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_123456789012345678901234567890123456780', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY'), ARRAY[22], 'Check count from time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'8 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'8 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'9 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'9 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'8 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'8 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'9 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'9 years'::interval, 'YYYY')); GRANT DELETE ON partman_test.time_taptest_table_123456789012345678901234567890123456780 TO partman_basic; REVOKE ALL ON partman_test.time_taptest_table_123456789012345678901234567890123456780 FROM partman_revoke; ALTER TABLE partman_test.time_taptest_table_123456789012345678901234567890123456780 OWNER TO partman_owner; UPDATE part_config SET premake = 6, optimize_trigger = 6 WHERE parent_table = 'partman_test.time_taptest_table_123456789012345678901234567890123456780'; SELECT run_maintenance(); -- Data for dynamic trigger INSERT INTO partman_test.time_taptest_table_123456789012345678901234567890123456780 (col1, col3) VALUES (generate_series(123,150), CURRENT_TIMESTAMP + '11 years'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_123456789012345678901234567890123456780', 'Check that parent table has had no data inserted to it'); -- Check dynamic trigger worked SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_123456789012345678901234567890123456780', ARRAY[148], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY'), ARRAY[28], 'Check count from time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY')); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY'), 'Check time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY'), 'Check time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'12 years'::interval, 'YYYY'), 'Check time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'12 years'::interval, 'YYYY')||' does not exist'); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY')); INSERT INTO partman_test.time_taptest_table_123456789012345678901234567890123456780 (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '20 years'::interval); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table_123456789012345678901234567890123456780', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT reapply_privileges('partman_test.time_taptest_table_123456789012345678901234567890123456780'); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'4 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'4 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'8 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'8 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'9 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'9 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'4 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'4 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'8 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'8 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'9 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'9 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'4 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'4 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'8 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'8 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'9 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'9 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY')); SELECT drop_partition_time('partman_test.time_taptest_table_123456789012345678901234567890123456780', '3 years', p_keep_table := false); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'4 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'4 years'::interval, 'YYYY')||' does not exist'); UPDATE part_config SET retention = '2 years'::interval WHERE parent_table = 'partman_test.time_taptest_table_123456789012345678901234567890123456780'; SELECT drop_partition_time('partman_test.time_taptest_table_123456789012345678901234567890123456780', p_retention_schema := 'partman_retention_test'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY')||' does not exist'); /* This test may fail around the end of the year. If so, swap to the commented out one below */ SELECT has_table('partman_retention_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY')||' got moved to new schema. This test may fail around the year boundary. See comment in tests for different test to try.'); /* This test may fail around the beginning of the year. If so, swap to the commented out one above */ --SELECT has_table('partman_retention_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY'), -- 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||' got moved to new schema. This test may fail around the year boundary. See comment in tests for different test to try.'); SELECT undo_partition('partman_test.time_taptest_table_123456789012345678901234567890123456780', 20, p_keep_table := false); /* This test may fail around the end of the year. If so, swap to the commented out one below */ SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table_123456789012345678901234567890123456780', ARRAY[129], 'Check count from parent table after undo. This test may fail around the year boundary. See comment in tests for different test to try.'); /* This test may fail around the beginning of the year. If so, swap to the commented out one above */ --SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table_123456789012345678901234567890123456780', ARRAY[108], 'Check count from parent table after undo. This test may fail around the year boundary. See comment in tests for different test to try.'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'8 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'8 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'9 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'9 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_12345678901234567890123456789012345678_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY')||' does not exist'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test-time-yearly.sql000066400000000000000000001441671412761706100206110ustar00rootroot00000000000000-- ########## TIME YEARLY TESTS ########## -- Other tests: UNLOGGED, Multi-column foreign key \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(179); CREATE SCHEMA partman_test; CREATE SCHEMA partman_retention_test; CREATE ROLE partman_basic; CREATE ROLE partman_revoke; CREATE ROLE partman_owner; CREATE TABLE partman_test.fk_test_reference (col2 text not null, col4 text not null); CREATE UNIQUE INDEX ON partman_test.fk_test_reference(col2, col4); INSERT INTO partman_test.fk_test_reference VALUES ('stuff', 'stuff'); CREATE UNLOGGED TABLE partman_test.time_taptest_table (col1 int primary key , col2 text not null default 'stuff' , col3 timestamptz NOT NULL DEFAULT now() , col4 text not null default 'stuff' , FOREIGN KEY (col2, col4) REFERENCES partman_test.fk_test_reference(col2, col4) MATCH FULL ON DELETE RESTRICT DEFERRABLE); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); GRANT SELECT,INSERT,UPDATE ON partman_test.time_taptest_table TO partman_basic; GRANT ALL ON partman_test.time_taptest_table TO partman_revoke; SELECT create_parent('partman_test.time_taptest_table', 'col3', 'partman', 'yearly'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY')||' does not exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'4 years'::interval, 'YYYY')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'5 years'::interval, 'YYYY')||' does not exist'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.time_taptest_table''::regclass', ARRAY['u'], 'Check that parent table is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||'''::regclass', ARRAY['u'], 'Check that partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||' is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')||'''::regclass', ARRAY['u'], 'Check that partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')||' is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'''::regclass', ARRAY['u'], 'Check that partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||' is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'''::regclass', ARRAY['u'], 'Check that partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||' is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY')||'''::regclass', ARRAY['u'], 'Check that partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY')||' is unlogged'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 years'::interval, 'YYYY')); SELECT col_is_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY'), ARRAY['col2', 'col4'], 'Check for inherited foreign key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')); SELECT col_is_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY'), ARRAY['col2', 'col4'], 'Check for inherited foreign key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')); SELECT col_is_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY'), ARRAY['col2', 'col4'], 'Check for inherited foreign key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')); SELECT col_is_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY'), ARRAY['col2', 'col4'], 'Check for inherited foreign key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY')); SELECT col_is_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY'), ARRAY['col2', 'col4'], 'Check for inherited foreign key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')); SELECT col_is_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY'), ARRAY['col2', 'col4'], 'Check for inherited foreign key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')); SELECT col_is_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY'), ARRAY['col2', 'col4'], 'Check for inherited foreign key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY')); SELECT col_is_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 years'::interval, 'YYYY'), ARRAY['col2', 'col4'], 'Check for inherited foreign key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_'||to_char(CURRENT_TIMESTAMP, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 years'::interval, 'YYYY')); SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table'')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved'); 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'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')); REVOKE INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON partman_test.time_taptest_table FROM partman_revoke; INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '1 year'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '2 years'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(26,30), CURRENT_TIMESTAMP + '3 years'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(31,37), CURRENT_TIMESTAMP + '4 years'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(40,49), CURRENT_TIMESTAMP - '1 years'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(50,70), CURRENT_TIMESTAMP - '2 years'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(71,85), CURRENT_TIMESTAMP - '3 years'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(86,100), CURRENT_TIMESTAMP - '4 years'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY'), ARRAY[7], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY'), ARRAY[21], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 years'::interval, 'YYYY'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 years'::interval, 'YYYY')); UPDATE part_config SET premake = 5, optimize_trigger = 5 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(101,122), CURRENT_TIMESTAMP + '5 years'::interval); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'8 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'9 years'::interval, 'YYYY')||' exists'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY')||'''::regclass', ARRAY['u'], 'Check that partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY')||' is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY')||'''::regclass', ARRAY['u'], 'Check that partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY')||' is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY')||'''::regclass', ARRAY['u'], 'Check that partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY')||' is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 years'::interval, 'YYYY')||'''::regclass', ARRAY['u'], 'Check that partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 years'::interval, 'YYYY')||' is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 years'::interval, 'YYYY')||'''::regclass', ARRAY['u'], 'Check that partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 years'::interval, 'YYYY')||' is unlogged'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY')||' does not exist'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 years'::interval, 'YYYY')); SELECT col_is_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY'), ARRAY['col2', 'col4'], 'Check for inherited foreign key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY')); SELECT col_is_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY'), ARRAY['col2', 'col4'], 'Check for inherited foreign key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY')); SELECT col_is_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY'), ARRAY['col2', 'col4'], 'Check for inherited foreign key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY')); SELECT col_is_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 years'::interval, 'YYYY'), ARRAY['col2', 'col4'], 'Check for inherited foreign key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 years'::interval, 'YYYY')); SELECT col_is_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 years'::interval, 'YYYY'), ARRAY['col2', 'col4'], 'Check for inherited foreign key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 years'::interval, 'YYYY')); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY'), ARRAY[22], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 years'::interval, 'YYYY')); GRANT DELETE ON partman_test.time_taptest_table TO partman_basic; REVOKE ALL ON partman_test.time_taptest_table FROM partman_revoke; ALTER TABLE partman_test.time_taptest_table OWNER TO partman_owner; UPDATE part_config SET premake = 6, optimize_trigger = 6 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(123,150), CURRENT_TIMESTAMP + '6 years'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[148], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY'), ARRAY[28], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY')); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 years'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 years'::interval, 'YYYY')||' does not exist'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY')||'''::regclass', ARRAY['u'], 'Check that partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY')||' is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY')||'''::regclass', ARRAY['u'], 'Check that partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY')||' is unlogged'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY')); SELECT col_is_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY'), ARRAY['col2', 'col4'], 'Check for inherited foreign key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY')); SELECT col_is_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY'), ARRAY['col2', 'col4'], 'Check for inherited foreign key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY')); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '20 years'::interval); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT reapply_privileges('partman_test.time_taptest_table'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY')); SELECT drop_partition_time('partman_test.time_taptest_table', '3 years', p_keep_table := false); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'4 years'::interval, 'YYYY')||' does not exist'); UPDATE part_config SET retention = '2 years'::interval WHERE parent_table = 'partman_test.time_taptest_table'; SELECT drop_partition_time('partman_test.time_taptest_table', p_retention_schema := 'partman_retention_test'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY')||' does not exist'); -- **** This test may fail around the end of the year. If so, swap to the commented out one below SELECT has_table('partman_retention_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY')||' got moved to new schema. This test may fail around the year boundary. See comment in tests for different test to try.'); -- **** This test may fail around the beginning of the year. If so, swap to the commented out one above --SELECT has_table('partman_retention_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY'), -- 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||' got moved to new schema. This test may fail around the year boundary. See comment in tests for different test to try.'); SELECT undo_partition('partman_test.time_taptest_table', 20, p_keep_table := false); -- **** This test may fail around the end of the year. If so, swap to the commented out one below SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[129], 'Check count from parent table after undo. This test may fail around the year boundary. See comment in tests for different test to try.'); -- **** This test may fail around the beginning of the year. If so, swap to the commented out one above --SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[108], 'Check count from parent table after undo. This test may fail around the year boundary. See comment in tests for different test to try.'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'8 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'9 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'10 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'11 years'::interval, 'YYYY')||' does not exist'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test_bgw/000077500000000000000000000000001412761706100164535ustar00rootroot00000000000000pg_partman-4.6.0/test/test_bgw/test-id-bgw.sql000066400000000000000000000510711412761706100213260ustar00rootroot00000000000000-- ########## ID TESTS WITH BACKGROUND WORKER RUNNING ########## -- Additional tests: turn off pg_jobmon logging, UNLOGGED, Make sure option to not inherit foreign keys works, larger than necessary p_batch_count to partition_data_id(), retention -- Set the pg_partman_bgw.interval setting in postgresql.conf to 10 seconds (or less) in order for this test suite to pass successfully. -- Cannot run this test inside a transaction since then the BGW would not see this partition set exists \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true --BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(124); CREATE SCHEMA partman_test; CREATE SCHEMA partman_retention_test; CREATE ROLE partman_basic; CREATE ROLE partman_revoke; CREATE ROLE partman_owner; CREATE TABLE partman_test.fk_test_reference (col2 text unique not null); INSERT INTO partman_test.fk_test_reference VALUES ('stuff'); CREATE UNLOGGED TABLE partman_test.id_taptest_table ( col1 int primary key , col2 text not null default 'stuff' references partman_test.fk_test_reference (col2) , col3 timestamptz DEFAULT now()); INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(1,9)); GRANT SELECT,INSERT,UPDATE ON partman_test.id_taptest_table TO partman_basic; GRANT ALL ON partman_test.id_taptest_table TO partman_revoke; SELECT results_eq('SELECT create_parent(''partman_test.id_taptest_table'', ''col1'', ''partman'', ''10'', p_inherit_fk := false, p_jobmon := false)::text', ARRAY['true'], 'Check that create_parent() returns true'); SELECT has_table('partman_test', 'id_taptest_table_p0', 'Check id_taptest_table_p0 exists'); SELECT has_table('partman_test', 'id_taptest_table_p10', 'Check id_taptest_table_p10 exists'); SELECT has_table('partman_test', 'id_taptest_table_p20', 'Check id_taptest_table_p20 exists'); SELECT has_table('partman_test', 'id_taptest_table_p30', 'Check id_taptest_table_p30 exists'); SELECT has_table('partman_test', 'id_taptest_table_p40', 'Check id_taptest_table_p40 exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50', 'Check id_taptest_table_p50 doesn''t exists yet'); SELECT col_is_pk('partman_test', 'id_taptest_table_p0', ARRAY['col1'], 'Check for primary key in id_taptest_table_p0'); SELECT col_is_pk('partman_test', 'id_taptest_table_p10', ARRAY['col1'], 'Check for primary key in id_taptest_table_p10'); SELECT col_is_pk('partman_test', 'id_taptest_table_p20', ARRAY['col1'], 'Check for primary key in id_taptest_table_p20'); SELECT col_is_pk('partman_test', 'id_taptest_table_p30', ARRAY['col1'], 'Check for primary key in id_taptest_table_p30'); SELECT col_is_pk('partman_test', 'id_taptest_table_p40', ARRAY['col1'], 'Check for primary key in id_taptest_table_p40'); SELECT col_isnt_fk('partman_test', 'id_taptest_table_p0', 'col2', 'Check that foreign key was NOT inherited to id_taptest_table_p0'); SELECT col_isnt_fk('partman_test', 'id_taptest_table_p10', 'col2', 'Check that foreign key was NOT inherited to id_taptest_table_p10'); SELECT col_isnt_fk('partman_test', 'id_taptest_table_p20', 'col2', 'Check that foreign key was NOT inherited to id_taptest_table_p20'); SELECT col_isnt_fk('partman_test', 'id_taptest_table_p30', 'col2', 'Check that foreign key was NOT inherited to id_taptest_table_p30'); SELECT col_isnt_fk('partman_test', 'id_taptest_table_p40', 'col2', 'Check that foreign key was NOT inherited to id_taptest_table_p40'); SELECT table_privs_are('partman_test', 'id_taptest_table_p0', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p0'); SELECT table_privs_are('partman_test', 'id_taptest_table_p10', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p10'); SELECT table_privs_are('partman_test', 'id_taptest_table_p20', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p20'); SELECT table_privs_are('partman_test', 'id_taptest_table_p30', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p30'); SELECT table_privs_are('partman_test', 'id_taptest_table_p40', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p40'); SELECT table_privs_are('partman_test', 'id_taptest_table_p0', 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of id_taptest_table_p0'); SELECT table_privs_are('partman_test', 'id_taptest_table_p10', 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of id_taptest_table_p10'); SELECT table_privs_are('partman_test', 'id_taptest_table_p20', 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of id_taptest_table_p20'); SELECT table_privs_are('partman_test', 'id_taptest_table_p30', 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of id_taptest_table_p30'); SELECT table_privs_are('partman_test', 'id_taptest_table_p40', 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of id_taptest_table_p40'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table''::regclass', ARRAY['u'], 'Check that parent table is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p0''::regclass', ARRAY['u'], 'Check that id_taptest_table_p0 is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p10''::regclass', ARRAY['u'], 'Check that id_taptest_table_p10 is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p20''::regclass', ARRAY['u'], 'Check that id_taptest_table_p20 is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p30''::regclass', ARRAY['u'], 'Check that id_taptest_table_p30 is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p40''::regclass', ARRAY['u'], 'Check that id_taptest_table_p40 is unlogged'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table'', p_batch_count := 5)::int', ARRAY[9], 'Check that partitioning function returns correct count of rows moved'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[9], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0', ARRAY[9], 'Check count from id_taptest_table_p0'); REVOKE INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON partman_test.id_taptest_table FROM partman_revoke; INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(10,25)); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10', ARRAY[10], 'Check count from id_taptest_table_p10'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20', ARRAY[6], 'Check count from id_taptest_table_p20'); -- Make sure automatic 50% serial partition creation is not working SELECT hasnt_table('partman_test', 'id_taptest_table_p50', 'Check id_taptest_table_p50 does not exist yet (before bgw run)'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60', 'Check id_taptest_table_p60 does not exist yet (before bgw run)'); SELECT pass('Waiting 20 seconds for background worker to run...'); SELECT pg_sleep(20); SELECT has_table('partman_test', 'id_taptest_table_p50', 'Check id_taptest_table_p50 exists'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p50''::regclass', ARRAY['u'], 'Check that id_taptest_table_p50 is unlogged'); SELECT has_table('partman_test', 'id_taptest_table_p60', 'Check id_taptest_table_p60 exists'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p60''::regclass', ARRAY['u'], 'Check that id_taptest_table_p60 is unlogged'); SELECT hasnt_table('partman_test', 'id_taptest_table_p70', 'Check id_taptest_table_p70 doesn''t exists yet'); SELECT col_is_pk('partman_test', 'id_taptest_table_p50', ARRAY['col1'], 'Check for primary key in id_taptest_table_p50'); SELECT col_isnt_fk('partman_test', 'id_taptest_table_p50', 'col2', 'Check that foreign key was NOT inherited to id_taptest_table_p50'); SELECT col_is_pk('partman_test', 'id_taptest_table_p60', ARRAY['col1'], 'Check for primary key in id_taptest_table_p60'); SELECT col_isnt_fk('partman_test', 'id_taptest_table_p60', 'col2', 'Check that foreign key was NOT inherited to id_taptest_table_p60'); SELECT table_privs_are('partman_test', 'id_taptest_table_p0', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p0'); SELECT table_privs_are('partman_test', 'id_taptest_table_p10', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p10'); SELECT table_privs_are('partman_test', 'id_taptest_table_p20', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p20'); SELECT table_privs_are('partman_test', 'id_taptest_table_p30', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p30'); SELECT table_privs_are('partman_test', 'id_taptest_table_p40', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p40'); SELECT table_privs_are('partman_test', 'id_taptest_table_p50', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p50'); SELECT table_privs_are('partman_test', 'id_taptest_table_p50', 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of id_taptest_table_p50'); SELECT table_privs_are('partman_test', 'id_taptest_table_p60', 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of id_taptest_table_p60'); GRANT DELETE ON partman_test.id_taptest_table TO partman_basic; REVOKE ALL ON partman_test.id_taptest_table FROM partman_revoke; ALTER TABLE partman_test.id_taptest_table OWNER TO partman_owner; INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(26,38)); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[38], 'Check count from id_taptest_table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20', ARRAY[10], 'Check count from id_taptest_table_p20'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30', ARRAY[9], 'Check count from id_taptest_table_p30'); SELECT pass('Waiting 20 seconds for background worker to run...'); SELECT pg_sleep(20); SELECT has_table('partman_test', 'id_taptest_table_p70', 'Check id_taptest_table_p70 exists'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p70''::regclass', ARRAY['u'], 'Check that id_taptest_table_p70 is unlogged'); SELECT hasnt_table('partman_test', 'id_taptest_table_p80', 'Check id_taptest_table_p90 doesn''t exists yet'); SELECT col_is_pk('partman_test', 'id_taptest_table_p70', ARRAY['col1'], 'Check for primary key in id_taptest_table_p70'); SELECT col_isnt_fk('partman_test', 'id_taptest_table_p70', 'col2', 'Check that foreign key was NOT inherited to id_taptest_table_p70'); SELECT table_privs_are('partman_test', 'id_taptest_table_p0', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p0'); SELECT table_privs_are('partman_test', 'id_taptest_table_p10', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p10'); SELECT table_privs_are('partman_test', 'id_taptest_table_p20', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p20'); SELECT table_privs_are('partman_test', 'id_taptest_table_p30', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p30'); SELECT table_privs_are('partman_test', 'id_taptest_table_p40', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p40'); SELECT table_privs_are('partman_test', 'id_taptest_table_p50', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p50'); SELECT table_privs_are('partman_test', 'id_taptest_table_p60', 'partman_basic', ARRAY['SELECT', 'INSERT', 'UPDATE'], 'Check partman_basic privileges of id_taptest_table_p60'); SELECT table_privs_are('partman_test', 'id_taptest_table_p50', 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of id_taptest_table_p50'); SELECT table_privs_are('partman_test', 'id_taptest_table_p60', 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of id_taptest_table_p60'); SELECT table_privs_are('partman_test', 'id_taptest_table_p70', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p70'); SELECT table_owner_is('partman_test', 'id_taptest_table_p70', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p70'); SELECT table_privs_are('partman_test', 'id_taptest_table_p70', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p70'); INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(200,210)); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT pass('Waiting 20 seconds for background worker to run...'); SELECT pg_sleep(20); SELECT reapply_privileges('partman_test.id_taptest_table'); SELECT table_privs_are('partman_test', 'id_taptest_table_p0', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p0'); SELECT table_privs_are('partman_test', 'id_taptest_table_p10', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p10'); SELECT table_privs_are('partman_test', 'id_taptest_table_p20', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p20'); SELECT table_privs_are('partman_test', 'id_taptest_table_p30', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p30'); SELECT table_privs_are('partman_test', 'id_taptest_table_p40', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p40'); SELECT table_privs_are('partman_test', 'id_taptest_table_p50', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p50'); SELECT table_privs_are('partman_test', 'id_taptest_table_p60', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p60'); SELECT table_privs_are('partman_test', 'id_taptest_table_p70', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p70'); SELECT table_privs_are('partman_test', 'id_taptest_table_p0', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p0'); SELECT table_privs_are('partman_test', 'id_taptest_table_p10', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p10'); SELECT table_privs_are('partman_test', 'id_taptest_table_p20', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p20'); SELECT table_privs_are('partman_test', 'id_taptest_table_p30', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p30'); SELECT table_privs_are('partman_test', 'id_taptest_table_p40', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p40'); SELECT table_privs_are('partman_test', 'id_taptest_table_p50', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p50'); SELECT table_privs_are('partman_test', 'id_taptest_table_p60', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p60'); SELECT table_privs_are('partman_test', 'id_taptest_table_p70', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p70'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p0', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p0'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p10', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p10'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p20', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p20'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p30', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p30'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p40', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p40'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p50', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p50'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p60', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p60'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p70', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p70'); -- Max value is 38 above SELECT drop_partition_id('partman_test.id_taptest_table', '20', p_keep_table := false); SELECT hasnt_table('partman_test', 'id_taptest_table_p0', 'Check id_taptest_table_p0 doesn''t exists anymore'); UPDATE part_config SET retention = '10', retention_schema = 'partman_retention_test' WHERE parent_table = 'partman_test.id_taptest_table'; SELECT pass('Waiting 20 seconds for background worker to run...'); SELECT pg_sleep(20); SELECT hasnt_table('partman_test', 'id_taptest_table_p10', 'Check id_taptest_table_p10 doesn''t exists anymore'); SELECT has_table('partman_retention_test', 'id_taptest_table_p10', 'Check id_taptest_table_p10 got moved to new schema'); -- Has to run twice because second time around is when it sees the partition is empty & drops it SELECT undo_partition('partman_test.id_taptest_table', 2, p_keep_table := false); SELECT hasnt_table('partman_test', 'id_taptest_table_p20', 'Check id_taptest_table_p20 does not exist'); SELECT undo_partition('partman_test.id_taptest_table', 10, p_keep_table := false); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table', ARRAY[30], 'Check count from parent table after undo'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30', 'Check id_taptest_table_p30 does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40', 'Check id_taptest_table_p40 does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50', 'Check id_taptest_table_p50 does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60', 'Check id_taptest_table_p60 does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p70', 'Check id_taptest_table_p70 does not exist'); DROP SCHEMA IF EXISTS partman_test CASCADE; DROP SCHEMA IF EXISTS partman_retention_test CASCADE; DROP ROLE IF EXISTS partman_basic; DROP ROLE IF EXISTS partman_revoke; DROP ROLE IF EXISTS partman_owner; SELECT hasnt_schema('partman_test', 'Ensure partman_test schema has been dropped'); SELECT hasnt_schema('partman_retention_test', 'Ensure partman_retention_test schema has been dropped'); SELECT hasnt_role('partman_basic', 'Ensure partman_basic role has been dropped'); SELECT hasnt_role('partman_revoke', 'Ensure partman_revoke role has been dropped'); SELECT hasnt_role('partman_owner', 'Ensure partman_owner role has been dropped'); SELECT * FROM finish(); --ROLLBACK; pg_partman-4.6.0/test/test_bgw/test-time-daily-bgw.sql000066400000000000000000000707111412761706100227720ustar00rootroot00000000000000-- ########## TIME TESTS WITH BACKGROUND WORKER RUNNING ########## -- Other tests: larger than necessary p_batch_count to partition_data_time(), create_parent() returns true, retention to new schema, retention keep indexes -- Set the pg_partman_bgw.interval setting in postgresql.conf to 10 seconds (or less) in order for this test suite to pass successfully. -- Cannot run this test inside a transaction since then the BGW would not see this partition set exists \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true --BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(108); CREATE SCHEMA partman_test; CREATE SCHEMA partman_retention_test; CREATE ROLE partman_basic; CREATE ROLE partman_revoke; CREATE ROLE partman_owner; CREATE TABLE partman_test.time_taptest_table (col1 int primary key, col2 text, col3 timestamptz NOT NULL DEFAULT now()); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); GRANT SELECT,INSERT,UPDATE ON partman_test.time_taptest_table TO partman_basic; GRANT ALL ON partman_test.time_taptest_table TO partman_revoke; SELECT create_parent('partman_test.time_taptest_table', 'col3', 'partman', 'daily'); 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')||' exists'); 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 table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table'', p_batch_count := 5)::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved'); 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')); REVOKE INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON partman_test.time_taptest_table FROM partman_revoke; INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '1 day'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '2 days'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[25], 'Check count from time_taptest_table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY[10], '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[5], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); UPDATE part_config SET premake = 5, optimize_trigger = 5 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT pass('Waiting 20 seconds for background worker to run...'); SELECT pg_sleep(20); SELECT has_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')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); GRANT DELETE ON partman_test.time_taptest_table TO partman_basic; REVOKE ALL ON partman_test.time_taptest_table FROM partman_revoke; ALTER TABLE partman_test.time_taptest_table OWNER TO partman_owner; UPDATE part_config SET premake = 6, optimize_trigger = 6 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT pass('Waiting 20 seconds for background worker to run...'); SELECT pg_sleep(20); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '20 days'::interval); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT reapply_privileges('partman_test.time_taptest_table'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT drop_partition_time('partman_test.time_taptest_table', '3 days', p_keep_table := false); 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')||' does not exist'); UPDATE part_config SET retention = '2 days'::interval, retention_schema = 'partman_retention_test' WHERE parent_table = 'partman_test.time_taptest_table'; SELECT pass('Waiting 20 seconds for background worker to run...'); SELECT pg_sleep(20); 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')||' does not exist'); SELECT has_table('partman_retention_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')||' got moved to new schema'); SELECT undo_partition('partman_test.time_taptest_table', 20); 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')||' still exists'); SELECT is_empty('SELECT * FROM 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 empty'); SELECT is_empty('SELECT * FROM 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 empty'); 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')||' still exists'); SELECT is_empty('SELECT * FROM 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 empty'); SELECT is_empty('SELECT * FROM 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 empty'); 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')||' still exists'); SELECT is_empty('SELECT * FROM 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 empty'); 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')||' still exists'); SELECT is_empty('SELECT * FROM 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 empty'); 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')||' still exists'); SELECT is_empty('SELECT * FROM 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 empty'); SELECT has_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')||' still exists'); SELECT is_empty('SELECT * FROM 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 empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' is empty'); DROP SCHEMA IF EXISTS partman_test CASCADE; DROP SCHEMA IF EXISTS partman_retention_test CASCADE; DROP ROLE IF EXISTS partman_basic; DROP ROLE IF EXISTS partman_revoke; DROP ROLE IF EXISTS partman_owner; SELECT hasnt_schema('partman_test', 'Ensure partman_test schema has been dropped'); SELECT hasnt_schema('partman_retention_test', 'Ensure partman_retention_test schema has been dropped'); SELECT hasnt_role('partman_basic', 'Ensure partman_basic role has been dropped'); SELECT hasnt_role('partman_revoke', 'Ensure partman_revoke role has been dropped'); SELECT hasnt_role('partman_owner', 'Ensure partman_owner role has been dropped'); SELECT * FROM finish(); --ROLLBACK; pg_partman-4.6.0/test/test_custom_time/000077500000000000000000000000001412761706100202245ustar00rootroot00000000000000pg_partman-4.6.0/test/test_custom_time/test-time-custom-100years-epoch.sql000066400000000000000000001432161412761706100266350ustar00rootroot00000000000000-- ########## TIME CUSTOM TESTS ########## \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(157); CREATE SCHEMA partman_test; CREATE SCHEMA partman_retention_test; CREATE ROLE partman_basic; CREATE ROLE partman_revoke; CREATE ROLE partman_owner; CREATE TABLE partman_test.time_taptest_table (col1 int primary key, col2 text, col3 bigint NOT NULL DEFAULT extract('epoch' from now())); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), extract('epoch' from CURRENT_TIMESTAMP)); GRANT SELECT,INSERT,UPDATE ON partman_test.time_taptest_table TO partman_basic; GRANT ALL ON partman_test.time_taptest_table TO partman_revoke; SELECT create_parent('partman_test.time_taptest_table', 'col3', 'partman', '100 years', p_epoch := 'seconds'); SELECT results_eq('SELECT partition_type FROM part_config WHERE parent_table = ''partman_test.time_taptest_table''' , ARRAY['time-custom'], 'Check that part_config has time-custom for partition_type value'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY')||' does not exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'500 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'500 years'::interval, 'YYYY')||' does not exist'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY')); SELECT partition_data_time('partman_test.time_taptest_table'); 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(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY')); REVOKE INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON partman_test.time_taptest_table FROM partman_revoke; INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), extract('epoch' from CURRENT_TIMESTAMP + '100 years'::interval)); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), extract('epoch' from CURRENT_TIMESTAMP + '200 years'::interval)); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(26,30), extract('epoch' from CURRENT_TIMESTAMP + '300 years'::interval)); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(31,37), extract('epoch' from CURRENT_TIMESTAMP + '400 years'::interval)); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(40,49), extract('epoch' from CURRENT_TIMESTAMP - '100 years'::interval)); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(50,70), extract('epoch' from CURRENT_TIMESTAMP - '200 years'::interval)); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(71,85), extract('epoch' from CURRENT_TIMESTAMP - '300 years'::interval)); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(86,100), extract('epoch' from CURRENT_TIMESTAMP - '400 years'::interval)); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY'), ARRAY[7], 'Check count from time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY'), ARRAY[21], 'Check count from time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY')); UPDATE part_config SET premake = 5 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(101,122), extract('epoch' from CURRENT_TIMESTAMP + '500 years'::interval)); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY')||' exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY')); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY'), ARRAY[22], 'Check count from time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY')); GRANT DELETE ON partman_test.time_taptest_table TO partman_basic; REVOKE ALL ON partman_test.time_taptest_table FROM partman_revoke; ALTER TABLE partman_test.time_taptest_table OWNER TO partman_owner; UPDATE part_config SET premake = 6 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(123,150), extract('epoch' from CURRENT_TIMESTAMP + '600 years'::interval)); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[148], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY'), ARRAY[28], 'Check count from time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY')); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1200 years'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1200 years'::interval, 'YYYY')||' exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY')); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), extract('epoch' from CURRENT_TIMESTAMP + '2000 years'::interval)); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT reapply_privileges('partman_test.time_taptest_table'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY')); SELECT drop_partition_time('partman_test.time_taptest_table', '300 years', p_keep_table := false); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY')||' does not exist'); UPDATE part_config SET retention = '200 years'::interval WHERE parent_table = 'partman_test.time_taptest_table'; SELECT drop_partition_time('partman_test.time_taptest_table', p_retention_schema := 'partman_retention_test'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY')||' does not exist'); SELECT has_table('partman_retention_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY')||' got moved to new schema'); SELECT undo_partition('partman_test.time_taptest_table', 20, p_keep_table := false); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[129], 'Check count from parent table after undo'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY')||' does not exist'); SELECT is_empty('SELECT * FROM custom_time_partitions WHERE parent_table = ''partman_test.time_taptest_table''', 'Check that custom_time_partitions table is empty'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test_custom_time/test-time-custom-100years.sql000066400000000000000000001377371412761706100255540ustar00rootroot00000000000000-- ########## TIME CUSTOM TESTS ########## -- Other tests: pass 'time-custom' to create_parent() \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(152); CREATE SCHEMA partman_test; CREATE SCHEMA partman_retention_test; CREATE ROLE partman_basic; CREATE ROLE partman_revoke; CREATE ROLE partman_owner; CREATE TABLE partman_test.time_taptest_table (col1 int primary key, col2 text, col3 timestamptz NOT NULL DEFAULT now()); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); GRANT SELECT,INSERT,UPDATE ON partman_test.time_taptest_table TO partman_basic; GRANT ALL ON partman_test.time_taptest_table TO partman_revoke; SELECT create_parent('partman_test.time_taptest_table', 'col3', 'time-custom', '100 years'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY')||' does not exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'500 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'500 years'::interval, 'YYYY')||' does not exist'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY')); SELECT partition_data_time('partman_test.time_taptest_table'); 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(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY')); REVOKE INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON partman_test.time_taptest_table FROM partman_revoke; INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '100 years'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '200 years'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(26,30), CURRENT_TIMESTAMP + '300 years'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(31,37), CURRENT_TIMESTAMP + '400 years'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(40,49), CURRENT_TIMESTAMP - '100 years'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(50,70), CURRENT_TIMESTAMP - '200 years'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(71,85), CURRENT_TIMESTAMP - '300 years'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(86,100), CURRENT_TIMESTAMP - '400 years'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY'), ARRAY[7], 'Check count from time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY'), ARRAY[21], 'Check count from time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY')); UPDATE part_config SET premake = 5, optimize_trigger = 5 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(101,122), CURRENT_TIMESTAMP + '500 years'::interval); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY')||' does not exist'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY')); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY'), ARRAY[22], 'Check count from time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY')); GRANT DELETE ON partman_test.time_taptest_table TO partman_basic; REVOKE ALL ON partman_test.time_taptest_table FROM partman_revoke; ALTER TABLE partman_test.time_taptest_table OWNER TO partman_owner; UPDATE part_config SET premake = 6, optimize_trigger = 6 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(123,150), CURRENT_TIMESTAMP + '600 years'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[148], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY'), ARRAY[28], 'Check count from time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY')); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1200 years'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1200 years'::interval, 'YYYY')||' does not exist'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY')); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '2000 years'::interval); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT reapply_privileges('partman_test.time_taptest_table'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY')); SELECT drop_partition_time('partman_test.time_taptest_table', '300 years', p_keep_table := false); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY')||' does not exist'); UPDATE part_config SET retention = '200 years'::interval WHERE parent_table = 'partman_test.time_taptest_table'; SELECT drop_partition_time('partman_test.time_taptest_table', p_retention_schema := 'partman_retention_test'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY')||' does not exist'); SELECT has_table('partman_retention_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY')||' got moved to new schema'); SELECT undo_partition('partman_test.time_taptest_table', 20, p_keep_table := false); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[129], 'Check count from parent table after undo'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY')||' does not exist'); SELECT is_empty('SELECT * FROM custom_time_partitions WHERE parent_table = ''partman_test.time_taptest_table''', 'Check that custom_time_partitions table is empty'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test_custom_time/test-time-custom-30sec.sql000066400000000000000000002327521412761706100251160ustar00rootroot00000000000000-- ########## TIME CUSTOM TESTS ########## -- May fail when run in the first or second 30 seconds of the minute due to rounding down to the nearest minute. -- If it does, wait until the next block of 30 seconds starts and try again. -- If it is failing no matter when it is run, please create an issue on Github with a log of your result when running with "pg_prove -ovf" \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(150); CREATE SCHEMA partman_test; CREATE SCHEMA partman_retention_test; CREATE ROLE partman_basic; CREATE ROLE partman_revoke; CREATE ROLE partman_owner; CREATE TABLE partman_test.time_taptest_table (col1 int primary key, col2 text, col3 timestamptz NOT NULL DEFAULT now()); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); GRANT SELECT,INSERT,UPDATE ON partman_test.time_taptest_table TO partman_basic; GRANT ALL ON partman_test.time_taptest_table TO partman_revoke; SELECT create_parent('partman_test.time_taptest_table', 'col3', 'partman', '30 seconds'); -- Must run_maintenance because when interval time is between 1 hour and 1 minute, the first partition name done by above is always the nearest hour rounded down SELECT run_maintenance(); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MISS')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' exists'); /* extra previous tables may exist due to new rounding down of the hour. Test left here for manual checking SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'150 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'150 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist'); */ SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT partition_data_time('partman_test.time_taptest_table'); 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(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS')); REVOKE INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON partman_test.time_taptest_table FROM partman_revoke; INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '30 secs'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '60 secs'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(26,30), CURRENT_TIMESTAMP + '90 secs'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(31,37), CURRENT_TIMESTAMP + '120 secs'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(40,49), CURRENT_TIMESTAMP - '30 secs'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(50,70), CURRENT_TIMESTAMP - '60 secs'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(71,85), CURRENT_TIMESTAMP - '90 secs'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(86,100), CURRENT_TIMESTAMP - '120 secs'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[7], 'Check count from time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[21], 'Check count from time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 secs'::interval, 'YYYY_MM_DD_HH24MISS')); UPDATE part_config SET premake = 5, optimize_trigger = 5 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(101,122), CURRENT_TIMESTAMP + '150 secs'::interval); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[22], 'Check count from time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 secs'::interval, 'YYYY_MM_DD_HH24MISS')); GRANT DELETE ON partman_test.time_taptest_table TO partman_basic; REVOKE ALL ON partman_test.time_taptest_table FROM partman_revoke; ALTER TABLE partman_test.time_taptest_table OWNER TO partman_owner; UPDATE part_config SET premake = 6, optimize_trigger = 6 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(123,150), CURRENT_TIMESTAMP + '180 secs'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[148], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[28], 'Check count from time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'360 mins'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'360 mins'::interval, 'YYYY_MM_DD_HH24MISS')||' exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 secs'::interval, 'YYYY_MM_DD_HH24MISS')); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '600 mins'::interval); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT reapply_privileges('partman_test.time_taptest_table'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT drop_partition_time('partman_test.time_taptest_table', '90 secs', p_keep_table := false); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist'); UPDATE part_config SET retention = '60 secs'::interval WHERE parent_table = 'partman_test.time_taptest_table'; SELECT drop_partition_time('partman_test.time_taptest_table', p_retention_schema := 'partman_retention_test'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist'); SELECT has_table('partman_retention_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' got moved to new schema'); SELECT undo_partition('partman_test.time_taptest_table', 20, p_keep_table := false); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[129], 'Check count from parent table after undo'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test_native/000077500000000000000000000000001412761706100171625ustar00rootroot00000000000000pg_partman-4.6.0/test/test_native/not_working_yet/000077500000000000000000000000001412761706100224035ustar00rootroot00000000000000pg_partman-4.6.0/test/test_native/not_working_yet/test-id-id-id-subpart-native.sql000066400000000000000000017413351412761706100304410ustar00rootroot00000000000000-- ########## NATIVE ID PARENT / ID SUBPARENT / ID SUB-SUB-PARENT ########## -- May have to increase max_locks_per_transaction above the default 64 for this test to run properly --TODO comment out cluster index stuff until native supports it \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(3096); CREATE SCHEMA partman_test; -- Add back when native support available --CREATE TABLE partman_test.id_taptest_table (col1 int primary key, col2 text default 'stuff', col3 timestamptz NOT NULL DEFAULT now()); -- ALTER TABLE partman_test.id_taptest_table CLUSTER ON id_taptest_table_pkey; CREATE TABLE partman_test.id_taptest_table ( col1 int , col2 text default 'stuff' , col3 timestamptz NOT NULL DEFAULT now()) PARTITION BY RANGE(col1); CREATE TABLE partman_test.undo_taptest (LIKE partman_test.id_taptest_table INCLUDING ALL); SELECT create_parent('partman_test.id_taptest_table', 'col1', 'native', '10000', p_jobmon := false, p_premake := 10); INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(1,100000)); -- larger insert for testing partition/undo scripts --INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(100001,2000000)); SELECT run_maintenance(); SELECT has_partition('partman_test', 'id_taptest_table', 'Check that id_taptest_table is natively partitioned'); SELECT has_table('partman_test', 'id_taptest_table_p100000', 'Check id_taptest_table_p100000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p110000', 'Check id_taptest_table_p110000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p120000', 'Check id_taptest_table_p120000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p90000', 'Check id_taptest_table_p90000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p80000', 'Check id_taptest_table_p80000 exists'); -- Restore when native supports cluster --SELECT results_eq('SELECT i.indisclustered::text FROM pg_class c JOIN pg_index i ON c.oid = i.indexrelid WHERE c.relname = ''id_taptest_table_p100000_pkey''', ARRAY['true'], -- 'Check that id_taptest_table_p100000_pkey is clustered'); --SELECT results_eq('SELECT i.indisclustered::text FROM pg_class c JOIN pg_index i ON c.oid = i.indexrelid WHERE c.relname = ''id_taptest_table_p110000_pkey''', ARRAY['true'], -- 'Check that id_taptest_table_p110000_pkey is clustered'); --SELECT results_eq('SELECT i.indisclustered::text FROM pg_class c JOIN pg_index i ON c.oid = i.indexrelid WHERE c.relname = ''id_taptest_table_p120000_pkey''', ARRAY['true'], -- 'Check that id_taptest_table_p120000_pkey is clustered'); --SELECT results_eq('SELECT i.indisclustered::text FROM pg_class c JOIN pg_index i ON c.oid = i.indexrelid WHERE c.relname = ''id_taptest_table_p90000_pkey''', ARRAY['true'], -- 'Check that id_taptest_table_p90000_pkey is clustered'); --SELECT results_eq('SELECT i.indisclustered::text FROM pg_class c JOIN pg_index i ON c.oid = i.indexrelid WHERE c.relname = ''id_taptest_table_p80000_pkey''', ARRAY['true'], -- 'Check that id_taptest_table_p80000_pkey is clustered'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[100000], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0', ARRAY[9999], 'Check count from id_taptest_table_p0'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000', ARRAY[10000], 'Check count from id_taptest_table_p10000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000', ARRAY[10000], 'Check count from id_taptest_table_p20000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000', ARRAY[10000], 'Check count from id_taptest_table_p30000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000', ARRAY[10000], 'Check count from id_taptest_table_p40000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000', ARRAY[10000], 'Check count from id_taptest_table_p50000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000', ARRAY[10000], 'Check count from id_taptest_table_p60000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000', ARRAY[10000], 'Check count from id_taptest_table_p70000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000', ARRAY[10000], 'Check count from id_taptest_table_p80000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000', ARRAY[10000], 'Check count from id_taptest_table_p90000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000', ARRAY[1], 'Check count from id_taptest_table_p100000'); SELECT create_sub_parent('partman_test.id_taptest_table', 'col1', 'native', '1000', p_jobmon := false, p_premake := 2, p_native_check := 'yes'); SELECT results_eq('SELECT sub_parent FROM part_config_sub ORDER BY sub_parent', ARRAY['partman_test.id_taptest_table'], 'Check that part_config_sub has all tables configured as needed'); --Reinsert data due to child table destruction INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(1,100000)); -- larger insert for testing partition/undo scripts --INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(100001,2000000)); SELECT has_partition('partman_test', 'id_taptest_table_p100000', 'Check id_taptest_table_p100000 is natively partitioned'); SELECT has_partition('partman_test', 'id_taptest_table_p110000', 'Check id_taptest_table_p110000 is natively partitioned'); SELECT has_partition('partman_test', 'id_taptest_table_p120000', 'Check id_taptest_table_p120000 is natively partitioned'); SELECT has_partition('partman_test', 'id_taptest_table_p90000', 'Check id_taptest_table_p90000 is natively partitioned'); SELECT has_partition('partman_test', 'id_taptest_table_p80000', 'Check id_taptest_table_p80000 is natively partitioned'); -- Test for normal partitions that should be made based on current max value of 100,000 SELECT has_table('partman_test', 'id_taptest_table_p90000_p98000', 'Check id_taptest_table_p90000_p98000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p90000_p99000', 'Check id_taptest_table_p90000_p99000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p100000_p100000', 'Check id_taptest_table_p100000_p100000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p100000_p101000', 'Check id_taptest_table_p100000_p101000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p100000_p102000', 'Check id_taptest_table_p100000_p102000 exists'); -- Restore when native supports cluster -- This should be enough cluster testing. If it worked above and here, should work for remaining subpartitioning --SELECT results_eq('SELECT i.indisclustered::text FROM pg_class c JOIN pg_index i ON c.oid = i.indexrelid WHERE c.relname = ''id_taptest_table_p90000_p98000_pkey''', ARRAY['true'], -- 'Check that id_taptest_table_p90000_p98000_pkey is clustered'); --SELECT results_eq('SELECT i.indisclustered::text FROM pg_class c JOIN pg_index i ON c.oid = i.indexrelid WHERE c.relname = ''id_taptest_table_p90000_p99000_pkey''', ARRAY['true'], -- 'Check that id_taptest_table_p90000_p99000_pkey is clustered'); --SELECT results_eq('SELECT i.indisclustered::text FROM pg_class c JOIN pg_index i ON c.oid = i.indexrelid WHERE c.relname = ''id_taptest_table_p100000_p100000_pkey''', ARRAY['true'], -- 'Check that id_taptest_table_p100000_p100000_pkey is clustered'); --SELECT results_eq('SELECT i.indisclustered::text FROM pg_class c JOIN pg_index i ON c.oid = i.indexrelid WHERE c.relname = ''id_taptest_table_p100000_p101000_pkey''', ARRAY['true'], -- 'Check that id_taptest_table_p100000_p101000_pkey is clustered'); --SELECT results_eq('SELECT i.indisclustered::text FROM pg_class c JOIN pg_index i ON c.oid = i.indexrelid WHERE c.relname = ''id_taptest_table_p100000_p102000_pkey''', ARRAY['true'], -- 'Check that id_taptest_table_p100000_p102000_pkey is clustered'); -- Tests that ensure minimal partition was made in all sets SELECT has_table('partman_test', 'id_taptest_table_p0_p0', 'Check id_taptest_table_p0_p0 exists'); SELECT has_table('partman_test', 'id_taptest_table_p10000_p10000', 'Check id_taptest_table_p10000_p10000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p20000_p20000', 'Check id_taptest_table_p20000_p20000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p30000_p30000', 'Check id_taptest_table_p30000_p30000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p40000_p40000', 'Check id_taptest_table_p40000_p40000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p50000_p50000', 'Check id_taptest_table_p50000_p50000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p60000_p60000', 'Check id_taptest_table_p60000_p60000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p70000_p70000', 'Check id_taptest_table_p70000_p70000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p80000_p80000', 'Check id_taptest_table_p80000_p80000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p110000_p110000', 'Check id_taptest_table_p110000_p110000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p120000_p120000', 'Check id_taptest_table_p120000_p120000 exists'); -- Test that all partitions have their data/exist SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[100000], 'Check count from parent table'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p0', 'Check that parent table id_taptest_table_p0 has had data moved to partition '); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p0', ARRAY[999], 'Check count from id_taptest_table_p0_p0'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p1000', ARRAY[1000], 'Check count from id_taptest_table_p0_p1000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p2000', ARRAY[1000], 'Check count from id_taptest_table_p0_p2000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p3000', ARRAY[1000], 'Check count from id_taptest_table_p0_p3000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p4000', ARRAY[1000], 'Check count from id_taptest_table_p0_p4000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p5000', ARRAY[1000], 'Check count from id_taptest_table_p0_p5000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p6000', ARRAY[1000], 'Check count from id_taptest_table_p0_p6000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p7000', ARRAY[1000], 'Check count from id_taptest_table_p0_p7000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p8000', ARRAY[1000], 'Check count from id_taptest_table_p0_p8000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p9000', ARRAY[1000], 'Check count from id_taptest_table_p0_p9000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10000', 'Check that parent table id_taptest_table_p10000 has had data moved to partition '); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p10000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p10000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p11000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p11000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p12000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p12000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p13000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p13000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p14000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p14000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p15000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p15000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p16000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p16000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p17000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p17000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p18000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p18000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p19000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p19000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p20000', 'Check that parent table id_taptest_table_p20000 has had data moved to partition '); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p20000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p20000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p21000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p21000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p22000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p22000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p23000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p23000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p24000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p24000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p25000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p25000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p26000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p26000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p27000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p27000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p28000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p28000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p29000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p29000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p30000', 'Check that parent table id_taptest_table_p30000 has had data moved to partition '); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p30000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p30000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p31000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p31000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p32000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p32000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p33000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p33000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p34000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p34000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p35000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p35000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p36000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p36000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p37000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p37000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p38000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p38000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p39000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p39000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p40000', 'Check that parent table id_taptest_table_p40000 has had data moved to partition '); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p40000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p40000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p41000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p41000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p42000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p42000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p43000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p43000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p44000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p44000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p45000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p45000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p46000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p46000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p47000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p47000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p48000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p48000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p49000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p49000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p50000', 'Check that parent table id_taptest_table_p50000 has had data moved to partition '); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p50000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p50000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p51000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p51000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p52000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p52000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p53000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p53000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p54000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p54000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p55000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p55000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p56000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p56000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p57000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p57000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p58000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p58000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p59000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p59000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p60000', 'Check that parent table id_taptest_table_p60000 has had data moved to partition '); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p60000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p60000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p61000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p61000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p62000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p62000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p63000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p63000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p64000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p64000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p65000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p65000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p66000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p66000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p67000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p67000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p68000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p68000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p69000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p69000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p70000', 'Check that parent table id_taptest_table_p70000 has had data moved to partition '); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p70000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p70000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p71000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p71000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p72000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p72000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p73000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p73000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p74000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p74000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p75000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p75000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p76000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p76000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p77000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p77000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p78000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p78000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p79000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p79000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p80000', 'Check that parent table id_taptest_table_p80000 has had data moved to partition '); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p80000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p80000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p81000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p81000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p82000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p82000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p83000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p83000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p84000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p84000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p85000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p85000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p86000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p86000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p87000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p87000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p88000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p88000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p89000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p89000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p90000', 'Check that parent table id_taptest_table_p90000 has had data moved to partition '); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p90000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p90000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p91000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p91000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p92000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p92000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p93000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p93000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p94000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p94000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p95000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p95000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p96000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p96000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p97000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p97000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p98000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p98000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p99000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p99000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p100000', 'Check that parent table id_taptest_table_p100000 has had data moved to partition '); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p100000', ARRAY[1], 'Check count from id_taptest_table_p100000_p100000'); -- Create next level of subpartitions SELECT create_sub_parent('partman_test.id_taptest_table_p0', 'col1', 'partman', '100', p_jobmon := false, p_premake := 2, p_native_check := 'yes'); SELECT create_sub_parent('partman_test.id_taptest_table_p10000', 'col1', 'partman', '100', p_jobmon := false, p_premake := 2, p_native_check := 'yes'); SELECT create_sub_parent('partman_test.id_taptest_table_p20000', 'col1', 'partman', '100', p_jobmon := false, p_premake := 2, p_native_check := 'yes'); SELECT create_sub_parent('partman_test.id_taptest_table_p30000', 'col1', 'partman', '100', p_jobmon := false, p_premake := 2, p_native_check := 'yes'); SELECT create_sub_parent('partman_test.id_taptest_table_p40000', 'col1', 'partman', '100', p_jobmon := false, p_premake := 2, p_native_check := 'yes'); SELECT create_sub_parent('partman_test.id_taptest_table_p50000', 'col1', 'partman', '100', p_jobmon := false, p_premake := 2, p_native_check := 'yes'); SELECT create_sub_parent('partman_test.id_taptest_table_p60000', 'col1', 'partman', '100', p_jobmon := false, p_premake := 2, p_native_check := 'yes'); SELECT create_sub_parent('partman_test.id_taptest_table_p70000', 'col1', 'partman', '100', p_jobmon := false, p_premake := 2, p_native_check := 'yes'); SELECT create_sub_parent('partman_test.id_taptest_table_p80000', 'col1', 'partman', '100', p_jobmon := false, p_premake := 2, p_native_check := 'yes'); SELECT create_sub_parent('partman_test.id_taptest_table_p90000', 'col1', 'partman', '100', p_jobmon := false, p_premake := 2, p_native_check := 'yes'); SELECT create_sub_parent('partman_test.id_taptest_table_p100000', 'col1', 'partman', '100', p_jobmon := false, p_premake := 2, p_native_check := 'yes'); SELECT create_sub_parent('partman_test.id_taptest_table_p110000', 'col1', 'partman', '100', p_jobmon := false, p_premake := 2, p_native_check := 'yes'); SELECT create_sub_parent('partman_test.id_taptest_table_p120000', 'col1', 'partman', '100', p_jobmon := false, p_premake := 2, p_native_check := 'yes'); --Reinsert data due to child table destruction INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(1,100000)); -- larger insert for testing partition/undo scripts --INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(100001,2000000)); SELECT results_eq('SELECT sub_parent FROM part_config_sub ORDER BY sub_parent', ARRAY['partman_test.id_taptest_table', 'partman_test.id_taptest_table_p0', 'partman_test.id_taptest_table_p10000', 'partman_test.id_taptest_table_p100000', 'partman_test.id_taptest_table_p110000', 'partman_test.id_taptest_table_p120000', 'partman_test.id_taptest_table_p20000', 'partman_test.id_taptest_table_p30000', 'partman_test.id_taptest_table_p40000', 'partman_test.id_taptest_table_p50000', 'partman_test.id_taptest_table_p60000', 'partman_test.id_taptest_table_p70000', 'partman_test.id_taptest_table_p80000', 'partman_test.id_taptest_table_p90000' ], 'Check that part_config_sub has all tables configured as needed'); -- TODO Add has_partition check for all new sub-partitions SELECT has_partition('partman_test', 'id_taptest_table_p100000_p100000', 'Check id_taptest_table_p100000_p100000 is natively partitioned'); SELECT has_table('partman_test', 'id_taptest_table_p110000_p110000', 'Check id_taptest_table_p110000_p110000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p120000_p120000', 'Check id_taptest_table_p120000_p120000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p90000_p90000', 'Check id_taptest_table_p90000_p90000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p80000_p80000', 'Check id_taptest_table_p80000_p80000 exists'); -- Test for normal partitions that should be made based on current max value of 100,000 SELECT has_table('partman_test', 'id_taptest_table_p90000_p99000_p99800', 'Check id_taptest_table_p90000_p99000_p99800 exists'); SELECT has_table('partman_test', 'id_taptest_table_p90000_p99000_p99900', 'Check id_taptest_table_p90000_p99000_p99800 exists'); SELECT has_table('partman_test', 'id_taptest_table_p100000_p100000_p100000', 'Check id_taptest_table_p100000_p100000_p100000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p100000_p100000_p100100', 'Check id_taptest_table_p100000_p100000_p100100 exists'); SELECT has_table('partman_test', 'id_taptest_table_p100000_p100000_p100200', 'Check id_taptest_table_p100000_p100000_p100200 exists'); -- Tests that ensure minimal partition was made in all sets SELECT has_table('partman_test', 'id_taptest_table_p0_p0_p0', 'Check id_taptest_table_p0_p0_p0 exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p1000_p1000', 'Check id_taptest_table_p0_p1000_p1000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p2000_p2000', 'Check id_taptest_table_p0_p2000_p2000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p3000_p3000', 'Check id_taptest_table_p0_p3000_p3000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p4000_p4000', 'Check id_taptest_table_p0_p4000_p4000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p5000_p5000', 'Check id_taptest_table_p0_p5000_p5000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p6000_p6000', 'Check id_taptest_table_p0_p6000_p6000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p7000_p7000', 'Check id_taptest_table_p0_p7000_p7000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p8000_p8000', 'Check id_taptest_table_p0_p8000_p8000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p9000_p9000', 'Check id_taptest_table_p0_p9000_p1000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p10000_p10000_p10000', 'Check id_taptest_table_p10000_p10000_p10000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p10000_p11000_p11000', 'Check id_taptest_table_p10000_p11000_p11000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p10000_p12000_p12000', 'Check id_taptest_table_p10000_p12000_p12000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p10000_p13000_p13000', 'Check id_taptest_table_p10000_p13000_p13000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p10000_p14000_p14000', 'Check id_taptest_table_p10000_p14000_p14000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p10000_p15000_p15000', 'Check id_taptest_table_p10000_p15000_p15000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p10000_p16000_p16000', 'Check id_taptest_table_p10000_p16000_p16000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p10000_p17000_p17000', 'Check id_taptest_table_p10000_p17000_p17000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p10000_p18000_p18000', 'Check id_taptest_table_p10000_p18000_p18000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p10000_p19000_p19000', 'Check id_taptest_table_p10000_p19000_p19000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p20000_p20000_p20000', 'Check id_taptest_table_p20000_p20000_p20000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p20000_p21000_p21000', 'Check id_taptest_table_p20000_p21000_p21000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p20000_p22000_p22000', 'Check id_taptest_table_p20000_p22000_p22000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p20000_p23000_p23000', 'Check id_taptest_table_p20000_p23000_p23000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p20000_p24000_p24000', 'Check id_taptest_table_p10000_p24000_p24000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p20000_p25000_p25000', 'Check id_taptest_table_p20000_p25000_p25000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p20000_p26000_p26000', 'Check id_taptest_table_p20000_p26000_p26000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p20000_p27000_p27000', 'Check id_taptest_table_p20000_p27000_p27000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p20000_p28000_p28000', 'Check id_taptest_table_p20000_p28000_p28000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p20000_p29000_p29000', 'Check id_taptest_table_p20000_p29000_p29000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p30000_p30000_p30000', 'Check id_taptest_table_p30000_p30000_p30000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p30000_p31000_p31000', 'Check id_taptest_table_p30000_p31000_p31000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p30000_p32000_p32000', 'Check id_taptest_table_p30000_p32000_p32000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p30000_p33000_p33000', 'Check id_taptest_table_p30000_p33000_p33000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p30000_p34000_p34000', 'Check id_taptest_table_p30000_p34000_p34000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p30000_p35000_p35000', 'Check id_taptest_table_p30000_p35000_p35000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p30000_p36000_p36000', 'Check id_taptest_table_p30000_p36000_p36000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p30000_p37000_p37000', 'Check id_taptest_table_p30000_p37000_p37000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p30000_p38000_p38000', 'Check id_taptest_table_p30000_p38000_p38000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p30000_p39000_p39000', 'Check id_taptest_table_p30000_p39000_p39000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p40000_p40000_p40000', 'Check id_taptest_table_p40000_p40000_p40000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p40000_p41000_p41000', 'Check id_taptest_table_p40000_p41000_p41000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p40000_p42000_p42000', 'Check id_taptest_table_p40000_p42000_p42000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p40000_p43000_p43000', 'Check id_taptest_table_p40000_p43000_p43000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p40000_p44000_p44000', 'Check id_taptest_table_p40000_p44000_p44000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p40000_p45000_p45000', 'Check id_taptest_table_p40000_p45000_p45000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p40000_p46000_p46000', 'Check id_taptest_table_p40000_p46000_p46000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p40000_p47000_p47000', 'Check id_taptest_table_p40000_p47000_p47000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p40000_p48000_p48000', 'Check id_taptest_table_p40000_p48000_p48000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p40000_p49000_p49000', 'Check id_taptest_table_p40000_p49000_p49000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p50000_p50000_p50000', 'Check id_taptest_table_p50000_p50000_p50000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p50000_p51000_p51000', 'Check id_taptest_table_p50000_p51000_p51000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p50000_p52000_p52000', 'Check id_taptest_table_p50000_p52000_p52000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p50000_p53000_p53000', 'Check id_taptest_table_p50000_p53000_p53000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p50000_p54000_p54000', 'Check id_taptest_table_p50000_p54000_p54000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p50000_p55000_p55000', 'Check id_taptest_table_p50000_p55000_p55000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p50000_p56000_p56000', 'Check id_taptest_table_p50000_p56000_p56000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p50000_p57000_p57000', 'Check id_taptest_table_p50000_p57000_p57000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p50000_p58000_p58000', 'Check id_taptest_table_p50000_p58000_p58000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p50000_p59000_p59000', 'Check id_taptest_table_p50000_p59000_p59000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p60000_p60000_p60000', 'Check id_taptest_table_p60000_p60000_p60000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p60000_p61000_p61000', 'Check id_taptest_table_p60000_p61000_p61000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p60000_p62000_p62000', 'Check id_taptest_table_p60000_p62000_p62000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p60000_p63000_p63000', 'Check id_taptest_table_p60000_p63000_p63000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p60000_p64000_p64000', 'Check id_taptest_table_p60000_p64000_p64000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p60000_p65000_p65000', 'Check id_taptest_table_p60000_p65000_p65000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p60000_p66000_p66000', 'Check id_taptest_table_p60000_p66000_p66000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p60000_p67000_p67000', 'Check id_taptest_table_p60000_p67000_p67000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p60000_p68000_p68000', 'Check id_taptest_table_p60000_p68000_p68000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p60000_p69000_p69000', 'Check id_taptest_table_p60000_p69000_p69000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p70000_p70000_p70000', 'Check id_taptest_table_p70000_p70000_p70000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p70000_p71000_p71000', 'Check id_taptest_table_p70000_p71000_p71000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p70000_p72000_p72000', 'Check id_taptest_table_p70000_p72000_p72000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p70000_p73000_p73000', 'Check id_taptest_table_p70000_p73000_p73000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p70000_p74000_p74000', 'Check id_taptest_table_p70000_p74000_p74000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p70000_p75000_p75000', 'Check id_taptest_table_p70000_p75000_p75000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p70000_p76000_p76000', 'Check id_taptest_table_p70000_p76000_p76000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p70000_p77000_p77000', 'Check id_taptest_table_p70000_p77000_p77000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p70000_p78000_p78000', 'Check id_taptest_table_p70000_p78000_p78000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p70000_p79000_p79000', 'Check id_taptest_table_p70000_p79000_p79000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p80000_p80000_p80000', 'Check id_taptest_table_p80000_p80000_p80000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p80000_p81000_p81000', 'Check id_taptest_table_p80000_p81000_p80000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p80000_p82000_p82000', 'Check id_taptest_table_p80000_p82000_p80000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p80000_p83000_p83000', 'Check id_taptest_table_p80000_p83000_p80000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p80000_p84000_p84000', 'Check id_taptest_table_p80000_p84000_p80000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p80000_p85000_p85000', 'Check id_taptest_table_p80000_p85000_p80000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p80000_p86000_p86000', 'Check id_taptest_table_p80000_p86000_p80000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p80000_p87000_p87000', 'Check id_taptest_table_p80000_p87000_p80000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p80000_p88000_p88000', 'Check id_taptest_table_p80000_p88000_p80000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p80000_p89000_p89000', 'Check id_taptest_table_p80000_p89000_p80000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p90000_p90000_p90000', 'Check id_taptest_table_p90000_p90000_p90000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p90000_p91000_p91000', 'Check id_taptest_table_p90000_p91000_p91000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p90000_p92000_p92000', 'Check id_taptest_table_p90000_p92000_p92000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p90000_p93000_p93000', 'Check id_taptest_table_p90000_p93000_p93000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p90000_p94000_p94000', 'Check id_taptest_table_p90000_p94000_p94000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p90000_p95000_p95000', 'Check id_taptest_table_p90000_p95000_p95000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p90000_p96000_p96000', 'Check id_taptest_table_p90000_p96000_p96000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p90000_p97000_p97000', 'Check id_taptest_table_p90000_p97000_p97000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p90000_p98000_p98000', 'Check id_taptest_table_p90000_p98000_p98000 exists'); -- All the extra partitions above exist because there is data in that range and it was partitioned out -- Do a hasnt check just for sanity SELECT has_table('partman_test', 'id_taptest_table_p100000_p100000_p100000', 'Check id_taptest_table_p100000_p100000_p100000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p100000_p101000_p101000', 'Check id_taptest_table_p100000_p101000_p101000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p100000_p102000_p102000', 'Check id_taptest_table_p100000_p102000_p102000 exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p100000_p103000_p103000', 'Check id_taptest_table_p100000_p103000_p103000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p110000_p110000_p110000', 'Check id_taptest_table_p110000_p110000_p110000 exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p110000_p111000_p111000', 'Check id_taptest_table_p110000_p111000_p111000 does not exist'); SELECT has_table('partman_test', 'id_taptest_table_p120000_p120000_p120000', 'Check id_taptest_table_p120000_p120000_p120000 exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p120000_p121000_p121000', 'Check id_taptest_table_p120000_p121000_p121000 does not exist'); -- Test that all parent tables partitions have no data SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[100000], 'Check count from parent table'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p0', 'Check that parent table id_taptest_table_p0 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10000', 'Check that parent table id_taptest_table_p10000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p20000', 'Check that parent table id_taptest_table_p20000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p30000', 'Check that parent table id_taptest_table_p30000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p40000', 'Check that parent table id_taptest_table_p40000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p50000', 'Check that parent table id_taptest_table_p50000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p60000', 'Check that parent table id_taptest_table_p60000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p70000', 'Check that parent table id_taptest_table_p70000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p80000', 'Check that parent table id_taptest_table_p80000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p90000', 'Check that parent table id_taptest_table_p90000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p100000', 'Check that parent table id_taptest_table_p100000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p0_p0', 'Check that parent table id_taptest_table_p0_p0 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p0_p1000', 'Check that parent table id_taptest_table_p0_p1000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p0_p2000', 'Check that parent table id_taptest_table_p0_p2000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p0_p3000', 'Check that parent table id_taptest_table_p0_p3000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p0_p4000', 'Check that parent table id_taptest_table_p0_p4000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p0_p5000', 'Check that parent table id_taptest_table_p0_p5000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p0_p6000', 'Check that parent table id_taptest_table_p0_p6000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p0_p7000', 'Check that parent table id_taptest_table_p0_p7000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p0_p8000', 'Check that parent table id_taptest_table_p0_p8000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p0_p9000', 'Check that parent table id_taptest_table_p0_p9000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10000_p10000', 'Check that parent table id_taptest_table_p10000_p10000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10000_p11000', 'Check that parent table id_taptest_table_p10000_p11000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10000_p12000', 'Check that parent table id_taptest_table_p10000_p12000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10000_p13000', 'Check that parent table id_taptest_table_p10000_p13000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10000_p14000', 'Check that parent table id_taptest_table_p10000_p14000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10000_p15000', 'Check that parent table id_taptest_table_p10000_p15000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10000_p16000', 'Check that parent table id_taptest_table_p10000_p16000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10000_p17000', 'Check that parent table id_taptest_table_p10000_p17000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10000_p18000', 'Check that parent table id_taptest_table_p10000_p18000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10000_p19000', 'Check that parent table id_taptest_table_p10000_p19000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p20000_p20000', 'Check that parent table id_taptest_table_p20000_p20000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p20000_p22000', 'Check that parent table id_taptest_table_p20000_p22000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p20000_p23000', 'Check that parent table id_taptest_table_p20000_p23000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p20000_p24000', 'Check that parent table id_taptest_table_p20000_p24000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p20000_p25000', 'Check that parent table id_taptest_table_p20000_p25000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p20000_p26000', 'Check that parent table id_taptest_table_p20000_p26000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p20000_p27000', 'Check that parent table id_taptest_table_p20000_p27000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p20000_p28000', 'Check that parent table id_taptest_table_p20000_p28000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p20000_p29000', 'Check that parent table id_taptest_table_p20000_p29000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p30000_p30000', 'Check that parent table id_taptest_table_p30000_p30000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p30000_p31000', 'Check that parent table id_taptest_table_p30000_p31000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p30000_p32000', 'Check that parent table id_taptest_table_p30000_p32000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p30000_p33000', 'Check that parent table id_taptest_table_p30000_p33000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p30000_p34000', 'Check that parent table id_taptest_table_p30000_p34000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p30000_p35000', 'Check that parent table id_taptest_table_p30000_p35000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p30000_p36000', 'Check that parent table id_taptest_table_p30000_p36000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p30000_p37000', 'Check that parent table id_taptest_table_p30000_p37000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p30000_p38000', 'Check that parent table id_taptest_table_p30000_p38000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p30000_p39000', 'Check that parent table id_taptest_table_p30000_p39000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p40000_p40000', 'Check that parent table id_taptest_table_p40000_p40000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p40000_p41000', 'Check that parent table id_taptest_table_p40000_p41000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p40000_p42000', 'Check that parent table id_taptest_table_p40000_p42000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p40000_p43000', 'Check that parent table id_taptest_table_p40000_p43000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p40000_p44000', 'Check that parent table id_taptest_table_p40000_p44000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p40000_p45000', 'Check that parent table id_taptest_table_p40000_p45000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p40000_p46000', 'Check that parent table id_taptest_table_p40000_p46000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p40000_p47000', 'Check that parent table id_taptest_table_p40000_p47000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p40000_p48000', 'Check that parent table id_taptest_table_p40000_p48000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p40000_p49000', 'Check that parent table id_taptest_table_p40000_p49000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p50000_p50000', 'Check that parent table id_taptest_table_p50000_p50000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p50000_p51000', 'Check that parent table id_taptest_table_p50000_p51000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p50000_p52000', 'Check that parent table id_taptest_table_p50000_p52000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p50000_p53000', 'Check that parent table id_taptest_table_p50000_p53000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p50000_p54000', 'Check that parent table id_taptest_table_p50000_p54000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p50000_p55000', 'Check that parent table id_taptest_table_p50000_p55000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p50000_p56000', 'Check that parent table id_taptest_table_p50000_p56000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p50000_p57000', 'Check that parent table id_taptest_table_p50000_p57000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p50000_p58000', 'Check that parent table id_taptest_table_p50000_p58000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p50000_p59000', 'Check that parent table id_taptest_table_p50000_p59000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p60000_p60000', 'Check that parent table id_taptest_table_p60000_p60000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p60000_p61000', 'Check that parent table id_taptest_table_p60000_p61000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p60000_p62000', 'Check that parent table id_taptest_table_p60000_p62000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p60000_p63000', 'Check that parent table id_taptest_table_p60000_p63000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p60000_p64000', 'Check that parent table id_taptest_table_p60000_p64000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p60000_p65000', 'Check that parent table id_taptest_table_p60000_p65000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p60000_p66000', 'Check that parent table id_taptest_table_p60000_p66000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p60000_p67000', 'Check that parent table id_taptest_table_p60000_p67000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p60000_p68000', 'Check that parent table id_taptest_table_p60000_p68000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p60000_p69000', 'Check that parent table id_taptest_table_p60000_p69000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p70000_p70000', 'Check that parent table id_taptest_table_p70000_p70000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p70000_p71000', 'Check that parent table id_taptest_table_p70000_p71000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p70000_p72000', 'Check that parent table id_taptest_table_p70000_p72000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p70000_p73000', 'Check that parent table id_taptest_table_p70000_p73000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p70000_p74000', 'Check that parent table id_taptest_table_p70000_p74000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p70000_p75000', 'Check that parent table id_taptest_table_p70000_p75000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p70000_p76000', 'Check that parent table id_taptest_table_p70000_p76000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p70000_p77000', 'Check that parent table id_taptest_table_p70000_p77000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p70000_p78000', 'Check that parent table id_taptest_table_p70000_p78000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p70000_p79000', 'Check that parent table id_taptest_table_p70000_p79000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p80000_p80000', 'Check that parent table id_taptest_table_p80000_p80000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p80000_p81000', 'Check that parent table id_taptest_table_p80000_p81000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p80000_p82000', 'Check that parent table id_taptest_table_p80000_p82000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p80000_p83000', 'Check that parent table id_taptest_table_p80000_p83000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p80000_p84000', 'Check that parent table id_taptest_table_p80000_p84000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p80000_p85000', 'Check that parent table id_taptest_table_p80000_p85000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p80000_p86000', 'Check that parent table id_taptest_table_p80000_p86000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p80000_p87000', 'Check that parent table id_taptest_table_p80000_p87000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p80000_p88000', 'Check that parent table id_taptest_table_p80000_p88000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p80000_p89000', 'Check that parent table id_taptest_table_p80000_p89000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p90000_p90000', 'Check that parent table id_taptest_table_p90000_p90000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p90000_p91000', 'Check that parent table id_taptest_table_p90000_p91000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p90000_p92000', 'Check that parent table id_taptest_table_p90000_p92000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p90000_p93000', 'Check that parent table id_taptest_table_p90000_p93000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p90000_p94000', 'Check that parent table id_taptest_table_p90000_p94000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p90000_p95000', 'Check that parent table id_taptest_table_p90000_p95000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p90000_p96000', 'Check that parent table id_taptest_table_p90000_p96000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p90000_p97000', 'Check that parent table id_taptest_table_p90000_p97000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p90000_p98000', 'Check that parent table id_taptest_table_p90000_p98000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p90000_p99000', 'Check that parent table id_taptest_table_p90000_p99000 has had data moved to partition '); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p100000_p100000', 'Check that parent table id_taptest_table_p100000_p100000 has had data moved to partition '); -- Test that all child partitions have their data/exist SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p0_p0', ARRAY[99], 'Check count from id_taptest_table_p0_p0_p0'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p0_p100', ARRAY[100], 'Check count from id_taptest_table_p0_p0_p100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p0_p200', ARRAY[100], 'Check count from id_taptest_table_p0_p0_p200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p0_p300', ARRAY[100], 'Check count from id_taptest_table_p0_p0_p300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p0_p400', ARRAY[100], 'Check count from id_taptest_table_p0_p0_p400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p0_p500', ARRAY[100], 'Check count from id_taptest_table_p0_p0_p500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p0_p600', ARRAY[100], 'Check count from id_taptest_table_p0_p0_p600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p0_p700', ARRAY[100], 'Check count from id_taptest_table_p0_p0_p700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p0_p800', ARRAY[100], 'Check count from id_taptest_table_p0_p0_p800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p0_p900', ARRAY[100], 'Check count from id_taptest_table_p0_p0_p900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p1000_p1000', ARRAY[100], 'Check count from id_taptest_table_p0_p1000_p1000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p1000_p1100', ARRAY[100], 'Check count from id_taptest_table_p0_p1000_p1100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p1000_p1200', ARRAY[100], 'Check count from id_taptest_table_p0_p1000_p1200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p1000_p1300', ARRAY[100], 'Check count from id_taptest_table_p0_p1000_p1300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p1000_p1400', ARRAY[100], 'Check count from id_taptest_table_p0_p1000_p1400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p1000_p1500', ARRAY[100], 'Check count from id_taptest_table_p0_p1000_p1500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p1000_p1600', ARRAY[100], 'Check count from id_taptest_table_p0_p1000_p1600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p1000_p1700', ARRAY[100], 'Check count from id_taptest_table_p0_p1000_p1700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p1000_p1800', ARRAY[100], 'Check count from id_taptest_table_p0_p1000_p1800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p1000_p1900', ARRAY[100], 'Check count from id_taptest_table_p0_p1000_p1900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p2000_p2000', ARRAY[100], 'Check count from id_taptest_table_p0_p2000_p2000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p2000_p2100', ARRAY[100], 'Check count from id_taptest_table_p0_p2000_p2100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p2000_p2200', ARRAY[100], 'Check count from id_taptest_table_p0_p2000_p2200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p2000_p2300', ARRAY[100], 'Check count from id_taptest_table_p0_p2000_p2300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p2000_p2400', ARRAY[100], 'Check count from id_taptest_table_p0_p2000_p2400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p2000_p2500', ARRAY[100], 'Check count from id_taptest_table_p0_p2000_p2500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p2000_p2600', ARRAY[100], 'Check count from id_taptest_table_p0_p2000_p2600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p2000_p2700', ARRAY[100], 'Check count from id_taptest_table_p0_p2000_p2700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p2000_p2800', ARRAY[100], 'Check count from id_taptest_table_p0_p2000_p2800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p2000_p2900', ARRAY[100], 'Check count from id_taptest_table_p0_p2000_p2900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p3000_p3000', ARRAY[100], 'Check count from id_taptest_table_p0_p3000_p3000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p3000_p3100', ARRAY[100], 'Check count from id_taptest_table_p0_p3000_p3100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p3000_p3200', ARRAY[100], 'Check count from id_taptest_table_p0_p3000_p3200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p3000_p3300', ARRAY[100], 'Check count from id_taptest_table_p0_p3000_p3300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p3000_p3400', ARRAY[100], 'Check count from id_taptest_table_p0_p3000_p3400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p3000_p3500', ARRAY[100], 'Check count from id_taptest_table_p0_p3000_p3500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p3000_p3600', ARRAY[100], 'Check count from id_taptest_table_p0_p3000_p3600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p3000_p3700', ARRAY[100], 'Check count from id_taptest_table_p0_p3000_p3700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p3000_p3800', ARRAY[100], 'Check count from id_taptest_table_p0_p3000_p3800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p3000_p3900', ARRAY[100], 'Check count from id_taptest_table_p0_p3000_p3900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p4000_p4000', ARRAY[100], 'Check count from id_taptest_table_p0_p4000_p4000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p4000_p4100', ARRAY[100], 'Check count from id_taptest_table_p0_p4000_p4100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p4000_p4200', ARRAY[100], 'Check count from id_taptest_table_p0_p4000_p4200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p4000_p4300', ARRAY[100], 'Check count from id_taptest_table_p0_p4000_p4300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p4000_p4400', ARRAY[100], 'Check count from id_taptest_table_p0_p4000_p4400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p4000_p4500', ARRAY[100], 'Check count from id_taptest_table_p0_p4000_p4500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p4000_p4600', ARRAY[100], 'Check count from id_taptest_table_p0_p4000_p4600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p4000_p4700', ARRAY[100], 'Check count from id_taptest_table_p0_p4000_p4700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p4000_p4800', ARRAY[100], 'Check count from id_taptest_table_p0_p4000_p4800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p4000_p4900', ARRAY[100], 'Check count from id_taptest_table_p0_p4000_p4900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p5000_p5000', ARRAY[100], 'Check count from id_taptest_table_p0_p5000_p5000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p5000_p5100', ARRAY[100], 'Check count from id_taptest_table_p0_p5000_p5100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p5000_p5200', ARRAY[100], 'Check count from id_taptest_table_p0_p5000_p5200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p5000_p5300', ARRAY[100], 'Check count from id_taptest_table_p0_p5000_p5300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p5000_p5400', ARRAY[100], 'Check count from id_taptest_table_p0_p5000_p5400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p5000_p5500', ARRAY[100], 'Check count from id_taptest_table_p0_p5000_p5500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p5000_p5600', ARRAY[100], 'Check count from id_taptest_table_p0_p5000_p5600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p5000_p5700', ARRAY[100], 'Check count from id_taptest_table_p0_p5000_p5700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p5000_p5800', ARRAY[100], 'Check count from id_taptest_table_p0_p5000_p5800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p5000_p5900', ARRAY[100], 'Check count from id_taptest_table_p0_p5000_p5900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p6000_p6000', ARRAY[100], 'Check count from id_taptest_table_p0_p6000_p6000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p6000_p6100', ARRAY[100], 'Check count from id_taptest_table_p0_p6000_p6100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p6000_p6200', ARRAY[100], 'Check count from id_taptest_table_p0_p6000_p6200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p6000_p6300', ARRAY[100], 'Check count from id_taptest_table_p0_p6000_p6300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p6000_p6400', ARRAY[100], 'Check count from id_taptest_table_p0_p6000_p6400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p6000_p6500', ARRAY[100], 'Check count from id_taptest_table_p0_p6000_p6500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p6000_p6600', ARRAY[100], 'Check count from id_taptest_table_p0_p6000_p6600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p6000_p6700', ARRAY[100], 'Check count from id_taptest_table_p0_p6000_p6700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p6000_p6800', ARRAY[100], 'Check count from id_taptest_table_p0_p6000_p6800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p6000_p6900', ARRAY[100], 'Check count from id_taptest_table_p0_p6000_p6900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p7000_p7000', ARRAY[100], 'Check count from id_taptest_table_p0_p7000_p7000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p7000_p7100', ARRAY[100], 'Check count from id_taptest_table_p0_p7000_p7100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p7000_p7200', ARRAY[100], 'Check count from id_taptest_table_p0_p7000_p7200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p7000_p7300', ARRAY[100], 'Check count from id_taptest_table_p0_p7000_p7300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p7000_p7400', ARRAY[100], 'Check count from id_taptest_table_p0_p7000_p7400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p7000_p7500', ARRAY[100], 'Check count from id_taptest_table_p0_p7000_p7500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p7000_p7600', ARRAY[100], 'Check count from id_taptest_table_p0_p7000_p7600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p7000_p7700', ARRAY[100], 'Check count from id_taptest_table_p0_p7000_p7700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p7000_p7800', ARRAY[100], 'Check count from id_taptest_table_p0_p7000_p7800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p7000_p7900', ARRAY[100], 'Check count from id_taptest_table_p0_p7000_p7900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p8000_p8000', ARRAY[100], 'Check count from id_taptest_table_p0_p8000_p8000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p8000_p8100', ARRAY[100], 'Check count from id_taptest_table_p0_p8000_p8100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p8000_p8200', ARRAY[100], 'Check count from id_taptest_table_p0_p8000_p8200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p8000_p8300', ARRAY[100], 'Check count from id_taptest_table_p0_p8000_p8300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p8000_p8400', ARRAY[100], 'Check count from id_taptest_table_p0_p8000_p8400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p8000_p8500', ARRAY[100], 'Check count from id_taptest_table_p0_p8000_p8500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p8000_p8600', ARRAY[100], 'Check count from id_taptest_table_p0_p8000_p8600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p8000_p8700', ARRAY[100], 'Check count from id_taptest_table_p0_p8000_p8700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p8000_p8800', ARRAY[100], 'Check count from id_taptest_table_p0_p8000_p8800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p8000_p8900', ARRAY[100], 'Check count from id_taptest_table_p0_p8000_p8900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p9000_p9000', ARRAY[100], 'Check count from id_taptest_table_p0_p9000_p9000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p9000_p9100', ARRAY[100], 'Check count from id_taptest_table_p0_p9000_p9100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p9000_p9200', ARRAY[100], 'Check count from id_taptest_table_p0_p9000_p9200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p9000_p9300', ARRAY[100], 'Check count from id_taptest_table_p0_p9000_p9300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p9000_p9400', ARRAY[100], 'Check count from id_taptest_table_p0_p9000_p9400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p9000_p9500', ARRAY[100], 'Check count from id_taptest_table_p0_p9000_p9500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p9000_p9600', ARRAY[100], 'Check count from id_taptest_table_p0_p9000_p9600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p9000_p9700', ARRAY[100], 'Check count from id_taptest_table_p0_p9000_p9700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p9000_p9800', ARRAY[100], 'Check count from id_taptest_table_p0_p9000_p9800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p9000_p9900', ARRAY[100], 'Check count from id_taptest_table_p0_p9000_p9900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p10000_p10000', ARRAY[100], 'Check count from id_taptest_table_p10000_p10000_p10000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p10000_p10100', ARRAY[100], 'Check count from id_taptest_table_p10000_p10000_p10100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p10000_p10200', ARRAY[100], 'Check count from id_taptest_table_p10000_p10000_p10200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p10000_p10300', ARRAY[100], 'Check count from id_taptest_table_p10000_p10000_p10300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p10000_p10400', ARRAY[100], 'Check count from id_taptest_table_p10000_p10000_p10400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p10000_p10500', ARRAY[100], 'Check count from id_taptest_table_p10000_p10000_p10500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p10000_p10600', ARRAY[100], 'Check count from id_taptest_table_p10000_p10000_p10600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p10000_p10700', ARRAY[100], 'Check count from id_taptest_table_p10000_p10000_p10700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p10000_p10800', ARRAY[100], 'Check count from id_taptest_table_p10000_p10000_p10800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p10000_p10900', ARRAY[100], 'Check count from id_taptest_table_p10000_p10000_p10900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p11000_p11000', ARRAY[100], 'Check count from id_taptest_table_p10000_p11000_p11000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p11000_p11100', ARRAY[100], 'Check count from id_taptest_table_p10000_p11000_p11100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p11000_p11200', ARRAY[100], 'Check count from id_taptest_table_p10000_p11000_p11200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p11000_p11300', ARRAY[100], 'Check count from id_taptest_table_p10000_p11000_p11300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p11000_p11400', ARRAY[100], 'Check count from id_taptest_table_p10000_p11000_p11400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p11000_p11500', ARRAY[100], 'Check count from id_taptest_table_p10000_p11000_p11500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p11000_p11600', ARRAY[100], 'Check count from id_taptest_table_p10000_p11000_p11600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p11000_p11700', ARRAY[100], 'Check count from id_taptest_table_p10000_p11000_p11700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p11000_p11800', ARRAY[100], 'Check count from id_taptest_table_p10000_p11000_p11800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p11000_p11900', ARRAY[100], 'Check count from id_taptest_table_p10000_p11000_p11900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p12000_p12000', ARRAY[100], 'Check count from id_taptest_table_p10000_p12000_p12000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p12000_p12100', ARRAY[100], 'Check count from id_taptest_table_p10000_p12000_p12100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p12000_p12200', ARRAY[100], 'Check count from id_taptest_table_p10000_p12000_p12200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p12000_p12300', ARRAY[100], 'Check count from id_taptest_table_p10000_p12000_p12300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p12000_p12400', ARRAY[100], 'Check count from id_taptest_table_p10000_p12000_p12400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p12000_p12500', ARRAY[100], 'Check count from id_taptest_table_p10000_p12000_p12500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p12000_p12600', ARRAY[100], 'Check count from id_taptest_table_p10000_p12000_p12600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p12000_p12700', ARRAY[100], 'Check count from id_taptest_table_p10000_p12000_p12700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p12000_p12800', ARRAY[100], 'Check count from id_taptest_table_p10000_p12000_p12800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p12000_p12900', ARRAY[100], 'Check count from id_taptest_table_p10000_p12000_p12900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p13000_p13000', ARRAY[100], 'Check count from id_taptest_table_p10000_p13000_p13000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p13000_p13100', ARRAY[100], 'Check count from id_taptest_table_p10000_p13000_p13100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p13000_p13200', ARRAY[100], 'Check count from id_taptest_table_p10000_p13000_p13200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p13000_p13300', ARRAY[100], 'Check count from id_taptest_table_p10000_p13000_p13300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p13000_p13400', ARRAY[100], 'Check count from id_taptest_table_p10000_p13000_p13400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p13000_p13500', ARRAY[100], 'Check count from id_taptest_table_p10000_p13000_p13500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p13000_p13600', ARRAY[100], 'Check count from id_taptest_table_p10000_p13000_p13600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p13000_p13700', ARRAY[100], 'Check count from id_taptest_table_p10000_p13000_p13700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p13000_p13800', ARRAY[100], 'Check count from id_taptest_table_p10000_p13000_p13800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p13000_p13900', ARRAY[100], 'Check count from id_taptest_table_p10000_p13000_p13900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p14000_p14000', ARRAY[100], 'Check count from id_taptest_table_p10000_p14000_p14000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p14000_p14100', ARRAY[100], 'Check count from id_taptest_table_p10000_p14000_p14100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p14000_p14200', ARRAY[100], 'Check count from id_taptest_table_p10000_p14000_p14200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p14000_p14300', ARRAY[100], 'Check count from id_taptest_table_p10000_p14000_p14300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p14000_p14400', ARRAY[100], 'Check count from id_taptest_table_p10000_p14000_p14400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p14000_p14500', ARRAY[100], 'Check count from id_taptest_table_p10000_p14000_p14500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p14000_p14600', ARRAY[100], 'Check count from id_taptest_table_p10000_p14000_p14600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p14000_p14700', ARRAY[100], 'Check count from id_taptest_table_p10000_p14000_p14700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p14000_p14800', ARRAY[100], 'Check count from id_taptest_table_p10000_p14000_p14800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p14000_p14900', ARRAY[100], 'Check count from id_taptest_table_p10000_p14000_p14900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p15000_p15000', ARRAY[100], 'Check count from id_taptest_table_p10000_p15000_p15000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p15000_p15100', ARRAY[100], 'Check count from id_taptest_table_p10000_p15000_p15100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p15000_p15200', ARRAY[100], 'Check count from id_taptest_table_p10000_p15000_p15200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p15000_p15300', ARRAY[100], 'Check count from id_taptest_table_p10000_p15000_p15300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p15000_p15400', ARRAY[100], 'Check count from id_taptest_table_p10000_p15000_p15400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p15000_p15500', ARRAY[100], 'Check count from id_taptest_table_p10000_p15000_p15500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p15000_p15600', ARRAY[100], 'Check count from id_taptest_table_p10000_p15000_p15600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p15000_p15700', ARRAY[100], 'Check count from id_taptest_table_p10000_p15000_p15700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p15000_p15800', ARRAY[100], 'Check count from id_taptest_table_p10000_p15000_p15800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p15000_p15900', ARRAY[100], 'Check count from id_taptest_table_p10000_p15000_p15900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p16000_p16000', ARRAY[100], 'Check count from id_taptest_table_p10000_p16000_p16000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p16000_p16100', ARRAY[100], 'Check count from id_taptest_table_p10000_p16000_p16100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p16000_p16200', ARRAY[100], 'Check count from id_taptest_table_p10000_p16000_p16200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p16000_p16300', ARRAY[100], 'Check count from id_taptest_table_p10000_p16000_p16300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p16000_p16400', ARRAY[100], 'Check count from id_taptest_table_p10000_p16000_p16400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p16000_p16500', ARRAY[100], 'Check count from id_taptest_table_p10000_p16000_p16500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p16000_p16600', ARRAY[100], 'Check count from id_taptest_table_p10000_p16000_p16600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p16000_p16700', ARRAY[100], 'Check count from id_taptest_table_p10000_p16000_p16700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p16000_p16800', ARRAY[100], 'Check count from id_taptest_table_p10000_p16000_p16800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p16000_p16900', ARRAY[100], 'Check count from id_taptest_table_p10000_p16000_p16900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p17000_p17000', ARRAY[100], 'Check count from id_taptest_table_p10000_p17000_p17000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p17000_p17100', ARRAY[100], 'Check count from id_taptest_table_p10000_p17000_p17100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p17000_p17200', ARRAY[100], 'Check count from id_taptest_table_p10000_p17000_p17200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p17000_p17300', ARRAY[100], 'Check count from id_taptest_table_p10000_p17000_p17300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p17000_p17400', ARRAY[100], 'Check count from id_taptest_table_p10000_p17000_p17400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p17000_p17500', ARRAY[100], 'Check count from id_taptest_table_p10000_p17000_p17500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p17000_p17600', ARRAY[100], 'Check count from id_taptest_table_p10000_p17000_p17600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p17000_p17700', ARRAY[100], 'Check count from id_taptest_table_p10000_p17000_p17700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p17000_p17800', ARRAY[100], 'Check count from id_taptest_table_p10000_p17000_p17800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p17000_p17900', ARRAY[100], 'Check count from id_taptest_table_p10000_p17000_p17900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p18000_p18000', ARRAY[100], 'Check count from id_taptest_table_p10000_p18000_p18000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p18000_p18100', ARRAY[100], 'Check count from id_taptest_table_p10000_p18000_p18100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p18000_p18200', ARRAY[100], 'Check count from id_taptest_table_p10000_p18000_p18200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p18000_p18300', ARRAY[100], 'Check count from id_taptest_table_p10000_p18000_p18300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p18000_p18400', ARRAY[100], 'Check count from id_taptest_table_p10000_p18000_p18400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p18000_p18500', ARRAY[100], 'Check count from id_taptest_table_p10000_p18000_p18500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p18000_p18600', ARRAY[100], 'Check count from id_taptest_table_p10000_p18000_p18600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p18000_p18700', ARRAY[100], 'Check count from id_taptest_table_p10000_p18000_p18700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p18000_p18800', ARRAY[100], 'Check count from id_taptest_table_p10000_p18000_p18800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p18000_p18900', ARRAY[100], 'Check count from id_taptest_table_p10000_p18000_p18900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p19000_p19000', ARRAY[100], 'Check count from id_taptest_table_p10000_p19000_p19000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p19000_p19100', ARRAY[100], 'Check count from id_taptest_table_p10000_p19000_p19100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p19000_p19200', ARRAY[100], 'Check count from id_taptest_table_p10000_p19000_p19200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p19000_p19300', ARRAY[100], 'Check count from id_taptest_table_p10000_p19000_p19300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p19000_p19400', ARRAY[100], 'Check count from id_taptest_table_p10000_p19000_p19400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p19000_p19500', ARRAY[100], 'Check count from id_taptest_table_p10000_p19000_p19500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p19000_p19600', ARRAY[100], 'Check count from id_taptest_table_p10000_p19000_p19600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p19000_p19700', ARRAY[100], 'Check count from id_taptest_table_p10000_p19000_p19700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p19000_p19800', ARRAY[100], 'Check count from id_taptest_table_p10000_p19000_p19800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10000_p19000_p19900', ARRAY[100], 'Check count from id_taptest_table_p10000_p19000_p19900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p20000_p20000', ARRAY[100], 'Check count from id_taptest_table_p20000_p20000_p20000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p20000_p20100', ARRAY[100], 'Check count from id_taptest_table_p20000_p20000_p20100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p20000_p20200', ARRAY[100], 'Check count from id_taptest_table_p20000_p20000_p20200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p20000_p20300', ARRAY[100], 'Check count from id_taptest_table_p20000_p20000_p20300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p20000_p20400', ARRAY[100], 'Check count from id_taptest_table_p20000_p20000_p20400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p20000_p20500', ARRAY[100], 'Check count from id_taptest_table_p20000_p20000_p20500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p20000_p20600', ARRAY[100], 'Check count from id_taptest_table_p20000_p20000_p20600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p20000_p20700', ARRAY[100], 'Check count from id_taptest_table_p20000_p20000_p20700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p20000_p20800', ARRAY[100], 'Check count from id_taptest_table_p20000_p20000_p20800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p20000_p20900', ARRAY[100], 'Check count from id_taptest_table_p20000_p20000_p20900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p21000_p21000', ARRAY[100], 'Check count from id_taptest_table_p20000_p21000_p21000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p21000_p21100', ARRAY[100], 'Check count from id_taptest_table_p20000_p21000_p21100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p21000_p21200', ARRAY[100], 'Check count from id_taptest_table_p20000_p21000_p21200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p21000_p21300', ARRAY[100], 'Check count from id_taptest_table_p20000_p21000_p21300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p21000_p21400', ARRAY[100], 'Check count from id_taptest_table_p20000_p21000_p21400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p21000_p21500', ARRAY[100], 'Check count from id_taptest_table_p20000_p21000_p21500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p21000_p21600', ARRAY[100], 'Check count from id_taptest_table_p20000_p21000_p21600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p21000_p21700', ARRAY[100], 'Check count from id_taptest_table_p20000_p21000_p21700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p21000_p21800', ARRAY[100], 'Check count from id_taptest_table_p20000_p21000_p21800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p21000_p21900', ARRAY[100], 'Check count from id_taptest_table_p20000_p21000_p21900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p22000_p22000', ARRAY[100], 'Check count from id_taptest_table_p20000_p22000_p22000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p22000_p22100', ARRAY[100], 'Check count from id_taptest_table_p20000_p22000_p22100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p22000_p22200', ARRAY[100], 'Check count from id_taptest_table_p20000_p22000_p22200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p22000_p22300', ARRAY[100], 'Check count from id_taptest_table_p20000_p22000_p22300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p22000_p22400', ARRAY[100], 'Check count from id_taptest_table_p20000_p22000_p22400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p22000_p22500', ARRAY[100], 'Check count from id_taptest_table_p20000_p22000_p22500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p22000_p22600', ARRAY[100], 'Check count from id_taptest_table_p20000_p22000_p22600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p22000_p22700', ARRAY[100], 'Check count from id_taptest_table_p20000_p22000_p22700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p22000_p22800', ARRAY[100], 'Check count from id_taptest_table_p20000_p22000_p22800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p22000_p22900', ARRAY[100], 'Check count from id_taptest_table_p20000_p22000_p22900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p23000_p23000', ARRAY[100], 'Check count from id_taptest_table_p20000_p23000_p23000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p23000_p23100', ARRAY[100], 'Check count from id_taptest_table_p20000_p23000_p23100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p23000_p23200', ARRAY[100], 'Check count from id_taptest_table_p20000_p23000_p23200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p23000_p23300', ARRAY[100], 'Check count from id_taptest_table_p20000_p23000_p23300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p23000_p23400', ARRAY[100], 'Check count from id_taptest_table_p20000_p23000_p23400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p23000_p23500', ARRAY[100], 'Check count from id_taptest_table_p20000_p23000_p23500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p23000_p23600', ARRAY[100], 'Check count from id_taptest_table_p20000_p23000_p23600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p23000_p23700', ARRAY[100], 'Check count from id_taptest_table_p20000_p23000_p23700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p23000_p23800', ARRAY[100], 'Check count from id_taptest_table_p20000_p23000_p23800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p23000_p23900', ARRAY[100], 'Check count from id_taptest_table_p20000_p23000_p23900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p24000_p24000', ARRAY[100], 'Check count from id_taptest_table_p20000_p24000_p24000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p24000_p24100', ARRAY[100], 'Check count from id_taptest_table_p20000_p24000_p24100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p24000_p24200', ARRAY[100], 'Check count from id_taptest_table_p20000_p24000_p24200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p24000_p24300', ARRAY[100], 'Check count from id_taptest_table_p20000_p24000_p24300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p24000_p24400', ARRAY[100], 'Check count from id_taptest_table_p20000_p24000_p24400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p24000_p24500', ARRAY[100], 'Check count from id_taptest_table_p20000_p24000_p24500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p24000_p24600', ARRAY[100], 'Check count from id_taptest_table_p20000_p24000_p24600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p24000_p24700', ARRAY[100], 'Check count from id_taptest_table_p20000_p24000_p24700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p24000_p24800', ARRAY[100], 'Check count from id_taptest_table_p20000_p24000_p24800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p24000_p24900', ARRAY[100], 'Check count from id_taptest_table_p20000_p24000_p24900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p25000_p25000', ARRAY[100], 'Check count from id_taptest_table_p20000_p25000_p25000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p25000_p25100', ARRAY[100], 'Check count from id_taptest_table_p20000_p25000_p25100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p25000_p25200', ARRAY[100], 'Check count from id_taptest_table_p20000_p25000_p25200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p25000_p25300', ARRAY[100], 'Check count from id_taptest_table_p20000_p25000_p25300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p25000_p25400', ARRAY[100], 'Check count from id_taptest_table_p20000_p25000_p25400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p25000_p25500', ARRAY[100], 'Check count from id_taptest_table_p20000_p25000_p25500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p25000_p25600', ARRAY[100], 'Check count from id_taptest_table_p20000_p25000_p25600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p25000_p25700', ARRAY[100], 'Check count from id_taptest_table_p20000_p25000_p25700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p25000_p25800', ARRAY[100], 'Check count from id_taptest_table_p20000_p25000_p25800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p25000_p25900', ARRAY[100], 'Check count from id_taptest_table_p20000_p25000_p25900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p26000_p26000', ARRAY[100], 'Check count from id_taptest_table_p20000_p26000_p26000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p26000_p26100', ARRAY[100], 'Check count from id_taptest_table_p20000_p26000_p26100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p26000_p26200', ARRAY[100], 'Check count from id_taptest_table_p20000_p26000_p26200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p26000_p26300', ARRAY[100], 'Check count from id_taptest_table_p20000_p26000_p26300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p26000_p26400', ARRAY[100], 'Check count from id_taptest_table_p20000_p26000_p26400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p26000_p26500', ARRAY[100], 'Check count from id_taptest_table_p20000_p26000_p26500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p26000_p26600', ARRAY[100], 'Check count from id_taptest_table_p20000_p26000_p26600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p26000_p26700', ARRAY[100], 'Check count from id_taptest_table_p20000_p26000_p26700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p26000_p26800', ARRAY[100], 'Check count from id_taptest_table_p20000_p26000_p26800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p26000_p26900', ARRAY[100], 'Check count from id_taptest_table_p20000_p26000_p26900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p27000_p27000', ARRAY[100], 'Check count from id_taptest_table_p20000_p27000_p27000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p27000_p27100', ARRAY[100], 'Check count from id_taptest_table_p20000_p27000_p27100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p27000_p27200', ARRAY[100], 'Check count from id_taptest_table_p20000_p27000_p27200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p27000_p27300', ARRAY[100], 'Check count from id_taptest_table_p20000_p27000_p27300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p27000_p27400', ARRAY[100], 'Check count from id_taptest_table_p20000_p27000_p27400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p27000_p27500', ARRAY[100], 'Check count from id_taptest_table_p20000_p27000_p27500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p27000_p27600', ARRAY[100], 'Check count from id_taptest_table_p20000_p27000_p27600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p27000_p27700', ARRAY[100], 'Check count from id_taptest_table_p20000_p27000_p27700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p27000_p27800', ARRAY[100], 'Check count from id_taptest_table_p20000_p27000_p27800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p27000_p27900', ARRAY[100], 'Check count from id_taptest_table_p20000_p27000_p27900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p28000_p28000', ARRAY[100], 'Check count from id_taptest_table_p20000_p28000_p28000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p28000_p28100', ARRAY[100], 'Check count from id_taptest_table_p20000_p28000_p28100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p28000_p28200', ARRAY[100], 'Check count from id_taptest_table_p20000_p28000_p28200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p28000_p28300', ARRAY[100], 'Check count from id_taptest_table_p20000_p28000_p28300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p28000_p28400', ARRAY[100], 'Check count from id_taptest_table_p20000_p28000_p28400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p28000_p28500', ARRAY[100], 'Check count from id_taptest_table_p20000_p28000_p28500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p28000_p28600', ARRAY[100], 'Check count from id_taptest_table_p20000_p28000_p28600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p28000_p28700', ARRAY[100], 'Check count from id_taptest_table_p20000_p28000_p28700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p28000_p28800', ARRAY[100], 'Check count from id_taptest_table_p20000_p28000_p28800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p28000_p28900', ARRAY[100], 'Check count from id_taptest_table_p20000_p28000_p28900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p29000_p29000', ARRAY[100], 'Check count from id_taptest_table_p20000_p29000_p29000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p29000_p29100', ARRAY[100], 'Check count from id_taptest_table_p20000_p29000_p29100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p29000_p29200', ARRAY[100], 'Check count from id_taptest_table_p20000_p29000_p29200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p29000_p29300', ARRAY[100], 'Check count from id_taptest_table_p20000_p29000_p29300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p29000_p29400', ARRAY[100], 'Check count from id_taptest_table_p20000_p29000_p29400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p29000_p29500', ARRAY[100], 'Check count from id_taptest_table_p20000_p29000_p29500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p29000_p29600', ARRAY[100], 'Check count from id_taptest_table_p20000_p29000_p29600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p29000_p29700', ARRAY[100], 'Check count from id_taptest_table_p20000_p29000_p29700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p29000_p29800', ARRAY[100], 'Check count from id_taptest_table_p20000_p29000_p29800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20000_p29000_p29900', ARRAY[100], 'Check count from id_taptest_table_p20000_p29000_p29900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p30000_p30000', ARRAY[100], 'Check count from id_taptest_table_p30000_p30000_p30000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p30000_p30100', ARRAY[100], 'Check count from id_taptest_table_p30000_p30000_p30100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p30000_p30200', ARRAY[100], 'Check count from id_taptest_table_p30000_p30000_p30200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p30000_p30300', ARRAY[100], 'Check count from id_taptest_table_p30000_p30000_p30300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p30000_p30400', ARRAY[100], 'Check count from id_taptest_table_p30000_p30000_p30400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p30000_p30500', ARRAY[100], 'Check count from id_taptest_table_p30000_p30000_p30500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p30000_p30600', ARRAY[100], 'Check count from id_taptest_table_p30000_p30000_p30600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p30000_p30700', ARRAY[100], 'Check count from id_taptest_table_p30000_p30000_p30700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p30000_p30800', ARRAY[100], 'Check count from id_taptest_table_p30000_p30000_p30800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p30000_p30900', ARRAY[100], 'Check count from id_taptest_table_p30000_p30000_p30900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p31000_p31000', ARRAY[100], 'Check count from id_taptest_table_p30000_p31000_p31000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p31000_p31100', ARRAY[100], 'Check count from id_taptest_table_p30000_p31000_p31100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p31000_p31200', ARRAY[100], 'Check count from id_taptest_table_p30000_p31000_p31200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p31000_p31300', ARRAY[100], 'Check count from id_taptest_table_p30000_p31000_p31300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p31000_p31400', ARRAY[100], 'Check count from id_taptest_table_p30000_p31000_p31400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p31000_p31500', ARRAY[100], 'Check count from id_taptest_table_p30000_p31000_p31500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p31000_p31600', ARRAY[100], 'Check count from id_taptest_table_p30000_p31000_p31600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p31000_p31700', ARRAY[100], 'Check count from id_taptest_table_p30000_p31000_p31700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p31000_p31800', ARRAY[100], 'Check count from id_taptest_table_p30000_p31000_p31800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p31000_p31900', ARRAY[100], 'Check count from id_taptest_table_p30000_p31000_p31900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p32000_p32000', ARRAY[100], 'Check count from id_taptest_table_p30000_p32000_p32000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p32000_p32100', ARRAY[100], 'Check count from id_taptest_table_p30000_p32000_p32100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p32000_p32200', ARRAY[100], 'Check count from id_taptest_table_p30000_p32000_p32200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p32000_p32300', ARRAY[100], 'Check count from id_taptest_table_p30000_p32000_p32300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p32000_p32400', ARRAY[100], 'Check count from id_taptest_table_p30000_p32000_p32400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p32000_p32500', ARRAY[100], 'Check count from id_taptest_table_p30000_p32000_p32500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p32000_p32600', ARRAY[100], 'Check count from id_taptest_table_p30000_p32000_p32600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p32000_p32700', ARRAY[100], 'Check count from id_taptest_table_p30000_p32000_p32700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p32000_p32800', ARRAY[100], 'Check count from id_taptest_table_p30000_p32000_p32800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p32000_p32900', ARRAY[100], 'Check count from id_taptest_table_p30000_p32000_p32900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p33000_p33000', ARRAY[100], 'Check count from id_taptest_table_p30000_p33000_p33000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p33000_p33100', ARRAY[100], 'Check count from id_taptest_table_p30000_p33000_p33100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p33000_p33200', ARRAY[100], 'Check count from id_taptest_table_p30000_p33000_p33200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p33000_p33300', ARRAY[100], 'Check count from id_taptest_table_p30000_p33000_p33300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p33000_p33400', ARRAY[100], 'Check count from id_taptest_table_p30000_p33000_p33400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p33000_p33500', ARRAY[100], 'Check count from id_taptest_table_p30000_p33000_p33500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p33000_p33600', ARRAY[100], 'Check count from id_taptest_table_p30000_p33000_p33600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p33000_p33700', ARRAY[100], 'Check count from id_taptest_table_p30000_p33000_p33700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p33000_p33800', ARRAY[100], 'Check count from id_taptest_table_p30000_p33000_p33800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p33000_p33900', ARRAY[100], 'Check count from id_taptest_table_p30000_p33000_p33900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p34000_p34000', ARRAY[100], 'Check count from id_taptest_table_p30000_p34000_p34000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p34000_p34100', ARRAY[100], 'Check count from id_taptest_table_p30000_p34000_p34100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p34000_p34200', ARRAY[100], 'Check count from id_taptest_table_p30000_p34000_p34200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p34000_p34300', ARRAY[100], 'Check count from id_taptest_table_p30000_p34000_p34300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p34000_p34400', ARRAY[100], 'Check count from id_taptest_table_p30000_p34000_p34400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p34000_p34500', ARRAY[100], 'Check count from id_taptest_table_p30000_p34000_p34500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p34000_p34600', ARRAY[100], 'Check count from id_taptest_table_p30000_p34000_p34600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p34000_p34700', ARRAY[100], 'Check count from id_taptest_table_p30000_p34000_p34700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p34000_p34800', ARRAY[100], 'Check count from id_taptest_table_p30000_p34000_p34800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p34000_p34900', ARRAY[100], 'Check count from id_taptest_table_p30000_p34000_p34900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p35000_p35000', ARRAY[100], 'Check count from id_taptest_table_p30000_p35000_p35000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p35000_p35100', ARRAY[100], 'Check count from id_taptest_table_p30000_p35000_p35100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p35000_p35200', ARRAY[100], 'Check count from id_taptest_table_p30000_p35000_p35200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p35000_p35300', ARRAY[100], 'Check count from id_taptest_table_p30000_p35000_p35300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p35000_p35400', ARRAY[100], 'Check count from id_taptest_table_p30000_p35000_p35400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p35000_p35500', ARRAY[100], 'Check count from id_taptest_table_p30000_p35000_p35500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p35000_p35600', ARRAY[100], 'Check count from id_taptest_table_p30000_p35000_p35600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p35000_p35700', ARRAY[100], 'Check count from id_taptest_table_p30000_p35000_p35700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p35000_p35800', ARRAY[100], 'Check count from id_taptest_table_p30000_p35000_p35800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p35000_p35900', ARRAY[100], 'Check count from id_taptest_table_p30000_p35000_p35900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p36000_p36000', ARRAY[100], 'Check count from id_taptest_table_p30000_p36000_p36000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p36000_p36100', ARRAY[100], 'Check count from id_taptest_table_p30000_p36000_p36100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p36000_p36200', ARRAY[100], 'Check count from id_taptest_table_p30000_p36000_p36200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p36000_p36300', ARRAY[100], 'Check count from id_taptest_table_p30000_p36000_p36300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p36000_p36400', ARRAY[100], 'Check count from id_taptest_table_p30000_p36000_p36400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p36000_p36500', ARRAY[100], 'Check count from id_taptest_table_p30000_p36000_p36500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p36000_p36600', ARRAY[100], 'Check count from id_taptest_table_p30000_p36000_p36600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p36000_p36700', ARRAY[100], 'Check count from id_taptest_table_p30000_p36000_p36700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p36000_p36800', ARRAY[100], 'Check count from id_taptest_table_p30000_p36000_p36800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p36000_p36900', ARRAY[100], 'Check count from id_taptest_table_p30000_p36000_p36900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p37000_p37000', ARRAY[100], 'Check count from id_taptest_table_p30000_p37000_p37000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p37000_p37100', ARRAY[100], 'Check count from id_taptest_table_p30000_p37000_p37100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p37000_p37200', ARRAY[100], 'Check count from id_taptest_table_p30000_p37000_p37200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p37000_p37300', ARRAY[100], 'Check count from id_taptest_table_p30000_p37000_p37300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p37000_p37400', ARRAY[100], 'Check count from id_taptest_table_p30000_p37000_p37400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p37000_p37500', ARRAY[100], 'Check count from id_taptest_table_p30000_p37000_p37500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p37000_p37600', ARRAY[100], 'Check count from id_taptest_table_p30000_p37000_p37600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p37000_p37700', ARRAY[100], 'Check count from id_taptest_table_p30000_p37000_p37700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p37000_p37800', ARRAY[100], 'Check count from id_taptest_table_p30000_p37000_p37800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p37000_p37900', ARRAY[100], 'Check count from id_taptest_table_p30000_p37000_p37900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p38000_p38000', ARRAY[100], 'Check count from id_taptest_table_p30000_p38000_p38000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p38000_p38100', ARRAY[100], 'Check count from id_taptest_table_p30000_p38000_p38100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p38000_p38200', ARRAY[100], 'Check count from id_taptest_table_p30000_p38000_p38200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p38000_p38300', ARRAY[100], 'Check count from id_taptest_table_p30000_p38000_p38300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p38000_p38400', ARRAY[100], 'Check count from id_taptest_table_p30000_p38000_p38400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p38000_p38500', ARRAY[100], 'Check count from id_taptest_table_p30000_p38000_p38500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p38000_p38600', ARRAY[100], 'Check count from id_taptest_table_p30000_p38000_p38600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p38000_p38700', ARRAY[100], 'Check count from id_taptest_table_p30000_p38000_p38700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p38000_p38800', ARRAY[100], 'Check count from id_taptest_table_p30000_p38000_p38800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p38000_p38900', ARRAY[100], 'Check count from id_taptest_table_p30000_p38000_p38900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p39000_p39000', ARRAY[100], 'Check count from id_taptest_table_p30000_p39000_p39000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p39000_p39100', ARRAY[100], 'Check count from id_taptest_table_p30000_p39000_p39100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p39000_p39200', ARRAY[100], 'Check count from id_taptest_table_p30000_p39000_p39200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p39000_p39300', ARRAY[100], 'Check count from id_taptest_table_p30000_p39000_p39300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p39000_p39400', ARRAY[100], 'Check count from id_taptest_table_p30000_p39000_p39400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p39000_p39500', ARRAY[100], 'Check count from id_taptest_table_p30000_p39000_p39500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p39000_p39600', ARRAY[100], 'Check count from id_taptest_table_p30000_p39000_p39600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p39000_p39700', ARRAY[100], 'Check count from id_taptest_table_p30000_p39000_p39700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p39000_p39800', ARRAY[100], 'Check count from id_taptest_table_p30000_p39000_p39800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30000_p39000_p39900', ARRAY[100], 'Check count from id_taptest_table_p30000_p39000_p39900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p40000_p40000', ARRAY[100], 'Check count from id_taptest_table_p40000_p40000_p40000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p40000_p40100', ARRAY[100], 'Check count from id_taptest_table_p40000_p40000_p40100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p40000_p40200', ARRAY[100], 'Check count from id_taptest_table_p40000_p40000_p40200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p40000_p40300', ARRAY[100], 'Check count from id_taptest_table_p40000_p40000_p40300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p40000_p40400', ARRAY[100], 'Check count from id_taptest_table_p40000_p40000_p40400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p40000_p40500', ARRAY[100], 'Check count from id_taptest_table_p40000_p40000_p40500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p40000_p40600', ARRAY[100], 'Check count from id_taptest_table_p40000_p40000_p40600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p40000_p40700', ARRAY[100], 'Check count from id_taptest_table_p40000_p40000_p40700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p40000_p40800', ARRAY[100], 'Check count from id_taptest_table_p40000_p40000_p40800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p40000_p40900', ARRAY[100], 'Check count from id_taptest_table_p40000_p40000_p40900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p41000_p41000', ARRAY[100], 'Check count from id_taptest_table_p40000_p41000_p41000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p41000_p41100', ARRAY[100], 'Check count from id_taptest_table_p40000_p41000_p41100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p41000_p41200', ARRAY[100], 'Check count from id_taptest_table_p40000_p41000_p41200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p41000_p41300', ARRAY[100], 'Check count from id_taptest_table_p40000_p41000_p41300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p41000_p41400', ARRAY[100], 'Check count from id_taptest_table_p40000_p41000_p41400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p41000_p41500', ARRAY[100], 'Check count from id_taptest_table_p40000_p41000_p41500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p41000_p41600', ARRAY[100], 'Check count from id_taptest_table_p40000_p41000_p41600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p41000_p41700', ARRAY[100], 'Check count from id_taptest_table_p40000_p41000_p41700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p41000_p41800', ARRAY[100], 'Check count from id_taptest_table_p40000_p41000_p41800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p41000_p41900', ARRAY[100], 'Check count from id_taptest_table_p40000_p41000_p41900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p42000_p42000', ARRAY[100], 'Check count from id_taptest_table_p40000_p42000_p42000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p42000_p42100', ARRAY[100], 'Check count from id_taptest_table_p40000_p42000_p42100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p42000_p42200', ARRAY[100], 'Check count from id_taptest_table_p40000_p42000_p42200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p42000_p42300', ARRAY[100], 'Check count from id_taptest_table_p40000_p42000_p42300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p42000_p42400', ARRAY[100], 'Check count from id_taptest_table_p40000_p42000_p42400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p42000_p42500', ARRAY[100], 'Check count from id_taptest_table_p40000_p42000_p42500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p42000_p42600', ARRAY[100], 'Check count from id_taptest_table_p40000_p42000_p42600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p42000_p42700', ARRAY[100], 'Check count from id_taptest_table_p40000_p42000_p42700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p42000_p42800', ARRAY[100], 'Check count from id_taptest_table_p40000_p42000_p42800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p42000_p42900', ARRAY[100], 'Check count from id_taptest_table_p40000_p42000_p42900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p43000_p43000', ARRAY[100], 'Check count from id_taptest_table_p40000_p43000_p43000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p43000_p43100', ARRAY[100], 'Check count from id_taptest_table_p40000_p43000_p43100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p43000_p43200', ARRAY[100], 'Check count from id_taptest_table_p40000_p43000_p43200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p43000_p43300', ARRAY[100], 'Check count from id_taptest_table_p40000_p43000_p43300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p43000_p43400', ARRAY[100], 'Check count from id_taptest_table_p40000_p43000_p43400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p43000_p43500', ARRAY[100], 'Check count from id_taptest_table_p40000_p43000_p43500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p43000_p43600', ARRAY[100], 'Check count from id_taptest_table_p40000_p43000_p43600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p43000_p43700', ARRAY[100], 'Check count from id_taptest_table_p40000_p43000_p43700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p43000_p43800', ARRAY[100], 'Check count from id_taptest_table_p40000_p43000_p43800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p43000_p43900', ARRAY[100], 'Check count from id_taptest_table_p40000_p43000_p43900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p44000_p44000', ARRAY[100], 'Check count from id_taptest_table_p40000_p44000_p44000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p44000_p44100', ARRAY[100], 'Check count from id_taptest_table_p40000_p44000_p44100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p44000_p44200', ARRAY[100], 'Check count from id_taptest_table_p40000_p44000_p44200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p44000_p44300', ARRAY[100], 'Check count from id_taptest_table_p40000_p44000_p44300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p44000_p44400', ARRAY[100], 'Check count from id_taptest_table_p40000_p44000_p44400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p44000_p44500', ARRAY[100], 'Check count from id_taptest_table_p40000_p44000_p44500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p44000_p44600', ARRAY[100], 'Check count from id_taptest_table_p40000_p44000_p44600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p44000_p44700', ARRAY[100], 'Check count from id_taptest_table_p40000_p44000_p44700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p44000_p44800', ARRAY[100], 'Check count from id_taptest_table_p40000_p44000_p44800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p44000_p44900', ARRAY[100], 'Check count from id_taptest_table_p40000_p44000_p44900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p45000_p45000', ARRAY[100], 'Check count from id_taptest_table_p40000_p45000_p45000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p45000_p45100', ARRAY[100], 'Check count from id_taptest_table_p40000_p45000_p45100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p45000_p45200', ARRAY[100], 'Check count from id_taptest_table_p40000_p45000_p45200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p45000_p45300', ARRAY[100], 'Check count from id_taptest_table_p40000_p45000_p45300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p45000_p45400', ARRAY[100], 'Check count from id_taptest_table_p40000_p45000_p45400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p45000_p45500', ARRAY[100], 'Check count from id_taptest_table_p40000_p45000_p45500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p45000_p45600', ARRAY[100], 'Check count from id_taptest_table_p40000_p45000_p45600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p45000_p45700', ARRAY[100], 'Check count from id_taptest_table_p40000_p45000_p45700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p45000_p45800', ARRAY[100], 'Check count from id_taptest_table_p40000_p45000_p45800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p45000_p45900', ARRAY[100], 'Check count from id_taptest_table_p40000_p45000_p45900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p46000_p46000', ARRAY[100], 'Check count from id_taptest_table_p40000_p46000_p46000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p46000_p46100', ARRAY[100], 'Check count from id_taptest_table_p40000_p46000_p46100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p46000_p46200', ARRAY[100], 'Check count from id_taptest_table_p40000_p46000_p46200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p46000_p46300', ARRAY[100], 'Check count from id_taptest_table_p40000_p46000_p46300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p46000_p46400', ARRAY[100], 'Check count from id_taptest_table_p40000_p46000_p46400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p46000_p46500', ARRAY[100], 'Check count from id_taptest_table_p40000_p46000_p46500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p46000_p46600', ARRAY[100], 'Check count from id_taptest_table_p40000_p46000_p46600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p46000_p46700', ARRAY[100], 'Check count from id_taptest_table_p40000_p46000_p46700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p46000_p46800', ARRAY[100], 'Check count from id_taptest_table_p40000_p46000_p46800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p46000_p46900', ARRAY[100], 'Check count from id_taptest_table_p40000_p46000_p46900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p47000_p47000', ARRAY[100], 'Check count from id_taptest_table_p40000_p47000_p47000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p47000_p47100', ARRAY[100], 'Check count from id_taptest_table_p40000_p47000_p47100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p47000_p47200', ARRAY[100], 'Check count from id_taptest_table_p40000_p47000_p47200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p47000_p47300', ARRAY[100], 'Check count from id_taptest_table_p40000_p47000_p47300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p47000_p47400', ARRAY[100], 'Check count from id_taptest_table_p40000_p47000_p47400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p47000_p47500', ARRAY[100], 'Check count from id_taptest_table_p40000_p47000_p47500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p47000_p47600', ARRAY[100], 'Check count from id_taptest_table_p40000_p47000_p47600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p47000_p47700', ARRAY[100], 'Check count from id_taptest_table_p40000_p47000_p47700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p47000_p47800', ARRAY[100], 'Check count from id_taptest_table_p40000_p47000_p47800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p47000_p47900', ARRAY[100], 'Check count from id_taptest_table_p40000_p47000_p47900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p48000_p48000', ARRAY[100], 'Check count from id_taptest_table_p40000_p48000_p48000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p48000_p48100', ARRAY[100], 'Check count from id_taptest_table_p40000_p48000_p48100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p48000_p48200', ARRAY[100], 'Check count from id_taptest_table_p40000_p48000_p48200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p48000_p48300', ARRAY[100], 'Check count from id_taptest_table_p40000_p48000_p48300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p48000_p48400', ARRAY[100], 'Check count from id_taptest_table_p40000_p48000_p48400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p48000_p48500', ARRAY[100], 'Check count from id_taptest_table_p40000_p48000_p48500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p48000_p48600', ARRAY[100], 'Check count from id_taptest_table_p40000_p48000_p48600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p48000_p48700', ARRAY[100], 'Check count from id_taptest_table_p40000_p48000_p48700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p48000_p48800', ARRAY[100], 'Check count from id_taptest_table_p40000_p48000_p48800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p48000_p48900', ARRAY[100], 'Check count from id_taptest_table_p40000_p48000_p48900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p49000_p49000', ARRAY[100], 'Check count from id_taptest_table_p40000_p49000_p49000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p49000_p49100', ARRAY[100], 'Check count from id_taptest_table_p40000_p49000_p49100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p49000_p49200', ARRAY[100], 'Check count from id_taptest_table_p40000_p49000_p49200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p49000_p49300', ARRAY[100], 'Check count from id_taptest_table_p40000_p49000_p49300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p49000_p49400', ARRAY[100], 'Check count from id_taptest_table_p40000_p49000_p49400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p49000_p49500', ARRAY[100], 'Check count from id_taptest_table_p40000_p49000_p49500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p49000_p49600', ARRAY[100], 'Check count from id_taptest_table_p40000_p49000_p49600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p49000_p49700', ARRAY[100], 'Check count from id_taptest_table_p40000_p49000_p49700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p49000_p49800', ARRAY[100], 'Check count from id_taptest_table_p40000_p49000_p49800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p40000_p49000_p49900', ARRAY[100], 'Check count from id_taptest_table_p40000_p49000_p49900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p50000_p50000', ARRAY[100], 'Check count from id_taptest_table_p50000_p50000_p50000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p50000_p50100', ARRAY[100], 'Check count from id_taptest_table_p50000_p50000_p50100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p50000_p50200', ARRAY[100], 'Check count from id_taptest_table_p50000_p50000_p50200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p50000_p50300', ARRAY[100], 'Check count from id_taptest_table_p50000_p50000_p50300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p50000_p50400', ARRAY[100], 'Check count from id_taptest_table_p50000_p50000_p50400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p50000_p50500', ARRAY[100], 'Check count from id_taptest_table_p50000_p50000_p50500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p50000_p50600', ARRAY[100], 'Check count from id_taptest_table_p50000_p50000_p50600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p50000_p50700', ARRAY[100], 'Check count from id_taptest_table_p50000_p50000_p50700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p50000_p50800', ARRAY[100], 'Check count from id_taptest_table_p50000_p50000_p50800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p50000_p50900', ARRAY[100], 'Check count from id_taptest_table_p50000_p50000_p50900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p51000_p51000', ARRAY[100], 'Check count from id_taptest_table_p50000_p51000_p51000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p51000_p51100', ARRAY[100], 'Check count from id_taptest_table_p50000_p51000_p51100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p51000_p51200', ARRAY[100], 'Check count from id_taptest_table_p50000_p51000_p51200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p51000_p51300', ARRAY[100], 'Check count from id_taptest_table_p50000_p51000_p51300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p51000_p51400', ARRAY[100], 'Check count from id_taptest_table_p50000_p51000_p51400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p51000_p51500', ARRAY[100], 'Check count from id_taptest_table_p50000_p51000_p51500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p51000_p51600', ARRAY[100], 'Check count from id_taptest_table_p50000_p51000_p51600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p51000_p51700', ARRAY[100], 'Check count from id_taptest_table_p50000_p51000_p51700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p51000_p51800', ARRAY[100], 'Check count from id_taptest_table_p50000_p51000_p51800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p51000_p51900', ARRAY[100], 'Check count from id_taptest_table_p50000_p51000_p51900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p52000_p52000', ARRAY[100], 'Check count from id_taptest_table_p50000_p52000_p52000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p52000_p52100', ARRAY[100], 'Check count from id_taptest_table_p50000_p52000_p52100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p52000_p52200', ARRAY[100], 'Check count from id_taptest_table_p50000_p52000_p52200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p52000_p52300', ARRAY[100], 'Check count from id_taptest_table_p50000_p52000_p52300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p52000_p52400', ARRAY[100], 'Check count from id_taptest_table_p50000_p52000_p52400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p52000_p52500', ARRAY[100], 'Check count from id_taptest_table_p50000_p52000_p52500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p52000_p52600', ARRAY[100], 'Check count from id_taptest_table_p50000_p52000_p52600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p52000_p52700', ARRAY[100], 'Check count from id_taptest_table_p50000_p52000_p52700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p52000_p52800', ARRAY[100], 'Check count from id_taptest_table_p50000_p52000_p52800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p52000_p52900', ARRAY[100], 'Check count from id_taptest_table_p50000_p52000_p52900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p53000_p53000', ARRAY[100], 'Check count from id_taptest_table_p50000_p53000_p53000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p53000_p53100', ARRAY[100], 'Check count from id_taptest_table_p50000_p53000_p53100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p53000_p53200', ARRAY[100], 'Check count from id_taptest_table_p50000_p53000_p53200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p53000_p53300', ARRAY[100], 'Check count from id_taptest_table_p50000_p53000_p53300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p53000_p53400', ARRAY[100], 'Check count from id_taptest_table_p50000_p53000_p53400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p53000_p53500', ARRAY[100], 'Check count from id_taptest_table_p50000_p53000_p53500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p53000_p53600', ARRAY[100], 'Check count from id_taptest_table_p50000_p53000_p53600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p53000_p53700', ARRAY[100], 'Check count from id_taptest_table_p50000_p53000_p53700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p53000_p53800', ARRAY[100], 'Check count from id_taptest_table_p50000_p53000_p53800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p53000_p53900', ARRAY[100], 'Check count from id_taptest_table_p50000_p53000_p53900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p54000_p54000', ARRAY[100], 'Check count from id_taptest_table_p50000_p54000_p54000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p54000_p54100', ARRAY[100], 'Check count from id_taptest_table_p50000_p54000_p54100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p54000_p54200', ARRAY[100], 'Check count from id_taptest_table_p50000_p54000_p54200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p54000_p54300', ARRAY[100], 'Check count from id_taptest_table_p50000_p54000_p54300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p54000_p54400', ARRAY[100], 'Check count from id_taptest_table_p50000_p54000_p54400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p54000_p54500', ARRAY[100], 'Check count from id_taptest_table_p50000_p54000_p54500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p54000_p54600', ARRAY[100], 'Check count from id_taptest_table_p50000_p54000_p54600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p54000_p54700', ARRAY[100], 'Check count from id_taptest_table_p50000_p54000_p54700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p54000_p54800', ARRAY[100], 'Check count from id_taptest_table_p50000_p54000_p54800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p54000_p54900', ARRAY[100], 'Check count from id_taptest_table_p50000_p54000_p54900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p55000_p55000', ARRAY[100], 'Check count from id_taptest_table_p50000_p55000_p55000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p55000_p55100', ARRAY[100], 'Check count from id_taptest_table_p50000_p55000_p55100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p55000_p55200', ARRAY[100], 'Check count from id_taptest_table_p50000_p55000_p55200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p55000_p55300', ARRAY[100], 'Check count from id_taptest_table_p50000_p55000_p55300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p55000_p55400', ARRAY[100], 'Check count from id_taptest_table_p50000_p55000_p55400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p55000_p55500', ARRAY[100], 'Check count from id_taptest_table_p50000_p55000_p55500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p55000_p55600', ARRAY[100], 'Check count from id_taptest_table_p50000_p55000_p55600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p55000_p55700', ARRAY[100], 'Check count from id_taptest_table_p50000_p55000_p55700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p55000_p55800', ARRAY[100], 'Check count from id_taptest_table_p50000_p55000_p55800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p55000_p55900', ARRAY[100], 'Check count from id_taptest_table_p50000_p55000_p55900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p56000_p56000', ARRAY[100], 'Check count from id_taptest_table_p50000_p56000_p56000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p56000_p56100', ARRAY[100], 'Check count from id_taptest_table_p50000_p56000_p56100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p56000_p56200', ARRAY[100], 'Check count from id_taptest_table_p50000_p56000_p56200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p56000_p56300', ARRAY[100], 'Check count from id_taptest_table_p50000_p56000_p56300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p56000_p56400', ARRAY[100], 'Check count from id_taptest_table_p50000_p56000_p56400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p56000_p56500', ARRAY[100], 'Check count from id_taptest_table_p50000_p56000_p56500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p56000_p56600', ARRAY[100], 'Check count from id_taptest_table_p50000_p56000_p56600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p56000_p56700', ARRAY[100], 'Check count from id_taptest_table_p50000_p56000_p56700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p56000_p56800', ARRAY[100], 'Check count from id_taptest_table_p50000_p56000_p56800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p56000_p56900', ARRAY[100], 'Check count from id_taptest_table_p50000_p56000_p56900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p57000_p57000', ARRAY[100], 'Check count from id_taptest_table_p50000_p57000_p57000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p57000_p57100', ARRAY[100], 'Check count from id_taptest_table_p50000_p57000_p57100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p57000_p57200', ARRAY[100], 'Check count from id_taptest_table_p50000_p57000_p57200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p57000_p57300', ARRAY[100], 'Check count from id_taptest_table_p50000_p57000_p57300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p57000_p57400', ARRAY[100], 'Check count from id_taptest_table_p50000_p57000_p57400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p57000_p57500', ARRAY[100], 'Check count from id_taptest_table_p50000_p57000_p57500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p57000_p57600', ARRAY[100], 'Check count from id_taptest_table_p50000_p57000_p57600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p57000_p57700', ARRAY[100], 'Check count from id_taptest_table_p50000_p57000_p57700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p57000_p57800', ARRAY[100], 'Check count from id_taptest_table_p50000_p57000_p57800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p57000_p57900', ARRAY[100], 'Check count from id_taptest_table_p50000_p57000_p57900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p58000_p58000', ARRAY[100], 'Check count from id_taptest_table_p50000_p58000_p58000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p58000_p58100', ARRAY[100], 'Check count from id_taptest_table_p50000_p58000_p58100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p58000_p58200', ARRAY[100], 'Check count from id_taptest_table_p50000_p58000_p58200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p58000_p58300', ARRAY[100], 'Check count from id_taptest_table_p50000_p58000_p58300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p58000_p58400', ARRAY[100], 'Check count from id_taptest_table_p50000_p58000_p58400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p58000_p58500', ARRAY[100], 'Check count from id_taptest_table_p50000_p58000_p58500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p58000_p58600', ARRAY[100], 'Check count from id_taptest_table_p50000_p58000_p58600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p58000_p58700', ARRAY[100], 'Check count from id_taptest_table_p50000_p58000_p58700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p58000_p58800', ARRAY[100], 'Check count from id_taptest_table_p50000_p58000_p58800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p58000_p58900', ARRAY[100], 'Check count from id_taptest_table_p50000_p58000_p58900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p59000_p59000', ARRAY[100], 'Check count from id_taptest_table_p50000_p59000_p59000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p59000_p59100', ARRAY[100], 'Check count from id_taptest_table_p50000_p59000_p59100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p59000_p59200', ARRAY[100], 'Check count from id_taptest_table_p50000_p59000_p59200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p59000_p59300', ARRAY[100], 'Check count from id_taptest_table_p50000_p59000_p59300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p59000_p59400', ARRAY[100], 'Check count from id_taptest_table_p50000_p59000_p59400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p59000_p59500', ARRAY[100], 'Check count from id_taptest_table_p50000_p59000_p59500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p59000_p59600', ARRAY[100], 'Check count from id_taptest_table_p50000_p59000_p59600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p59000_p59700', ARRAY[100], 'Check count from id_taptest_table_p50000_p59000_p59700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p59000_p59800', ARRAY[100], 'Check count from id_taptest_table_p50000_p59000_p59800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p50000_p59000_p59900', ARRAY[100], 'Check count from id_taptest_table_p50000_p59000_p59900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p60000_p60000', ARRAY[100], 'Check count from id_taptest_table_p60000_p60000_p60000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p60000_p60100', ARRAY[100], 'Check count from id_taptest_table_p60000_p60000_p60100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p60000_p60200', ARRAY[100], 'Check count from id_taptest_table_p60000_p60000_p60200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p60000_p60300', ARRAY[100], 'Check count from id_taptest_table_p60000_p60000_p60300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p60000_p60400', ARRAY[100], 'Check count from id_taptest_table_p60000_p60000_p60400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p60000_p60500', ARRAY[100], 'Check count from id_taptest_table_p60000_p60000_p60500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p60000_p60600', ARRAY[100], 'Check count from id_taptest_table_p60000_p60000_p60600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p60000_p60700', ARRAY[100], 'Check count from id_taptest_table_p60000_p60000_p60700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p60000_p60800', ARRAY[100], 'Check count from id_taptest_table_p60000_p60000_p60800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p60000_p60900', ARRAY[100], 'Check count from id_taptest_table_p60000_p60000_p60900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p61000_p61000', ARRAY[100], 'Check count from id_taptest_table_p60000_p61000_p61000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p61000_p61100', ARRAY[100], 'Check count from id_taptest_table_p60000_p61000_p61100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p61000_p61200', ARRAY[100], 'Check count from id_taptest_table_p60000_p61000_p61200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p61000_p61300', ARRAY[100], 'Check count from id_taptest_table_p60000_p61000_p61300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p61000_p61400', ARRAY[100], 'Check count from id_taptest_table_p60000_p61000_p61400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p61000_p61500', ARRAY[100], 'Check count from id_taptest_table_p60000_p61000_p61500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p61000_p61600', ARRAY[100], 'Check count from id_taptest_table_p60000_p61000_p61600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p61000_p61700', ARRAY[100], 'Check count from id_taptest_table_p60000_p61000_p61700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p61000_p61800', ARRAY[100], 'Check count from id_taptest_table_p60000_p61000_p61800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p61000_p61900', ARRAY[100], 'Check count from id_taptest_table_p60000_p61000_p61900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p62000_p62000', ARRAY[100], 'Check count from id_taptest_table_p60000_p62000_p62000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p62000_p62100', ARRAY[100], 'Check count from id_taptest_table_p60000_p62000_p62100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p62000_p62200', ARRAY[100], 'Check count from id_taptest_table_p60000_p62000_p62200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p62000_p62300', ARRAY[100], 'Check count from id_taptest_table_p60000_p62000_p62300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p62000_p62400', ARRAY[100], 'Check count from id_taptest_table_p60000_p62000_p62400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p62000_p62500', ARRAY[100], 'Check count from id_taptest_table_p60000_p62000_p62500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p62000_p62600', ARRAY[100], 'Check count from id_taptest_table_p60000_p62000_p62600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p62000_p62700', ARRAY[100], 'Check count from id_taptest_table_p60000_p62000_p62700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p62000_p62800', ARRAY[100], 'Check count from id_taptest_table_p60000_p62000_p62800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p62000_p62900', ARRAY[100], 'Check count from id_taptest_table_p60000_p62000_p62900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p63000_p63000', ARRAY[100], 'Check count from id_taptest_table_p60000_p63000_p63000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p63000_p63100', ARRAY[100], 'Check count from id_taptest_table_p60000_p63000_p63100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p63000_p63200', ARRAY[100], 'Check count from id_taptest_table_p60000_p63000_p63200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p63000_p63300', ARRAY[100], 'Check count from id_taptest_table_p60000_p63000_p63300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p63000_p63400', ARRAY[100], 'Check count from id_taptest_table_p60000_p63000_p63400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p63000_p63500', ARRAY[100], 'Check count from id_taptest_table_p60000_p63000_p63500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p63000_p63600', ARRAY[100], 'Check count from id_taptest_table_p60000_p63000_p63600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p63000_p63700', ARRAY[100], 'Check count from id_taptest_table_p60000_p63000_p63700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p63000_p63800', ARRAY[100], 'Check count from id_taptest_table_p60000_p63000_p63800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p63000_p63900', ARRAY[100], 'Check count from id_taptest_table_p60000_p63000_p63900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p64000_p64000', ARRAY[100], 'Check count from id_taptest_table_p60000_p64000_p64000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p64000_p64100', ARRAY[100], 'Check count from id_taptest_table_p60000_p64000_p64100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p64000_p64200', ARRAY[100], 'Check count from id_taptest_table_p60000_p64000_p64200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p64000_p64300', ARRAY[100], 'Check count from id_taptest_table_p60000_p64000_p64300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p64000_p64400', ARRAY[100], 'Check count from id_taptest_table_p60000_p64000_p64400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p64000_p64500', ARRAY[100], 'Check count from id_taptest_table_p60000_p64000_p64500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p64000_p64600', ARRAY[100], 'Check count from id_taptest_table_p60000_p64000_p64600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p64000_p64700', ARRAY[100], 'Check count from id_taptest_table_p60000_p64000_p64700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p64000_p64800', ARRAY[100], 'Check count from id_taptest_table_p60000_p64000_p64800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p64000_p64900', ARRAY[100], 'Check count from id_taptest_table_p60000_p64000_p64900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p65000_p65000', ARRAY[100], 'Check count from id_taptest_table_p60000_p65000_p65000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p65000_p65100', ARRAY[100], 'Check count from id_taptest_table_p60000_p65000_p65100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p65000_p65200', ARRAY[100], 'Check count from id_taptest_table_p60000_p65000_p65200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p65000_p65300', ARRAY[100], 'Check count from id_taptest_table_p60000_p65000_p65300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p65000_p65400', ARRAY[100], 'Check count from id_taptest_table_p60000_p65000_p65400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p65000_p65500', ARRAY[100], 'Check count from id_taptest_table_p60000_p65000_p65500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p65000_p65600', ARRAY[100], 'Check count from id_taptest_table_p60000_p65000_p65600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p65000_p65700', ARRAY[100], 'Check count from id_taptest_table_p60000_p65000_p65700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p65000_p65800', ARRAY[100], 'Check count from id_taptest_table_p60000_p65000_p65800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p65000_p65900', ARRAY[100], 'Check count from id_taptest_table_p60000_p65000_p65900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p66000_p66000', ARRAY[100], 'Check count from id_taptest_table_p60000_p66000_p66000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p66000_p66100', ARRAY[100], 'Check count from id_taptest_table_p60000_p66000_p66100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p66000_p66200', ARRAY[100], 'Check count from id_taptest_table_p60000_p66000_p66200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p66000_p66300', ARRAY[100], 'Check count from id_taptest_table_p60000_p66000_p66300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p66000_p66400', ARRAY[100], 'Check count from id_taptest_table_p60000_p66000_p66400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p66000_p66500', ARRAY[100], 'Check count from id_taptest_table_p60000_p66000_p66500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p66000_p66600', ARRAY[100], 'Check count from id_taptest_table_p60000_p66000_p66600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p66000_p66700', ARRAY[100], 'Check count from id_taptest_table_p60000_p66000_p66700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p66000_p66800', ARRAY[100], 'Check count from id_taptest_table_p60000_p66000_p66800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p66000_p66900', ARRAY[100], 'Check count from id_taptest_table_p60000_p66000_p66900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p67000_p67000', ARRAY[100], 'Check count from id_taptest_table_p60000_p67000_p67000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p67000_p67100', ARRAY[100], 'Check count from id_taptest_table_p60000_p67000_p67100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p67000_p67200', ARRAY[100], 'Check count from id_taptest_table_p60000_p67000_p67200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p67000_p67300', ARRAY[100], 'Check count from id_taptest_table_p60000_p67000_p67300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p67000_p67400', ARRAY[100], 'Check count from id_taptest_table_p60000_p67000_p67400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p67000_p67500', ARRAY[100], 'Check count from id_taptest_table_p60000_p67000_p67500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p67000_p67600', ARRAY[100], 'Check count from id_taptest_table_p60000_p67000_p67600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p67000_p67700', ARRAY[100], 'Check count from id_taptest_table_p60000_p67000_p67700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p67000_p67800', ARRAY[100], 'Check count from id_taptest_table_p60000_p67000_p67800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p67000_p67900', ARRAY[100], 'Check count from id_taptest_table_p60000_p67000_p67900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p68000_p68000', ARRAY[100], 'Check count from id_taptest_table_p60000_p68000_p68000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p68000_p68100', ARRAY[100], 'Check count from id_taptest_table_p60000_p68000_p68100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p68000_p68200', ARRAY[100], 'Check count from id_taptest_table_p60000_p68000_p68200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p68000_p68300', ARRAY[100], 'Check count from id_taptest_table_p60000_p68000_p68300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p68000_p68400', ARRAY[100], 'Check count from id_taptest_table_p60000_p68000_p68400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p68000_p68500', ARRAY[100], 'Check count from id_taptest_table_p60000_p68000_p68500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p68000_p68600', ARRAY[100], 'Check count from id_taptest_table_p60000_p68000_p68600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p68000_p68700', ARRAY[100], 'Check count from id_taptest_table_p60000_p68000_p68700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p68000_p68800', ARRAY[100], 'Check count from id_taptest_table_p60000_p68000_p68800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p68000_p68900', ARRAY[100], 'Check count from id_taptest_table_p60000_p68000_p68900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p69000_p69000', ARRAY[100], 'Check count from id_taptest_table_p60000_p69000_p69000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p69000_p69100', ARRAY[100], 'Check count from id_taptest_table_p60000_p69000_p69100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p69000_p69200', ARRAY[100], 'Check count from id_taptest_table_p60000_p69000_p69200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p69000_p69300', ARRAY[100], 'Check count from id_taptest_table_p60000_p69000_p69300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p69000_p69400', ARRAY[100], 'Check count from id_taptest_table_p60000_p69000_p69400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p69000_p69500', ARRAY[100], 'Check count from id_taptest_table_p60000_p69000_p69500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p69000_p69600', ARRAY[100], 'Check count from id_taptest_table_p60000_p69000_p69600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p69000_p69700', ARRAY[100], 'Check count from id_taptest_table_p60000_p69000_p69700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p69000_p69800', ARRAY[100], 'Check count from id_taptest_table_p60000_p69000_p69800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p60000_p69000_p69900', ARRAY[100], 'Check count from id_taptest_table_p60000_p69000_p69900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p70000_p70000', ARRAY[100], 'Check count from id_taptest_table_p70000_p70000_p70000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p70000_p70100', ARRAY[100], 'Check count from id_taptest_table_p70000_p70000_p70100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p70000_p70200', ARRAY[100], 'Check count from id_taptest_table_p70000_p70000_p70200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p70000_p70300', ARRAY[100], 'Check count from id_taptest_table_p70000_p70000_p70300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p70000_p70400', ARRAY[100], 'Check count from id_taptest_table_p70000_p70000_p70400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p70000_p70500', ARRAY[100], 'Check count from id_taptest_table_p70000_p70000_p70500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p70000_p70600', ARRAY[100], 'Check count from id_taptest_table_p70000_p70000_p70600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p70000_p70700', ARRAY[100], 'Check count from id_taptest_table_p70000_p70000_p70700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p70000_p70800', ARRAY[100], 'Check count from id_taptest_table_p70000_p70000_p70800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p70000_p70900', ARRAY[100], 'Check count from id_taptest_table_p70000_p70000_p70900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p71000_p71000', ARRAY[100], 'Check count from id_taptest_table_p70000_p71000_p71000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p71000_p71100', ARRAY[100], 'Check count from id_taptest_table_p70000_p71000_p71100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p71000_p71200', ARRAY[100], 'Check count from id_taptest_table_p70000_p71000_p71200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p71000_p71300', ARRAY[100], 'Check count from id_taptest_table_p70000_p71000_p71300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p71000_p71400', ARRAY[100], 'Check count from id_taptest_table_p70000_p71000_p71400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p71000_p71500', ARRAY[100], 'Check count from id_taptest_table_p70000_p71000_p71500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p71000_p71600', ARRAY[100], 'Check count from id_taptest_table_p70000_p71000_p71600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p71000_p71700', ARRAY[100], 'Check count from id_taptest_table_p70000_p71000_p71700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p71000_p71800', ARRAY[100], 'Check count from id_taptest_table_p70000_p71000_p71800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p71000_p71900', ARRAY[100], 'Check count from id_taptest_table_p70000_p71000_p71900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p72000_p72000', ARRAY[100], 'Check count from id_taptest_table_p70000_p72000_p72000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p72000_p72100', ARRAY[100], 'Check count from id_taptest_table_p70000_p72000_p72100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p72000_p72200', ARRAY[100], 'Check count from id_taptest_table_p70000_p72000_p72200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p72000_p72300', ARRAY[100], 'Check count from id_taptest_table_p70000_p72000_p72300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p72000_p72400', ARRAY[100], 'Check count from id_taptest_table_p70000_p72000_p72400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p72000_p72500', ARRAY[100], 'Check count from id_taptest_table_p70000_p72000_p72500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p72000_p72600', ARRAY[100], 'Check count from id_taptest_table_p70000_p72000_p72600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p72000_p72700', ARRAY[100], 'Check count from id_taptest_table_p70000_p72000_p72700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p72000_p72800', ARRAY[100], 'Check count from id_taptest_table_p70000_p72000_p72800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p72000_p72900', ARRAY[100], 'Check count from id_taptest_table_p70000_p72000_p72900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p73000_p73000', ARRAY[100], 'Check count from id_taptest_table_p70000_p73000_p73000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p73000_p73100', ARRAY[100], 'Check count from id_taptest_table_p70000_p73000_p73100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p73000_p73200', ARRAY[100], 'Check count from id_taptest_table_p70000_p73000_p73200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p73000_p73300', ARRAY[100], 'Check count from id_taptest_table_p70000_p73000_p73300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p73000_p73400', ARRAY[100], 'Check count from id_taptest_table_p70000_p73000_p73400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p73000_p73500', ARRAY[100], 'Check count from id_taptest_table_p70000_p73000_p73500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p73000_p73600', ARRAY[100], 'Check count from id_taptest_table_p70000_p73000_p73600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p73000_p73700', ARRAY[100], 'Check count from id_taptest_table_p70000_p73000_p73700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p73000_p73800', ARRAY[100], 'Check count from id_taptest_table_p70000_p73000_p73800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p73000_p73900', ARRAY[100], 'Check count from id_taptest_table_p70000_p73000_p73900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p74000_p74000', ARRAY[100], 'Check count from id_taptest_table_p70000_p74000_p74000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p74000_p74100', ARRAY[100], 'Check count from id_taptest_table_p70000_p74000_p74100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p74000_p74200', ARRAY[100], 'Check count from id_taptest_table_p70000_p74000_p74200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p74000_p74300', ARRAY[100], 'Check count from id_taptest_table_p70000_p74000_p74300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p74000_p74400', ARRAY[100], 'Check count from id_taptest_table_p70000_p74000_p74400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p74000_p74500', ARRAY[100], 'Check count from id_taptest_table_p70000_p74000_p74500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p74000_p74600', ARRAY[100], 'Check count from id_taptest_table_p70000_p74000_p74600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p74000_p74700', ARRAY[100], 'Check count from id_taptest_table_p70000_p74000_p74700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p74000_p74800', ARRAY[100], 'Check count from id_taptest_table_p70000_p74000_p74800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p74000_p74900', ARRAY[100], 'Check count from id_taptest_table_p70000_p74000_p74900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p75000_p75000', ARRAY[100], 'Check count from id_taptest_table_p70000_p75000_p75000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p75000_p75100', ARRAY[100], 'Check count from id_taptest_table_p70000_p75000_p75100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p75000_p75200', ARRAY[100], 'Check count from id_taptest_table_p70000_p75000_p75200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p75000_p75300', ARRAY[100], 'Check count from id_taptest_table_p70000_p75000_p75300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p75000_p75400', ARRAY[100], 'Check count from id_taptest_table_p70000_p75000_p75400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p75000_p75500', ARRAY[100], 'Check count from id_taptest_table_p70000_p75000_p75500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p75000_p75600', ARRAY[100], 'Check count from id_taptest_table_p70000_p75000_p75600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p75000_p75700', ARRAY[100], 'Check count from id_taptest_table_p70000_p75000_p75700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p75000_p75800', ARRAY[100], 'Check count from id_taptest_table_p70000_p75000_p75800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p75000_p75900', ARRAY[100], 'Check count from id_taptest_table_p70000_p75000_p75900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p76000_p76000', ARRAY[100], 'Check count from id_taptest_table_p70000_p76000_p76000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p76000_p76100', ARRAY[100], 'Check count from id_taptest_table_p70000_p76000_p76100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p76000_p76200', ARRAY[100], 'Check count from id_taptest_table_p70000_p76000_p76200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p76000_p76300', ARRAY[100], 'Check count from id_taptest_table_p70000_p76000_p76300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p76000_p76400', ARRAY[100], 'Check count from id_taptest_table_p70000_p76000_p76400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p76000_p76500', ARRAY[100], 'Check count from id_taptest_table_p70000_p76000_p76500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p76000_p76600', ARRAY[100], 'Check count from id_taptest_table_p70000_p76000_p76600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p76000_p76700', ARRAY[100], 'Check count from id_taptest_table_p70000_p76000_p76700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p76000_p76800', ARRAY[100], 'Check count from id_taptest_table_p70000_p76000_p76800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p76000_p76900', ARRAY[100], 'Check count from id_taptest_table_p70000_p76000_p76900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p77000_p77000', ARRAY[100], 'Check count from id_taptest_table_p70000_p77000_p77000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p77000_p77100', ARRAY[100], 'Check count from id_taptest_table_p70000_p77000_p77100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p77000_p77200', ARRAY[100], 'Check count from id_taptest_table_p70000_p77000_p77200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p77000_p77300', ARRAY[100], 'Check count from id_taptest_table_p70000_p77000_p77300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p77000_p77400', ARRAY[100], 'Check count from id_taptest_table_p70000_p77000_p77400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p77000_p77500', ARRAY[100], 'Check count from id_taptest_table_p70000_p77000_p77500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p77000_p77600', ARRAY[100], 'Check count from id_taptest_table_p70000_p77000_p77600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p77000_p77700', ARRAY[100], 'Check count from id_taptest_table_p70000_p77000_p77700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p77000_p77800', ARRAY[100], 'Check count from id_taptest_table_p70000_p77000_p77800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p77000_p77900', ARRAY[100], 'Check count from id_taptest_table_p70000_p77000_p77900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p78000_p78000', ARRAY[100], 'Check count from id_taptest_table_p70000_p78000_p78000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p78000_p78100', ARRAY[100], 'Check count from id_taptest_table_p70000_p78000_p78100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p78000_p78200', ARRAY[100], 'Check count from id_taptest_table_p70000_p78000_p78200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p78000_p78300', ARRAY[100], 'Check count from id_taptest_table_p70000_p78000_p78300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p78000_p78400', ARRAY[100], 'Check count from id_taptest_table_p70000_p78000_p78400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p78000_p78500', ARRAY[100], 'Check count from id_taptest_table_p70000_p78000_p78500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p78000_p78600', ARRAY[100], 'Check count from id_taptest_table_p70000_p78000_p78600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p78000_p78700', ARRAY[100], 'Check count from id_taptest_table_p70000_p78000_p78700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p78000_p78800', ARRAY[100], 'Check count from id_taptest_table_p70000_p78000_p78800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p78000_p78900', ARRAY[100], 'Check count from id_taptest_table_p70000_p78000_p78900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p79000_p79000', ARRAY[100], 'Check count from id_taptest_table_p70000_p79000_p79000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p79000_p79100', ARRAY[100], 'Check count from id_taptest_table_p70000_p79000_p79100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p79000_p79200', ARRAY[100], 'Check count from id_taptest_table_p70000_p79000_p79200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p79000_p79300', ARRAY[100], 'Check count from id_taptest_table_p70000_p79000_p79300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p79000_p79400', ARRAY[100], 'Check count from id_taptest_table_p70000_p79000_p79400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p79000_p79500', ARRAY[100], 'Check count from id_taptest_table_p70000_p79000_p79500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p79000_p79600', ARRAY[100], 'Check count from id_taptest_table_p70000_p79000_p79600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p79000_p79700', ARRAY[100], 'Check count from id_taptest_table_p70000_p79000_p79700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p79000_p79800', ARRAY[100], 'Check count from id_taptest_table_p70000_p79000_p79800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p70000_p79000_p79900', ARRAY[100], 'Check count from id_taptest_table_p70000_p79000_p79900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p80000_p80000', ARRAY[100], 'Check count from id_taptest_table_p80000_p80000_p80000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p80000_p80100', ARRAY[100], 'Check count from id_taptest_table_p80000_p80000_p80100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p80000_p80200', ARRAY[100], 'Check count from id_taptest_table_p80000_p80000_p80200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p80000_p80300', ARRAY[100], 'Check count from id_taptest_table_p80000_p80000_p80300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p80000_p80400', ARRAY[100], 'Check count from id_taptest_table_p80000_p80000_p80400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p80000_p80500', ARRAY[100], 'Check count from id_taptest_table_p80000_p80000_p80500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p80000_p80600', ARRAY[100], 'Check count from id_taptest_table_p80000_p80000_p80600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p80000_p80700', ARRAY[100], 'Check count from id_taptest_table_p80000_p80000_p80700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p80000_p80800', ARRAY[100], 'Check count from id_taptest_table_p80000_p80000_p80800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p80000_p80900', ARRAY[100], 'Check count from id_taptest_table_p80000_p80000_p80900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p81000_p81000', ARRAY[100], 'Check count from id_taptest_table_p80000_p81000_p81000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p81000_p81100', ARRAY[100], 'Check count from id_taptest_table_p80000_p81000_p81100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p81000_p81200', ARRAY[100], 'Check count from id_taptest_table_p80000_p81000_p81200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p81000_p81300', ARRAY[100], 'Check count from id_taptest_table_p80000_p81000_p81300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p81000_p81400', ARRAY[100], 'Check count from id_taptest_table_p80000_p81000_p81400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p81000_p81500', ARRAY[100], 'Check count from id_taptest_table_p80000_p81000_p81500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p81000_p81600', ARRAY[100], 'Check count from id_taptest_table_p80000_p81000_p81600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p81000_p81700', ARRAY[100], 'Check count from id_taptest_table_p80000_p81000_p81700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p81000_p81800', ARRAY[100], 'Check count from id_taptest_table_p80000_p81000_p81800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p81000_p81900', ARRAY[100], 'Check count from id_taptest_table_p80000_p81000_p81900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p82000_p82000', ARRAY[100], 'Check count from id_taptest_table_p80000_p82000_p82000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p82000_p82100', ARRAY[100], 'Check count from id_taptest_table_p80000_p82000_p82100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p82000_p82200', ARRAY[100], 'Check count from id_taptest_table_p80000_p82000_p82200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p82000_p82300', ARRAY[100], 'Check count from id_taptest_table_p80000_p82000_p82300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p82000_p82400', ARRAY[100], 'Check count from id_taptest_table_p80000_p82000_p82400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p82000_p82500', ARRAY[100], 'Check count from id_taptest_table_p80000_p82000_p82500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p82000_p82600', ARRAY[100], 'Check count from id_taptest_table_p80000_p82000_p82600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p82000_p82700', ARRAY[100], 'Check count from id_taptest_table_p80000_p82000_p82700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p82000_p82800', ARRAY[100], 'Check count from id_taptest_table_p80000_p82000_p82800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p82000_p82900', ARRAY[100], 'Check count from id_taptest_table_p80000_p82000_p82900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p83000_p83000', ARRAY[100], 'Check count from id_taptest_table_p80000_p83000_p83000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p83000_p83100', ARRAY[100], 'Check count from id_taptest_table_p80000_p83000_p83100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p83000_p83200', ARRAY[100], 'Check count from id_taptest_table_p80000_p83000_p83200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p83000_p83300', ARRAY[100], 'Check count from id_taptest_table_p80000_p83000_p83300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p83000_p83400', ARRAY[100], 'Check count from id_taptest_table_p80000_p83000_p83400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p83000_p83500', ARRAY[100], 'Check count from id_taptest_table_p80000_p83000_p83500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p83000_p83600', ARRAY[100], 'Check count from id_taptest_table_p80000_p83000_p83600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p83000_p83700', ARRAY[100], 'Check count from id_taptest_table_p80000_p83000_p83700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p83000_p83800', ARRAY[100], 'Check count from id_taptest_table_p80000_p83000_p83800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p83000_p83900', ARRAY[100], 'Check count from id_taptest_table_p80000_p83000_p83900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p84000_p84000', ARRAY[100], 'Check count from id_taptest_table_p80000_p84000_p84000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p84000_p84100', ARRAY[100], 'Check count from id_taptest_table_p80000_p84000_p84100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p84000_p84200', ARRAY[100], 'Check count from id_taptest_table_p80000_p84000_p84200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p84000_p84300', ARRAY[100], 'Check count from id_taptest_table_p80000_p84000_p84300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p84000_p84400', ARRAY[100], 'Check count from id_taptest_table_p80000_p84000_p84400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p84000_p84500', ARRAY[100], 'Check count from id_taptest_table_p80000_p84000_p84500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p84000_p84600', ARRAY[100], 'Check count from id_taptest_table_p80000_p84000_p84600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p84000_p84700', ARRAY[100], 'Check count from id_taptest_table_p80000_p84000_p84700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p84000_p84800', ARRAY[100], 'Check count from id_taptest_table_p80000_p84000_p84800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p84000_p84900', ARRAY[100], 'Check count from id_taptest_table_p80000_p84000_p84900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p85000_p85000', ARRAY[100], 'Check count from id_taptest_table_p80000_p85000_p85000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p85000_p85100', ARRAY[100], 'Check count from id_taptest_table_p80000_p85000_p85100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p85000_p85200', ARRAY[100], 'Check count from id_taptest_table_p80000_p85000_p85200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p85000_p85300', ARRAY[100], 'Check count from id_taptest_table_p80000_p85000_p85300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p85000_p85400', ARRAY[100], 'Check count from id_taptest_table_p80000_p85000_p85400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p85000_p85500', ARRAY[100], 'Check count from id_taptest_table_p80000_p85000_p85500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p85000_p85600', ARRAY[100], 'Check count from id_taptest_table_p80000_p85000_p85600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p85000_p85700', ARRAY[100], 'Check count from id_taptest_table_p80000_p85000_p85700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p85000_p85800', ARRAY[100], 'Check count from id_taptest_table_p80000_p85000_p85800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p85000_p85900', ARRAY[100], 'Check count from id_taptest_table_p80000_p85000_p85900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p86000_p86000', ARRAY[100], 'Check count from id_taptest_table_p80000_p86000_p86000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p86000_p86100', ARRAY[100], 'Check count from id_taptest_table_p80000_p86000_p86100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p86000_p86200', ARRAY[100], 'Check count from id_taptest_table_p80000_p86000_p86200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p86000_p86300', ARRAY[100], 'Check count from id_taptest_table_p80000_p86000_p86300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p86000_p86400', ARRAY[100], 'Check count from id_taptest_table_p80000_p86000_p86400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p86000_p86500', ARRAY[100], 'Check count from id_taptest_table_p80000_p86000_p86500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p86000_p86600', ARRAY[100], 'Check count from id_taptest_table_p80000_p86000_p86600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p86000_p86700', ARRAY[100], 'Check count from id_taptest_table_p80000_p86000_p86700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p86000_p86800', ARRAY[100], 'Check count from id_taptest_table_p80000_p86000_p86800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p86000_p86900', ARRAY[100], 'Check count from id_taptest_table_p80000_p86000_p86900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p87000_p87000', ARRAY[100], 'Check count from id_taptest_table_p80000_p87000_p87000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p87000_p87100', ARRAY[100], 'Check count from id_taptest_table_p80000_p87000_p87100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p87000_p87200', ARRAY[100], 'Check count from id_taptest_table_p80000_p87000_p87200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p87000_p87300', ARRAY[100], 'Check count from id_taptest_table_p80000_p87000_p87300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p87000_p87400', ARRAY[100], 'Check count from id_taptest_table_p80000_p87000_p87400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p87000_p87500', ARRAY[100], 'Check count from id_taptest_table_p80000_p87000_p87500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p87000_p87600', ARRAY[100], 'Check count from id_taptest_table_p80000_p87000_p87600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p87000_p87700', ARRAY[100], 'Check count from id_taptest_table_p80000_p87000_p87700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p87000_p87800', ARRAY[100], 'Check count from id_taptest_table_p80000_p87000_p87800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p87000_p87900', ARRAY[100], 'Check count from id_taptest_table_p80000_p87000_p87900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p88000_p88000', ARRAY[100], 'Check count from id_taptest_table_p80000_p88000_p88000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p88000_p88100', ARRAY[100], 'Check count from id_taptest_table_p80000_p88000_p88100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p88000_p88200', ARRAY[100], 'Check count from id_taptest_table_p80000_p88000_p88200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p88000_p88300', ARRAY[100], 'Check count from id_taptest_table_p80000_p88000_p88300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p88000_p88400', ARRAY[100], 'Check count from id_taptest_table_p80000_p88000_p88400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p88000_p88500', ARRAY[100], 'Check count from id_taptest_table_p80000_p88000_p88500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p88000_p88600', ARRAY[100], 'Check count from id_taptest_table_p80000_p88000_p88600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p88000_p88700', ARRAY[100], 'Check count from id_taptest_table_p80000_p88000_p88700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p88000_p88800', ARRAY[100], 'Check count from id_taptest_table_p80000_p88000_p88800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p88000_p88900', ARRAY[100], 'Check count from id_taptest_table_p80000_p88000_p88900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p89000_p89000', ARRAY[100], 'Check count from id_taptest_table_p80000_p89000_p89000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p89000_p89100', ARRAY[100], 'Check count from id_taptest_table_p80000_p89000_p89100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p89000_p89200', ARRAY[100], 'Check count from id_taptest_table_p80000_p89000_p89200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p89000_p89300', ARRAY[100], 'Check count from id_taptest_table_p80000_p89000_p89300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p89000_p89400', ARRAY[100], 'Check count from id_taptest_table_p80000_p89000_p89400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p89000_p89500', ARRAY[100], 'Check count from id_taptest_table_p80000_p89000_p89500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p89000_p89600', ARRAY[100], 'Check count from id_taptest_table_p80000_p89000_p89600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p89000_p89700', ARRAY[100], 'Check count from id_taptest_table_p80000_p89000_p89700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p89000_p89800', ARRAY[100], 'Check count from id_taptest_table_p80000_p89000_p89800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p80000_p89000_p89900', ARRAY[100], 'Check count from id_taptest_table_p80000_p89000_p89900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p90000_p90000', ARRAY[100], 'Check count from id_taptest_table_p90000_p90000_p90000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p90000_p90100', ARRAY[100], 'Check count from id_taptest_table_p90000_p90000_p90100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p90000_p90200', ARRAY[100], 'Check count from id_taptest_table_p90000_p90000_p90200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p90000_p90300', ARRAY[100], 'Check count from id_taptest_table_p90000_p90000_p90300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p90000_p90400', ARRAY[100], 'Check count from id_taptest_table_p90000_p90000_p90400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p90000_p90500', ARRAY[100], 'Check count from id_taptest_table_p90000_p90000_p90500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p90000_p90600', ARRAY[100], 'Check count from id_taptest_table_p90000_p90000_p90600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p90000_p90700', ARRAY[100], 'Check count from id_taptest_table_p90000_p90000_p90700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p90000_p90800', ARRAY[100], 'Check count from id_taptest_table_p90000_p90000_p90800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p90000_p90900', ARRAY[100], 'Check count from id_taptest_table_p90000_p90000_p90900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p91000_p91000', ARRAY[100], 'Check count from id_taptest_table_p90000_p91000_p91000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p91000_p91100', ARRAY[100], 'Check count from id_taptest_table_p90000_p91000_p91100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p91000_p91200', ARRAY[100], 'Check count from id_taptest_table_p90000_p91000_p91200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p91000_p91300', ARRAY[100], 'Check count from id_taptest_table_p90000_p91000_p91300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p91000_p91400', ARRAY[100], 'Check count from id_taptest_table_p90000_p91000_p91400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p91000_p91500', ARRAY[100], 'Check count from id_taptest_table_p90000_p91000_p91500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p91000_p91600', ARRAY[100], 'Check count from id_taptest_table_p90000_p91000_p91600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p91000_p91700', ARRAY[100], 'Check count from id_taptest_table_p90000_p91000_p91700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p91000_p91800', ARRAY[100], 'Check count from id_taptest_table_p90000_p91000_p91800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p91000_p91900', ARRAY[100], 'Check count from id_taptest_table_p90000_p91000_p91900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p92000_p92000', ARRAY[100], 'Check count from id_taptest_table_p90000_p92000_p92000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p92000_p92100', ARRAY[100], 'Check count from id_taptest_table_p90000_p92000_p92100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p92000_p92200', ARRAY[100], 'Check count from id_taptest_table_p90000_p92000_p92200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p92000_p92300', ARRAY[100], 'Check count from id_taptest_table_p90000_p92000_p92300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p92000_p92400', ARRAY[100], 'Check count from id_taptest_table_p90000_p92000_p92400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p92000_p92500', ARRAY[100], 'Check count from id_taptest_table_p90000_p92000_p92500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p92000_p92600', ARRAY[100], 'Check count from id_taptest_table_p90000_p92000_p92600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p92000_p92700', ARRAY[100], 'Check count from id_taptest_table_p90000_p92000_p92700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p92000_p92800', ARRAY[100], 'Check count from id_taptest_table_p90000_p92000_p92800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p92000_p92900', ARRAY[100], 'Check count from id_taptest_table_p90000_p92000_p92900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p93000_p93000', ARRAY[100], 'Check count from id_taptest_table_p90000_p93000_p93000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p93000_p93100', ARRAY[100], 'Check count from id_taptest_table_p90000_p93000_p93100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p93000_p93200', ARRAY[100], 'Check count from id_taptest_table_p90000_p93000_p93200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p93000_p93300', ARRAY[100], 'Check count from id_taptest_table_p90000_p93000_p93300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p93000_p93400', ARRAY[100], 'Check count from id_taptest_table_p90000_p93000_p93400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p93000_p93500', ARRAY[100], 'Check count from id_taptest_table_p90000_p93000_p93500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p93000_p93600', ARRAY[100], 'Check count from id_taptest_table_p90000_p93000_p93600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p93000_p93700', ARRAY[100], 'Check count from id_taptest_table_p90000_p93000_p93700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p93000_p93800', ARRAY[100], 'Check count from id_taptest_table_p90000_p93000_p93800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p93000_p93900', ARRAY[100], 'Check count from id_taptest_table_p90000_p93000_p93900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p94000_p94000', ARRAY[100], 'Check count from id_taptest_table_p90000_p94000_p94000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p94000_p94100', ARRAY[100], 'Check count from id_taptest_table_p90000_p94000_p94100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p94000_p94200', ARRAY[100], 'Check count from id_taptest_table_p90000_p94000_p94200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p94000_p94300', ARRAY[100], 'Check count from id_taptest_table_p90000_p94000_p94300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p94000_p94400', ARRAY[100], 'Check count from id_taptest_table_p90000_p94000_p94400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p94000_p94500', ARRAY[100], 'Check count from id_taptest_table_p90000_p94000_p94500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p94000_p94600', ARRAY[100], 'Check count from id_taptest_table_p90000_p94000_p94600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p94000_p94700', ARRAY[100], 'Check count from id_taptest_table_p90000_p94000_p94700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p94000_p94800', ARRAY[100], 'Check count from id_taptest_table_p90000_p94000_p94800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p94000_p94900', ARRAY[100], 'Check count from id_taptest_table_p90000_p94000_p94900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p95000_p95000', ARRAY[100], 'Check count from id_taptest_table_p90000_p95000_p95000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p95000_p95100', ARRAY[100], 'Check count from id_taptest_table_p90000_p95000_p95100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p95000_p95200', ARRAY[100], 'Check count from id_taptest_table_p90000_p95000_p95200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p95000_p95300', ARRAY[100], 'Check count from id_taptest_table_p90000_p95000_p95300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p95000_p95400', ARRAY[100], 'Check count from id_taptest_table_p90000_p95000_p95400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p95000_p95500', ARRAY[100], 'Check count from id_taptest_table_p90000_p95000_p95500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p95000_p95600', ARRAY[100], 'Check count from id_taptest_table_p90000_p95000_p95600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p95000_p95700', ARRAY[100], 'Check count from id_taptest_table_p90000_p95000_p95700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p95000_p95800', ARRAY[100], 'Check count from id_taptest_table_p90000_p95000_p95800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p95000_p95900', ARRAY[100], 'Check count from id_taptest_table_p90000_p95000_p95900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p96000_p96000', ARRAY[100], 'Check count from id_taptest_table_p90000_p96000_p96000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p96000_p96100', ARRAY[100], 'Check count from id_taptest_table_p90000_p96000_p96100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p96000_p96200', ARRAY[100], 'Check count from id_taptest_table_p90000_p96000_p96200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p96000_p96300', ARRAY[100], 'Check count from id_taptest_table_p90000_p96000_p96300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p96000_p96400', ARRAY[100], 'Check count from id_taptest_table_p90000_p96000_p96400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p96000_p96500', ARRAY[100], 'Check count from id_taptest_table_p90000_p96000_p96500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p96000_p96600', ARRAY[100], 'Check count from id_taptest_table_p90000_p96000_p96600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p96000_p96700', ARRAY[100], 'Check count from id_taptest_table_p90000_p96000_p96700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p96000_p96800', ARRAY[100], 'Check count from id_taptest_table_p90000_p96000_p96800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p96000_p96900', ARRAY[100], 'Check count from id_taptest_table_p90000_p96000_p96900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p97000_p97000', ARRAY[100], 'Check count from id_taptest_table_p90000_p97000_p97000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p97000_p97100', ARRAY[100], 'Check count from id_taptest_table_p90000_p97000_p97100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p97000_p97200', ARRAY[100], 'Check count from id_taptest_table_p90000_p97000_p97200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p97000_p97300', ARRAY[100], 'Check count from id_taptest_table_p90000_p97000_p97300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p97000_p97400', ARRAY[100], 'Check count from id_taptest_table_p90000_p97000_p97400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p97000_p97500', ARRAY[100], 'Check count from id_taptest_table_p90000_p97000_p97500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p97000_p97600', ARRAY[100], 'Check count from id_taptest_table_p90000_p97000_p97600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p97000_p97700', ARRAY[100], 'Check count from id_taptest_table_p90000_p97000_p97700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p97000_p97800', ARRAY[100], 'Check count from id_taptest_table_p90000_p97000_p97800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p97000_p97900', ARRAY[100], 'Check count from id_taptest_table_p90000_p97000_p97900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p98000_p98000', ARRAY[100], 'Check count from id_taptest_table_p90000_p98000_p98000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p98000_p98100', ARRAY[100], 'Check count from id_taptest_table_p90000_p98000_p98100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p98000_p98200', ARRAY[100], 'Check count from id_taptest_table_p90000_p98000_p98200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p98000_p98300', ARRAY[100], 'Check count from id_taptest_table_p90000_p98000_p98300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p98000_p98400', ARRAY[100], 'Check count from id_taptest_table_p90000_p98000_p98400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p98000_p98500', ARRAY[100], 'Check count from id_taptest_table_p90000_p98000_p98500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p98000_p98600', ARRAY[100], 'Check count from id_taptest_table_p90000_p98000_p98600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p98000_p98700', ARRAY[100], 'Check count from id_taptest_table_p90000_p98000_p98700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p98000_p98800', ARRAY[100], 'Check count from id_taptest_table_p90000_p98000_p98800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p98000_p98900', ARRAY[100], 'Check count from id_taptest_table_p90000_p98000_p98900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p99000_p99000', ARRAY[100], 'Check count from id_taptest_table_p90000_p99000_p99000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p99000_p99100', ARRAY[100], 'Check count from id_taptest_table_p90000_p99000_p99100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p99000_p99200', ARRAY[100], 'Check count from id_taptest_table_p90000_p99000_p99200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p99000_p99300', ARRAY[100], 'Check count from id_taptest_table_p90000_p99000_p99300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p99000_p99400', ARRAY[100], 'Check count from id_taptest_table_p90000_p99000_p99400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p99000_p99500', ARRAY[100], 'Check count from id_taptest_table_p90000_p99000_p99500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p99000_p99600', ARRAY[100], 'Check count from id_taptest_table_p90000_p99000_p99600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p99000_p99700', ARRAY[100], 'Check count from id_taptest_table_p90000_p99000_p99700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p99000_p99800', ARRAY[100], 'Check count from id_taptest_table_p90000_p99000_p99800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p90000_p99000_p99900', ARRAY[100], 'Check count from id_taptest_table_p90000_p99000_p99900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p100000_p100000', ARRAY[1], 'Check count from id_taptest_table_p100000_p100000_p100000'); -- INSERT batch 2 INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(100001, 100100)); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table partman_test.id_taptest_table is empty'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[100100], 'Check count from parent table partman_test.id_taptest_table (175000)'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p100000', 'Check that parent table partman_test.id_taptest_table_p100000 is empty'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p100000_p100000', 'Check that parent table partman_test.id_taptest_table_p100000_p100000 is empty'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p100000_p101000', 'Check that parent table partman_test.id_taptest_table_p100000_p101000 is empty'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p100000_p100000', ARRAY[100], 'Check count from id_taptest_table_p100000_p100000_p100000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p100000_p100100', ARRAY[1], 'Check count from id_taptest_table_p100000_p100000_p100100'); SELECT run_maintenance(); SELECT has_table('partman_test', 'id_taptest_table_p100000_p100000_p100300', 'Check id_taptest_table_p100000_p100000_p100300 exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p100000_p100000_p100400', 'Check id_taptest_table_p100000_p100000_p100400 does not exists'); UPDATE part_config SET premake = 11 WHERE parent_table LIKE 'partman_test.id_taptest_table%' AND partition_type = 'native'; SELECT run_maintenance(); INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(100101,107500)); -- Stuff likely went into some parent tables. It's diffcult to test a constant stream of input data to regularly run run_maintenence() against. SELECT results_eq('SELECT parent_table, count::int FROM check_parent() ORDER BY parent_table', $$VALUES ('partman_test.id_taptest_table_p100000_p101000', 900), ('partman_test.id_taptest_table_p100000_p102000', 900), ('partman_test.id_taptest_table_p100000_p103000', 900), ('partman_test.id_taptest_table_p100000_p104000', 900), ('partman_test.id_taptest_table_p100000_p105000', 900), ('partman_test.id_taptest_table_p100000_p106000', 900), ('partman_test.id_taptest_table_p100000_p107000', 401)$$, 'check_parent() run to see if stuff was put into parent tables'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p100000_p101000'', p_batch_count := 20)::int', ARRAY[900], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p100000_p101000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p100000_p102000'', p_batch_count := 20)::int', ARRAY[900], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p100000_p102000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p100000_p103000'', p_batch_count := 20)::int', ARRAY[900], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p100000_p103000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p100000_p104000'', p_batch_count := 20)::int', ARRAY[900], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p100000_p104000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p100000_p105000'', p_batch_count := 20)::int', ARRAY[900], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p100000_p105000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p100000_p106000'', p_batch_count := 20)::int', ARRAY[900], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p100000_p106000'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table_p100000_p107000'', p_batch_count := 20)::int', ARRAY[401], 'Check that partitioning function returns correct count of rows moved for id_taptest_table_p100000_p107000'); SELECT is_empty('SELECT * from check_parent()', 'check_parent() should return nothing now.'); -- Check for the rest of batch 2 tables now SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p100000_p100000', ARRAY[100], 'Check count fromid_taptest_table_p100000_p100000_p100000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p100000_p100100', ARRAY[100], 'Check count fromid_taptest_table_p100000_p100000_p100100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p100000_p100200', ARRAY[100], 'Check count fromid_taptest_table_p100000_p100000_p100200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p100000_p100300', ARRAY[100], 'Check count fromid_taptest_table_p100000_p100000_p100300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p100000_p100400', ARRAY[100], 'Check count fromid_taptest_table_p100000_p100000_p100400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p100000_p100500', ARRAY[100], 'Check count fromid_taptest_table_p100000_p100000_p100500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p100000_p100600', ARRAY[100], 'Check count fromid_taptest_table_p100000_p100000_p100600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p100000_p100700', ARRAY[100], 'Check count fromid_taptest_table_p100000_p100000_p100700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p100000_p100800', ARRAY[100], 'Check count fromid_taptest_table_p100000_p100000_p100800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p100000_p100900', ARRAY[100], 'Check count fromid_taptest_table_p100000_p100000_p100900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p101000_p101000', ARRAY[100], 'Check count fromid_taptest_table_p100000_p101000_p101000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p101000_p101100', ARRAY[100], 'Check count fromid_taptest_table_p100000_p101000_p101100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p101000_p101200', ARRAY[100], 'Check count fromid_taptest_table_p100000_p101000_p101200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p101000_p101300', ARRAY[100], 'Check count fromid_taptest_table_p100000_p101000_p101300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p101000_p101400', ARRAY[100], 'Check count fromid_taptest_table_p100000_p101000_p101400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p101000_p101500', ARRAY[100], 'Check count fromid_taptest_table_p100000_p101000_p101500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p101000_p101600', ARRAY[100], 'Check count fromid_taptest_table_p100000_p101000_p101600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p101000_p101700', ARRAY[100], 'Check count fromid_taptest_table_p100000_p101000_p101700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p101000_p101800', ARRAY[100], 'Check count fromid_taptest_table_p100000_p101000_p101800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p101000_p101900', ARRAY[100], 'Check count fromid_taptest_table_p100000_p101000_p101900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p102000_p102000', ARRAY[100], 'Check count fromid_taptest_table_p100000_p102000_p102000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p102000_p102100', ARRAY[100], 'Check count fromid_taptest_table_p100000_p102000_p102100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p102000_p102200', ARRAY[100], 'Check count fromid_taptest_table_p100000_p102000_p102200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p102000_p102300', ARRAY[100], 'Check count fromid_taptest_table_p100000_p102000_p102300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p102000_p102400', ARRAY[100], 'Check count fromid_taptest_table_p100000_p102000_p102400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p102000_p102500', ARRAY[100], 'Check count fromid_taptest_table_p100000_p102000_p102500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p102000_p102600', ARRAY[100], 'Check count fromid_taptest_table_p100000_p102000_p102600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p102000_p102700', ARRAY[100], 'Check count fromid_taptest_table_p100000_p102000_p102700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p102000_p102800', ARRAY[100], 'Check count fromid_taptest_table_p100000_p102000_p102800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p102000_p102900', ARRAY[100], 'Check count fromid_taptest_table_p100000_p102000_p102900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p103000_p103000', ARRAY[100], 'Check count fromid_taptest_table_p100000_p103000_p103000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p103000_p103100', ARRAY[100], 'Check count fromid_taptest_table_p100000_p103000_p103100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p103000_p103200', ARRAY[100], 'Check count fromid_taptest_table_p100000_p103000_p103200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p103000_p103300', ARRAY[100], 'Check count fromid_taptest_table_p100000_p103000_p103300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p103000_p103400', ARRAY[100], 'Check count fromid_taptest_table_p100000_p103000_p103400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p103000_p103500', ARRAY[100], 'Check count fromid_taptest_table_p100000_p103000_p103500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p103000_p103600', ARRAY[100], 'Check count fromid_taptest_table_p100000_p103000_p103600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p103000_p103700', ARRAY[100], 'Check count fromid_taptest_table_p100000_p103000_p103700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p103000_p103800', ARRAY[100], 'Check count fromid_taptest_table_p100000_p103000_p103800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p103000_p103900', ARRAY[100], 'Check count fromid_taptest_table_p100000_p103000_p103900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p104000_p104000', ARRAY[100], 'Check count fromid_taptest_table_p100000_p104000_p104000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p104000_p104100', ARRAY[100], 'Check count fromid_taptest_table_p100000_p104000_p104100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p104000_p104200', ARRAY[100], 'Check count fromid_taptest_table_p100000_p104000_p104200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p104000_p104300', ARRAY[100], 'Check count fromid_taptest_table_p100000_p104000_p104300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p104000_p104400', ARRAY[100], 'Check count fromid_taptest_table_p100000_p104000_p104400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p104000_p104500', ARRAY[100], 'Check count fromid_taptest_table_p100000_p104000_p104500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p104000_p104600', ARRAY[100], 'Check count fromid_taptest_table_p100000_p104000_p104600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p104000_p104700', ARRAY[100], 'Check count fromid_taptest_table_p100000_p104000_p104700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p104000_p104800', ARRAY[100], 'Check count fromid_taptest_table_p100000_p104000_p104800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p104000_p104900', ARRAY[100], 'Check count fromid_taptest_table_p100000_p104000_p104900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p105000_p105000', ARRAY[100], 'Check count fromid_taptest_table_p100000_p105000_p105000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p105000_p105100', ARRAY[100], 'Check count fromid_taptest_table_p100000_p105000_p105100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p105000_p105200', ARRAY[100], 'Check count fromid_taptest_table_p100000_p105000_p105200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p105000_p105300', ARRAY[100], 'Check count fromid_taptest_table_p100000_p105000_p105300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p105000_p105400', ARRAY[100], 'Check count fromid_taptest_table_p100000_p105000_p105400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p105000_p105500', ARRAY[100], 'Check count fromid_taptest_table_p100000_p105000_p105500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p105000_p105600', ARRAY[100], 'Check count fromid_taptest_table_p100000_p105000_p105600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p105000_p105700', ARRAY[100], 'Check count fromid_taptest_table_p100000_p105000_p105700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p105000_p105800', ARRAY[100], 'Check count fromid_taptest_table_p100000_p105000_p105800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p105000_p105900', ARRAY[100], 'Check count fromid_taptest_table_p100000_p105000_p105900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p106000_p106000', ARRAY[100], 'Check count fromid_taptest_table_p100000_p106000_p106000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p106000_p106100', ARRAY[100], 'Check count fromid_taptest_table_p100000_p106000_p106100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p106000_p106200', ARRAY[100], 'Check count fromid_taptest_table_p100000_p106000_p106200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p106000_p106300', ARRAY[100], 'Check count fromid_taptest_table_p100000_p106000_p106300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p106000_p106400', ARRAY[100], 'Check count fromid_taptest_table_p100000_p106000_p106400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p106000_p106500', ARRAY[100], 'Check count fromid_taptest_table_p100000_p106000_p106500'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p106000_p106600', ARRAY[100], 'Check count fromid_taptest_table_p100000_p106000_p106600'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p106000_p106700', ARRAY[100], 'Check count fromid_taptest_table_p100000_p106000_p106700'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p106000_p106800', ARRAY[100], 'Check count fromid_taptest_table_p100000_p106000_p106800'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p106000_p106900', ARRAY[100], 'Check count fromid_taptest_table_p100000_p106000_p106900'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p107000_p107000', ARRAY[100], 'Check count fromid_taptest_table_p100000_p107000_p107000'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p107000_p107100', ARRAY[100], 'Check count fromid_taptest_table_p100000_p107000_p107100'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p107000_p107200', ARRAY[100], 'Check count fromid_taptest_table_p100000_p107000_p107200'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p107000_p107300', ARRAY[100], 'Check count fromid_taptest_table_p100000_p107000_p107300'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p107000_p107400', ARRAY[100], 'Check count fromid_taptest_table_p100000_p107000_p107400'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p100000_p107000_p107500', ARRAY[1], 'Check count fromid_taptest_table_p100000_p107000_p107500'); SELECT run_maintenance(); -- UNDO ALL THE THINGS!! SELECT throws_matching('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table'', ''partman_test.undo_taptest'', 20, p_keep_table := false)', 'Child table for this parent has child table\(s\) itself \(partman_test\.id_taptest_table_p0\)\. Run undo partitioning on this table or remove inheritance first to ensure all data is properly moved to parent.*', 'Check that undoing partitions is prevented if subpartitions still exist'); -- First the sub-sub parents SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p0_p0'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[999], 'Undo partitioning for parent table partman_test.id_taptest_table_p0_p0'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p0_p1000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p0_p1000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p0_p2000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p0_p2000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p0_p3000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p0_p3000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p0_p4000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p0_p4000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p0_p5000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p0_p5000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p0_p6000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p0_p6000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p0_p7000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p0_p7000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p0_p8000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p0_p8000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p0_p9000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p0_p9000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p10000_p10000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p10000_p10000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p10000_p11000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p10000_p11000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p10000_p12000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p10000_p12000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p10000_p13000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p10000_p13000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p10000_p14000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p10000_p14000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p10000_p15000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p10000_p15000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p10000_p16000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p10000_p16000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p10000_p17000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p10000_p17000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p10000_p18000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p10000_p18000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p10000_p19000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p10000_p19000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p20000_p20000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p20000_p20000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p20000_p21000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p20000_p21000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p20000_p22000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p20000_p22000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p20000_p23000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p20000_p23000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p20000_p24000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p20000_p24000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p20000_p25000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p20000_p25000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p20000_p26000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p20000_p26000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p20000_p27000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p20000_p27000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p20000_p28000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p20000_p28000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p20000_p29000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p20000_p29000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p30000_p30000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p30000_p30000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p30000_p31000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p30000_p31000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p30000_p32000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p30000_p32000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p30000_p33000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p30000_p33000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p30000_p34000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p30000_p34000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p30000_p35000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p30000_p35000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p30000_p36000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p30000_p36000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p30000_p37000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p30000_p37000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p30000_p38000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p30000_p38000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p30000_p39000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p30000_p39000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p40000_p40000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p40000_p40000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p40000_p41000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p40000_p41000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p40000_p42000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p40000_p42000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p40000_p43000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p40000_p43000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p40000_p44000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p40000_p44000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p40000_p45000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p40000_p45000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p40000_p46000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p40000_p46000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p40000_p47000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p40000_p47000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p40000_p48000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p40000_p48000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p40000_p49000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p40000_p49000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p50000_p50000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p50000_p50000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p50000_p51000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p50000_p51000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p50000_p52000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p50000_p52000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p50000_p53000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p50000_p53000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p50000_p54000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p50000_p54000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p50000_p55000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p50000_p55000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p50000_p56000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p50000_p56000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p50000_p57000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p50000_p57000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p50000_p58000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p50000_p58000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p50000_p59000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p50000_p59000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p60000_p60000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p60000_p60000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p60000_p61000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p60000_p61000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p60000_p62000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p60000_p62000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p60000_p63000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p60000_p63000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p60000_p64000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p60000_p64000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p60000_p65000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p60000_p65000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p60000_p66000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p60000_p66000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p60000_p67000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p60000_p67000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p60000_p68000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p60000_p68000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p60000_p69000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p60000_p69000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p70000_p70000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p70000_p70000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p70000_p71000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p70000_p71000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p70000_p72000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p70000_p72000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p70000_p73000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p70000_p73000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p70000_p74000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p70000_p74000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p70000_p75000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p70000_p75000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p70000_p76000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p70000_p76000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p70000_p77000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p70000_p77000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p70000_p78000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p70000_p78000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p70000_p79000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p70000_p79000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p80000_p80000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p80000_p80000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p80000_p81000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p80000_p81000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p80000_p82000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p80000_p82000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p80000_p83000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p80000_p83000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p80000_p84000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p80000_p84000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p80000_p85000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p80000_p85000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p80000_p86000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p80000_p86000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p80000_p87000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p80000_p87000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p80000_p88000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p80000_p88000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p80000_p89000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p80000_p89000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p90000_p90000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p90000_p90000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p90000_p91000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p90000_p91000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p90000_p92000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p90000_p92000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p90000_p93000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p90000_p93000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p90000_p94000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p90000_p94000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p90000_p95000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p90000_p95000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p90000_p96000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p90000_p96000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p90000_p97000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p90000_p97000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p90000_p98000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p90000_p98000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p90000_p99000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p90000_p99000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p100000_p100000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p100000_p100000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p100000_p101000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p100000_p101000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p100000_p102000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p100000_p102000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p100000_p103000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p100000_p103000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p100000_p104000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p100000_p104000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p100000_p105000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p100000_p105000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p100000_p106000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[1000], 'Undo partitioning for parent table partman_test.id_taptest_table_p100000_p106000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p100000_p107000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[501], 'Undo partitioning for parent table partman_test.id_taptest_table_p100000_p107000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p100000_p108000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p100000_p108000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p100000_p109000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p100000_p109000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p110000_p110000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p110000_p110000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p120000_p120000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p120000_p120000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p130000_p130000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p130000_p130000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p140000_p140000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p140000_p140000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p150000_p150000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p150000_p150000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p160000_p160000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p160000_p160000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p170000_p170000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p170000_p170000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p180000_p180000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p180000_p180000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p190000_p190000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p190000_p190000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p200000_p200000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p200000_p200000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p210000_p210000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p210000_p210000'); -- Check that lowest child tables were removed (TODO need to clean this list up, it's got everything) SELECT hasnt_table('partman_test.id_taptest_table_p0_p0_p0', 'Check that table partman_test.id_taptest_table_p0_p0_p0 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p0_p100', 'Check that table partman_test.id_taptest_table_p0_p0_p100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p0_p200', 'Check that table partman_test.id_taptest_table_p0_p0_p200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p0_p300', 'Check that table partman_test.id_taptest_table_p0_p0_p300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p0_p400', 'Check that table partman_test.id_taptest_table_p0_p0_p400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p0_p500', 'Check that table partman_test.id_taptest_table_p0_p0_p500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p0_p600', 'Check that table partman_test.id_taptest_table_p0_p0_p600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p0_p700', 'Check that table partman_test.id_taptest_table_p0_p0_p700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p0_p800', 'Check that table partman_test.id_taptest_table_p0_p0_p800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p0_p900', 'Check that table partman_test.id_taptest_table_p0_p0_p900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p1000_p1000', 'Check that table partman_test.id_taptest_table_p0_p1000_p1000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p1000_p1100', 'Check that table partman_test.id_taptest_table_p0_p1000_p1100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p1000_p1200', 'Check that table partman_test.id_taptest_table_p0_p1000_p1200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p1000_p1300', 'Check that table partman_test.id_taptest_table_p0_p1000_p1300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p1000_p1400', 'Check that table partman_test.id_taptest_table_p0_p1000_p1400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p1000_p1500', 'Check that table partman_test.id_taptest_table_p0_p1000_p1500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p1000_p1600', 'Check that table partman_test.id_taptest_table_p0_p1000_p1600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p1000_p1700', 'Check that table partman_test.id_taptest_table_p0_p1000_p1700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p1000_p1800', 'Check that table partman_test.id_taptest_table_p0_p1000_p1800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p1000_p1900', 'Check that table partman_test.id_taptest_table_p0_p1000_p1900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p2000_p2000', 'Check that table partman_test.id_taptest_table_p0_p2000_p2000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p2000_p2100', 'Check that table partman_test.id_taptest_table_p0_p2000_p2100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p2000_p2200', 'Check that table partman_test.id_taptest_table_p0_p2000_p2200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p2000_p2300', 'Check that table partman_test.id_taptest_table_p0_p2000_p2300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p2000_p2400', 'Check that table partman_test.id_taptest_table_p0_p2000_p2400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p2000_p2500', 'Check that table partman_test.id_taptest_table_p0_p2000_p2500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p2000_p2600', 'Check that table partman_test.id_taptest_table_p0_p2000_p2600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p2000_p2700', 'Check that table partman_test.id_taptest_table_p0_p2000_p2700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p2000_p2800', 'Check that table partman_test.id_taptest_table_p0_p2000_p2800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p2000_p2900', 'Check that table partman_test.id_taptest_table_p0_p2000_p2900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p3000_p3000', 'Check that table partman_test.id_taptest_table_p0_p3000_p3000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p3000_p3100', 'Check that table partman_test.id_taptest_table_p0_p3000_p3100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p3000_p3200', 'Check that table partman_test.id_taptest_table_p0_p3000_p3200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p3000_p3300', 'Check that table partman_test.id_taptest_table_p0_p3000_p3300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p3000_p3400', 'Check that table partman_test.id_taptest_table_p0_p3000_p3400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p3000_p3500', 'Check that table partman_test.id_taptest_table_p0_p3000_p3500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p3000_p3600', 'Check that table partman_test.id_taptest_table_p0_p3000_p3600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p3000_p3700', 'Check that table partman_test.id_taptest_table_p0_p3000_p3700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p3000_p3800', 'Check that table partman_test.id_taptest_table_p0_p3000_p3800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p3000_p3900', 'Check that table partman_test.id_taptest_table_p0_p3000_p3900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p4000_p4000', 'Check that table partman_test.id_taptest_table_p0_p4000_p4000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p4000_p4100', 'Check that table partman_test.id_taptest_table_p0_p4000_p4100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p4000_p4200', 'Check that table partman_test.id_taptest_table_p0_p4000_p4200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p4000_p4300', 'Check that table partman_test.id_taptest_table_p0_p4000_p4300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p4000_p4400', 'Check that table partman_test.id_taptest_table_p0_p4000_p4400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p4000_p4500', 'Check that table partman_test.id_taptest_table_p0_p4000_p4500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p4000_p4600', 'Check that table partman_test.id_taptest_table_p0_p4000_p4600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p4000_p4700', 'Check that table partman_test.id_taptest_table_p0_p4000_p4700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p4000_p4800', 'Check that table partman_test.id_taptest_table_p0_p4000_p4800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p4000_p4900', 'Check that table partman_test.id_taptest_table_p0_p4000_p4900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p5000_p5000', 'Check that table partman_test.id_taptest_table_p0_p5000_p5000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p5000_p5100', 'Check that table partman_test.id_taptest_table_p0_p5000_p5100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p5000_p5200', 'Check that table partman_test.id_taptest_table_p0_p5000_p5200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p5000_p5300', 'Check that table partman_test.id_taptest_table_p0_p5000_p5300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p5000_p5400', 'Check that table partman_test.id_taptest_table_p0_p5000_p5400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p5000_p5500', 'Check that table partman_test.id_taptest_table_p0_p5000_p5500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p5000_p5600', 'Check that table partman_test.id_taptest_table_p0_p5000_p5600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p5000_p5700', 'Check that table partman_test.id_taptest_table_p0_p5000_p5700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p5000_p5800', 'Check that table partman_test.id_taptest_table_p0_p5000_p5800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p5000_p5900', 'Check that table partman_test.id_taptest_table_p0_p5000_p5900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p6000_p6000', 'Check that table partman_test.id_taptest_table_p0_p6000_p6000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p6000_p6100', 'Check that table partman_test.id_taptest_table_p0_p6000_p6100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p6000_p6200', 'Check that table partman_test.id_taptest_table_p0_p6000_p6200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p6000_p6300', 'Check that table partman_test.id_taptest_table_p0_p6000_p6300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p6000_p6400', 'Check that table partman_test.id_taptest_table_p0_p6000_p6400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p6000_p6500', 'Check that table partman_test.id_taptest_table_p0_p6000_p6500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p6000_p6600', 'Check that table partman_test.id_taptest_table_p0_p6000_p6600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p6000_p6700', 'Check that table partman_test.id_taptest_table_p0_p6000_p6700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p6000_p6800', 'Check that table partman_test.id_taptest_table_p0_p6000_p6800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p6000_p6900', 'Check that table partman_test.id_taptest_table_p0_p6000_p6900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p7000_p7000', 'Check that table partman_test.id_taptest_table_p0_p7000_p7000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p7000_p7100', 'Check that table partman_test.id_taptest_table_p0_p7000_p7100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p7000_p7200', 'Check that table partman_test.id_taptest_table_p0_p7000_p7200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p7000_p7300', 'Check that table partman_test.id_taptest_table_p0_p7000_p7300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p7000_p7400', 'Check that table partman_test.id_taptest_table_p0_p7000_p7400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p7000_p7500', 'Check that table partman_test.id_taptest_table_p0_p7000_p7500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p7000_p7600', 'Check that table partman_test.id_taptest_table_p0_p7000_p7600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p7000_p7700', 'Check that table partman_test.id_taptest_table_p0_p7000_p7700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p7000_p7800', 'Check that table partman_test.id_taptest_table_p0_p7000_p7800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p7000_p7900', 'Check that table partman_test.id_taptest_table_p0_p7000_p7900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p8000_p8000', 'Check that table partman_test.id_taptest_table_p0_p8000_p8000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p8000_p8100', 'Check that table partman_test.id_taptest_table_p0_p8000_p8100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p8000_p8200', 'Check that table partman_test.id_taptest_table_p0_p8000_p8200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p8000_p8300', 'Check that table partman_test.id_taptest_table_p0_p8000_p8300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p8000_p8400', 'Check that table partman_test.id_taptest_table_p0_p8000_p8400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p8000_p8500', 'Check that table partman_test.id_taptest_table_p0_p8000_p8500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p8000_p8600', 'Check that table partman_test.id_taptest_table_p0_p8000_p8600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p8000_p8700', 'Check that table partman_test.id_taptest_table_p0_p8000_p8700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p8000_p8800', 'Check that table partman_test.id_taptest_table_p0_p8000_p8800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p8000_p8900', 'Check that table partman_test.id_taptest_table_p0_p8000_p8900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p9000_p9000', 'Check that table partman_test.id_taptest_table_p0_p9000_p9000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p9000_p9100', 'Check that table partman_test.id_taptest_table_p0_p9000_p9100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p9000_p9200', 'Check that table partman_test.id_taptest_table_p0_p9000_p9200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p9000_p9300', 'Check that table partman_test.id_taptest_table_p0_p9000_p9300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p9000_p9400', 'Check that table partman_test.id_taptest_table_p0_p9000_p9400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p9000_p9500', 'Check that table partman_test.id_taptest_table_p0_p9000_p9500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p9000_p9600', 'Check that table partman_test.id_taptest_table_p0_p9000_p9600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p9000_p9700', 'Check that table partman_test.id_taptest_table_p0_p9000_p9700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p9000_p9800', 'Check that table partman_test.id_taptest_table_p0_p9000_p9800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p9000_p9900', 'Check that table partman_test.id_taptest_table_p0_p9000_p9900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p100000_p100000', 'Check that table partman_test.id_taptest_table_p100000_p100000_p100000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p100000_p100100', 'Check that table partman_test.id_taptest_table_p100000_p100000_p100100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p100000_p100200', 'Check that table partman_test.id_taptest_table_p100000_p100000_p100200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p100000_p100300', 'Check that table partman_test.id_taptest_table_p100000_p100000_p100300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p100000_p100400', 'Check that table partman_test.id_taptest_table_p100000_p100000_p100400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p100000_p100500', 'Check that table partman_test.id_taptest_table_p100000_p100000_p100500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p100000_p100600', 'Check that table partman_test.id_taptest_table_p100000_p100000_p100600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p100000_p100700', 'Check that table partman_test.id_taptest_table_p100000_p100000_p100700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p100000_p100800', 'Check that table partman_test.id_taptest_table_p100000_p100000_p100800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p100000_p100900', 'Check that table partman_test.id_taptest_table_p100000_p100000_p100900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p101000_p101000', 'Check that table partman_test.id_taptest_table_p100000_p101000_p101000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p101000_p101100', 'Check that table partman_test.id_taptest_table_p100000_p101000_p101100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p101000_p101200', 'Check that table partman_test.id_taptest_table_p100000_p101000_p101200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p101000_p101300', 'Check that table partman_test.id_taptest_table_p100000_p101000_p101300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p101000_p101400', 'Check that table partman_test.id_taptest_table_p100000_p101000_p101400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p101000_p101500', 'Check that table partman_test.id_taptest_table_p100000_p101000_p101500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p101000_p101600', 'Check that table partman_test.id_taptest_table_p100000_p101000_p101600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p101000_p101700', 'Check that table partman_test.id_taptest_table_p100000_p101000_p101700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p101000_p101800', 'Check that table partman_test.id_taptest_table_p100000_p101000_p101800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p101000_p101900', 'Check that table partman_test.id_taptest_table_p100000_p101000_p101900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p102000_p102000', 'Check that table partman_test.id_taptest_table_p100000_p102000_p102000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p102000_p102100', 'Check that table partman_test.id_taptest_table_p100000_p102000_p102100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p102000_p102200', 'Check that table partman_test.id_taptest_table_p100000_p102000_p102200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p102000_p102300', 'Check that table partman_test.id_taptest_table_p100000_p102000_p102300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p102000_p102400', 'Check that table partman_test.id_taptest_table_p100000_p102000_p102400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p102000_p102500', 'Check that table partman_test.id_taptest_table_p100000_p102000_p102500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p102000_p102600', 'Check that table partman_test.id_taptest_table_p100000_p102000_p102600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p102000_p102700', 'Check that table partman_test.id_taptest_table_p100000_p102000_p102700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p102000_p102800', 'Check that table partman_test.id_taptest_table_p100000_p102000_p102800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p102000_p102900', 'Check that table partman_test.id_taptest_table_p100000_p102000_p102900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p103000_p103000', 'Check that table partman_test.id_taptest_table_p100000_p103000_p103000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p103000_p103100', 'Check that table partman_test.id_taptest_table_p100000_p103000_p103100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p103000_p103200', 'Check that table partman_test.id_taptest_table_p100000_p103000_p103200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p103000_p103300', 'Check that table partman_test.id_taptest_table_p100000_p103000_p103300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p103000_p103400', 'Check that table partman_test.id_taptest_table_p100000_p103000_p103400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p103000_p103500', 'Check that table partman_test.id_taptest_table_p100000_p103000_p103500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p103000_p103600', 'Check that table partman_test.id_taptest_table_p100000_p103000_p103600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p103000_p103700', 'Check that table partman_test.id_taptest_table_p100000_p103000_p103700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p103000_p103800', 'Check that table partman_test.id_taptest_table_p100000_p103000_p103800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p103000_p103900', 'Check that table partman_test.id_taptest_table_p100000_p103000_p103900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p104000_p104000', 'Check that table partman_test.id_taptest_table_p100000_p104000_p104000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p104000_p104100', 'Check that table partman_test.id_taptest_table_p100000_p104000_p104100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p104000_p104200', 'Check that table partman_test.id_taptest_table_p100000_p104000_p104200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p104000_p104300', 'Check that table partman_test.id_taptest_table_p100000_p104000_p104300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p104000_p104400', 'Check that table partman_test.id_taptest_table_p100000_p104000_p104400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p104000_p104500', 'Check that table partman_test.id_taptest_table_p100000_p104000_p104500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p104000_p104600', 'Check that table partman_test.id_taptest_table_p100000_p104000_p104600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p104000_p104700', 'Check that table partman_test.id_taptest_table_p100000_p104000_p104700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p104000_p104800', 'Check that table partman_test.id_taptest_table_p100000_p104000_p104800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p104000_p104900', 'Check that table partman_test.id_taptest_table_p100000_p104000_p104900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p105000_p105000', 'Check that table partman_test.id_taptest_table_p100000_p105000_p105000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p105000_p105100', 'Check that table partman_test.id_taptest_table_p100000_p105000_p105100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p105000_p105200', 'Check that table partman_test.id_taptest_table_p100000_p105000_p105200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p105000_p105300', 'Check that table partman_test.id_taptest_table_p100000_p105000_p105300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p105000_p105400', 'Check that table partman_test.id_taptest_table_p100000_p105000_p105400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p105000_p105500', 'Check that table partman_test.id_taptest_table_p100000_p105000_p105500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p105000_p105600', 'Check that table partman_test.id_taptest_table_p100000_p105000_p105600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p105000_p105700', 'Check that table partman_test.id_taptest_table_p100000_p105000_p105700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p105000_p105800', 'Check that table partman_test.id_taptest_table_p100000_p105000_p105800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p105000_p105900', 'Check that table partman_test.id_taptest_table_p100000_p105000_p105900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p106000_p106000', 'Check that table partman_test.id_taptest_table_p100000_p106000_p106000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p106000_p106100', 'Check that table partman_test.id_taptest_table_p100000_p106000_p106100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p106000_p106200', 'Check that table partman_test.id_taptest_table_p100000_p106000_p106200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p106000_p106300', 'Check that table partman_test.id_taptest_table_p100000_p106000_p106300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p106000_p106400', 'Check that table partman_test.id_taptest_table_p100000_p106000_p106400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p106000_p106500', 'Check that table partman_test.id_taptest_table_p100000_p106000_p106500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p106000_p106600', 'Check that table partman_test.id_taptest_table_p100000_p106000_p106600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p106000_p106700', 'Check that table partman_test.id_taptest_table_p100000_p106000_p106700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p106000_p106800', 'Check that table partman_test.id_taptest_table_p100000_p106000_p106800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p106000_p106900', 'Check that table partman_test.id_taptest_table_p100000_p106000_p106900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p107000_p107000', 'Check that table partman_test.id_taptest_table_p100000_p107000_p107000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p107000_p107100', 'Check that table partman_test.id_taptest_table_p100000_p107000_p107100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p107000_p107200', 'Check that table partman_test.id_taptest_table_p100000_p107000_p107200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p107000_p107300', 'Check that table partman_test.id_taptest_table_p100000_p107000_p107300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p107000_p107400', 'Check that table partman_test.id_taptest_table_p100000_p107000_p107400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p107000_p107500', 'Check that table partman_test.id_taptest_table_p100000_p107000_p107500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p107000_p107600', 'Check that table partman_test.id_taptest_table_p100000_p107000_p107600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p107000_p107700', 'Check that table partman_test.id_taptest_table_p100000_p107000_p107700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p108000_p108000', 'Check that table partman_test.id_taptest_table_p100000_p108000_p108000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p109000_p109000', 'Check that table partman_test.id_taptest_table_p100000_p109000_p109000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p10000_p10000', 'Check that table partman_test.id_taptest_table_p10000_p10000_p10000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p10000_p10100', 'Check that table partman_test.id_taptest_table_p10000_p10000_p10100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p10000_p10200', 'Check that table partman_test.id_taptest_table_p10000_p10000_p10200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p10000_p10300', 'Check that table partman_test.id_taptest_table_p10000_p10000_p10300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p10000_p10400', 'Check that table partman_test.id_taptest_table_p10000_p10000_p10400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p10000_p10500', 'Check that table partman_test.id_taptest_table_p10000_p10000_p10500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p10000_p10600', 'Check that table partman_test.id_taptest_table_p10000_p10000_p10600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p10000_p10700', 'Check that table partman_test.id_taptest_table_p10000_p10000_p10700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p10000_p10800', 'Check that table partman_test.id_taptest_table_p10000_p10000_p10800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p10000_p10900', 'Check that table partman_test.id_taptest_table_p10000_p10000_p10900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p11000_p11000', 'Check that table partman_test.id_taptest_table_p10000_p11000_p11000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p11000_p11100', 'Check that table partman_test.id_taptest_table_p10000_p11000_p11100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p11000_p11200', 'Check that table partman_test.id_taptest_table_p10000_p11000_p11200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p11000_p11300', 'Check that table partman_test.id_taptest_table_p10000_p11000_p11300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p11000_p11400', 'Check that table partman_test.id_taptest_table_p10000_p11000_p11400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p11000_p11500', 'Check that table partman_test.id_taptest_table_p10000_p11000_p11500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p11000_p11600', 'Check that table partman_test.id_taptest_table_p10000_p11000_p11600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p11000_p11700', 'Check that table partman_test.id_taptest_table_p10000_p11000_p11700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p11000_p11800', 'Check that table partman_test.id_taptest_table_p10000_p11000_p11800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p11000_p11900', 'Check that table partman_test.id_taptest_table_p10000_p11000_p11900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p12000_p12000', 'Check that table partman_test.id_taptest_table_p10000_p12000_p12000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p12000_p12100', 'Check that table partman_test.id_taptest_table_p10000_p12000_p12100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p12000_p12200', 'Check that table partman_test.id_taptest_table_p10000_p12000_p12200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p12000_p12300', 'Check that table partman_test.id_taptest_table_p10000_p12000_p12300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p12000_p12400', 'Check that table partman_test.id_taptest_table_p10000_p12000_p12400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p12000_p12500', 'Check that table partman_test.id_taptest_table_p10000_p12000_p12500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p12000_p12600', 'Check that table partman_test.id_taptest_table_p10000_p12000_p12600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p12000_p12700', 'Check that table partman_test.id_taptest_table_p10000_p12000_p12700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p12000_p12800', 'Check that table partman_test.id_taptest_table_p10000_p12000_p12800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p12000_p12900', 'Check that table partman_test.id_taptest_table_p10000_p12000_p12900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p13000_p13000', 'Check that table partman_test.id_taptest_table_p10000_p13000_p13000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p13000_p13100', 'Check that table partman_test.id_taptest_table_p10000_p13000_p13100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p13000_p13200', 'Check that table partman_test.id_taptest_table_p10000_p13000_p13200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p13000_p13300', 'Check that table partman_test.id_taptest_table_p10000_p13000_p13300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p13000_p13400', 'Check that table partman_test.id_taptest_table_p10000_p13000_p13400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p13000_p13500', 'Check that table partman_test.id_taptest_table_p10000_p13000_p13500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p13000_p13600', 'Check that table partman_test.id_taptest_table_p10000_p13000_p13600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p13000_p13700', 'Check that table partman_test.id_taptest_table_p10000_p13000_p13700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p13000_p13800', 'Check that table partman_test.id_taptest_table_p10000_p13000_p13800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p13000_p13900', 'Check that table partman_test.id_taptest_table_p10000_p13000_p13900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p14000_p14000', 'Check that table partman_test.id_taptest_table_p10000_p14000_p14000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p14000_p14100', 'Check that table partman_test.id_taptest_table_p10000_p14000_p14100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p14000_p14200', 'Check that table partman_test.id_taptest_table_p10000_p14000_p14200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p14000_p14300', 'Check that table partman_test.id_taptest_table_p10000_p14000_p14300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p14000_p14400', 'Check that table partman_test.id_taptest_table_p10000_p14000_p14400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p14000_p14500', 'Check that table partman_test.id_taptest_table_p10000_p14000_p14500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p14000_p14600', 'Check that table partman_test.id_taptest_table_p10000_p14000_p14600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p14000_p14700', 'Check that table partman_test.id_taptest_table_p10000_p14000_p14700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p14000_p14800', 'Check that table partman_test.id_taptest_table_p10000_p14000_p14800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p14000_p14900', 'Check that table partman_test.id_taptest_table_p10000_p14000_p14900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p15000_p15000', 'Check that table partman_test.id_taptest_table_p10000_p15000_p15000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p15000_p15100', 'Check that table partman_test.id_taptest_table_p10000_p15000_p15100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p15000_p15200', 'Check that table partman_test.id_taptest_table_p10000_p15000_p15200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p15000_p15300', 'Check that table partman_test.id_taptest_table_p10000_p15000_p15300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p15000_p15400', 'Check that table partman_test.id_taptest_table_p10000_p15000_p15400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p15000_p15500', 'Check that table partman_test.id_taptest_table_p10000_p15000_p15500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p15000_p15600', 'Check that table partman_test.id_taptest_table_p10000_p15000_p15600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p15000_p15700', 'Check that table partman_test.id_taptest_table_p10000_p15000_p15700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p15000_p15800', 'Check that table partman_test.id_taptest_table_p10000_p15000_p15800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p15000_p15900', 'Check that table partman_test.id_taptest_table_p10000_p15000_p15900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p16000_p16000', 'Check that table partman_test.id_taptest_table_p10000_p16000_p16000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p16000_p16100', 'Check that table partman_test.id_taptest_table_p10000_p16000_p16100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p16000_p16200', 'Check that table partman_test.id_taptest_table_p10000_p16000_p16200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p16000_p16300', 'Check that table partman_test.id_taptest_table_p10000_p16000_p16300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p16000_p16400', 'Check that table partman_test.id_taptest_table_p10000_p16000_p16400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p16000_p16500', 'Check that table partman_test.id_taptest_table_p10000_p16000_p16500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p16000_p16600', 'Check that table partman_test.id_taptest_table_p10000_p16000_p16600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p16000_p16700', 'Check that table partman_test.id_taptest_table_p10000_p16000_p16700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p16000_p16800', 'Check that table partman_test.id_taptest_table_p10000_p16000_p16800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p16000_p16900', 'Check that table partman_test.id_taptest_table_p10000_p16000_p16900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p17000_p17000', 'Check that table partman_test.id_taptest_table_p10000_p17000_p17000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p17000_p17100', 'Check that table partman_test.id_taptest_table_p10000_p17000_p17100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p17000_p17200', 'Check that table partman_test.id_taptest_table_p10000_p17000_p17200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p17000_p17300', 'Check that table partman_test.id_taptest_table_p10000_p17000_p17300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p17000_p17400', 'Check that table partman_test.id_taptest_table_p10000_p17000_p17400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p17000_p17500', 'Check that table partman_test.id_taptest_table_p10000_p17000_p17500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p17000_p17600', 'Check that table partman_test.id_taptest_table_p10000_p17000_p17600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p17000_p17700', 'Check that table partman_test.id_taptest_table_p10000_p17000_p17700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p17000_p17800', 'Check that table partman_test.id_taptest_table_p10000_p17000_p17800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p17000_p17900', 'Check that table partman_test.id_taptest_table_p10000_p17000_p17900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p18000_p18000', 'Check that table partman_test.id_taptest_table_p10000_p18000_p18000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p18000_p18100', 'Check that table partman_test.id_taptest_table_p10000_p18000_p18100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p18000_p18200', 'Check that table partman_test.id_taptest_table_p10000_p18000_p18200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p18000_p18300', 'Check that table partman_test.id_taptest_table_p10000_p18000_p18300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p18000_p18400', 'Check that table partman_test.id_taptest_table_p10000_p18000_p18400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p18000_p18500', 'Check that table partman_test.id_taptest_table_p10000_p18000_p18500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p18000_p18600', 'Check that table partman_test.id_taptest_table_p10000_p18000_p18600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p18000_p18700', 'Check that table partman_test.id_taptest_table_p10000_p18000_p18700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p18000_p18800', 'Check that table partman_test.id_taptest_table_p10000_p18000_p18800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p18000_p18900', 'Check that table partman_test.id_taptest_table_p10000_p18000_p18900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p19000_p19000', 'Check that table partman_test.id_taptest_table_p10000_p19000_p19000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p19000_p19100', 'Check that table partman_test.id_taptest_table_p10000_p19000_p19100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p19000_p19200', 'Check that table partman_test.id_taptest_table_p10000_p19000_p19200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p19000_p19300', 'Check that table partman_test.id_taptest_table_p10000_p19000_p19300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p19000_p19400', 'Check that table partman_test.id_taptest_table_p10000_p19000_p19400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p19000_p19500', 'Check that table partman_test.id_taptest_table_p10000_p19000_p19500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p19000_p19600', 'Check that table partman_test.id_taptest_table_p10000_p19000_p19600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p19000_p19700', 'Check that table partman_test.id_taptest_table_p10000_p19000_p19700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p19000_p19800', 'Check that table partman_test.id_taptest_table_p10000_p19000_p19800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p19000_p19900', 'Check that table partman_test.id_taptest_table_p10000_p19000_p19900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p110000_p110000_p110000', 'Check that table partman_test.id_taptest_table_p110000_p110000_p110000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p120000_p120000_p120000', 'Check that table partman_test.id_taptest_table_p120000_p120000_p120000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p130000_p130000_p130000', 'Check that table partman_test.id_taptest_table_p130000_p130000_p130000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p140000_p140000_p140000', 'Check that table partman_test.id_taptest_table_p140000_p140000_p140000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p150000_p150000_p150000', 'Check that table partman_test.id_taptest_table_p150000_p150000_p150000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p160000_p160000_p160000', 'Check that table partman_test.id_taptest_table_p160000_p160000_p160000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p170000_p170000_p170000', 'Check that table partman_test.id_taptest_table_p170000_p170000_p170000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p180000_p180000_p180000', 'Check that table partman_test.id_taptest_table_p180000_p180000_p180000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p190000_p190000_p190000', 'Check that table partman_test.id_taptest_table_p190000_p190000_p190000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p200000_p200000_p200000', 'Check that table partman_test.id_taptest_table_p200000_p200000_p200000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p20000_p20000', 'Check that table partman_test.id_taptest_table_p20000_p20000_p20000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p20000_p20100', 'Check that table partman_test.id_taptest_table_p20000_p20000_p20100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p20000_p20200', 'Check that table partman_test.id_taptest_table_p20000_p20000_p20200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p20000_p20300', 'Check that table partman_test.id_taptest_table_p20000_p20000_p20300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p20000_p20400', 'Check that table partman_test.id_taptest_table_p20000_p20000_p20400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p20000_p20500', 'Check that table partman_test.id_taptest_table_p20000_p20000_p20500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p20000_p20600', 'Check that table partman_test.id_taptest_table_p20000_p20000_p20600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p20000_p20700', 'Check that table partman_test.id_taptest_table_p20000_p20000_p20700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p20000_p20800', 'Check that table partman_test.id_taptest_table_p20000_p20000_p20800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p20000_p20900', 'Check that table partman_test.id_taptest_table_p20000_p20000_p20900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p21000_p21000', 'Check that table partman_test.id_taptest_table_p20000_p21000_p21000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p21000_p21100', 'Check that table partman_test.id_taptest_table_p20000_p21000_p21100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p21000_p21200', 'Check that table partman_test.id_taptest_table_p20000_p21000_p21200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p21000_p21300', 'Check that table partman_test.id_taptest_table_p20000_p21000_p21300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p21000_p21400', 'Check that table partman_test.id_taptest_table_p20000_p21000_p21400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p21000_p21500', 'Check that table partman_test.id_taptest_table_p20000_p21000_p21500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p21000_p21600', 'Check that table partman_test.id_taptest_table_p20000_p21000_p21600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p21000_p21700', 'Check that table partman_test.id_taptest_table_p20000_p21000_p21700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p21000_p21800', 'Check that table partman_test.id_taptest_table_p20000_p21000_p21800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p21000_p21900', 'Check that table partman_test.id_taptest_table_p20000_p21000_p21900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p22000_p22000', 'Check that table partman_test.id_taptest_table_p20000_p22000_p22000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p22000_p22100', 'Check that table partman_test.id_taptest_table_p20000_p22000_p22100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p22000_p22200', 'Check that table partman_test.id_taptest_table_p20000_p22000_p22200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p22000_p22300', 'Check that table partman_test.id_taptest_table_p20000_p22000_p22300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p22000_p22400', 'Check that table partman_test.id_taptest_table_p20000_p22000_p22400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p22000_p22500', 'Check that table partman_test.id_taptest_table_p20000_p22000_p22500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p22000_p22600', 'Check that table partman_test.id_taptest_table_p20000_p22000_p22600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p22000_p22700', 'Check that table partman_test.id_taptest_table_p20000_p22000_p22700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p22000_p22800', 'Check that table partman_test.id_taptest_table_p20000_p22000_p22800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p22000_p22900', 'Check that table partman_test.id_taptest_table_p20000_p22000_p22900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p23000_p23000', 'Check that table partman_test.id_taptest_table_p20000_p23000_p23000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p23000_p23100', 'Check that table partman_test.id_taptest_table_p20000_p23000_p23100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p23000_p23200', 'Check that table partman_test.id_taptest_table_p20000_p23000_p23200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p23000_p23300', 'Check that table partman_test.id_taptest_table_p20000_p23000_p23300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p23000_p23400', 'Check that table partman_test.id_taptest_table_p20000_p23000_p23400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p23000_p23500', 'Check that table partman_test.id_taptest_table_p20000_p23000_p23500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p23000_p23600', 'Check that table partman_test.id_taptest_table_p20000_p23000_p23600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p23000_p23700', 'Check that table partman_test.id_taptest_table_p20000_p23000_p23700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p23000_p23800', 'Check that table partman_test.id_taptest_table_p20000_p23000_p23800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p23000_p23900', 'Check that table partman_test.id_taptest_table_p20000_p23000_p23900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p24000_p24000', 'Check that table partman_test.id_taptest_table_p20000_p24000_p24000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p24000_p24100', 'Check that table partman_test.id_taptest_table_p20000_p24000_p24100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p24000_p24200', 'Check that table partman_test.id_taptest_table_p20000_p24000_p24200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p24000_p24300', 'Check that table partman_test.id_taptest_table_p20000_p24000_p24300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p24000_p24400', 'Check that table partman_test.id_taptest_table_p20000_p24000_p24400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p24000_p24500', 'Check that table partman_test.id_taptest_table_p20000_p24000_p24500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p24000_p24600', 'Check that table partman_test.id_taptest_table_p20000_p24000_p24600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p24000_p24700', 'Check that table partman_test.id_taptest_table_p20000_p24000_p24700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p24000_p24800', 'Check that table partman_test.id_taptest_table_p20000_p24000_p24800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p24000_p24900', 'Check that table partman_test.id_taptest_table_p20000_p24000_p24900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p25000_p25000', 'Check that table partman_test.id_taptest_table_p20000_p25000_p25000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p25000_p25100', 'Check that table partman_test.id_taptest_table_p20000_p25000_p25100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p25000_p25200', 'Check that table partman_test.id_taptest_table_p20000_p25000_p25200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p25000_p25300', 'Check that table partman_test.id_taptest_table_p20000_p25000_p25300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p25000_p25400', 'Check that table partman_test.id_taptest_table_p20000_p25000_p25400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p25000_p25500', 'Check that table partman_test.id_taptest_table_p20000_p25000_p25500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p25000_p25600', 'Check that table partman_test.id_taptest_table_p20000_p25000_p25600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p25000_p25700', 'Check that table partman_test.id_taptest_table_p20000_p25000_p25700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p25000_p25800', 'Check that table partman_test.id_taptest_table_p20000_p25000_p25800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p25000_p25900', 'Check that table partman_test.id_taptest_table_p20000_p25000_p25900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p26000_p26000', 'Check that table partman_test.id_taptest_table_p20000_p26000_p26000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p26000_p26100', 'Check that table partman_test.id_taptest_table_p20000_p26000_p26100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p26000_p26200', 'Check that table partman_test.id_taptest_table_p20000_p26000_p26200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p26000_p26300', 'Check that table partman_test.id_taptest_table_p20000_p26000_p26300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p26000_p26400', 'Check that table partman_test.id_taptest_table_p20000_p26000_p26400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p26000_p26500', 'Check that table partman_test.id_taptest_table_p20000_p26000_p26500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p26000_p26600', 'Check that table partman_test.id_taptest_table_p20000_p26000_p26600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p26000_p26700', 'Check that table partman_test.id_taptest_table_p20000_p26000_p26700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p26000_p26800', 'Check that table partman_test.id_taptest_table_p20000_p26000_p26800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p26000_p26900', 'Check that table partman_test.id_taptest_table_p20000_p26000_p26900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p27000_p27000', 'Check that table partman_test.id_taptest_table_p20000_p27000_p27000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p27000_p27100', 'Check that table partman_test.id_taptest_table_p20000_p27000_p27100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p27000_p27200', 'Check that table partman_test.id_taptest_table_p20000_p27000_p27200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p27000_p27300', 'Check that table partman_test.id_taptest_table_p20000_p27000_p27300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p27000_p27400', 'Check that table partman_test.id_taptest_table_p20000_p27000_p27400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p27000_p27500', 'Check that table partman_test.id_taptest_table_p20000_p27000_p27500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p27000_p27600', 'Check that table partman_test.id_taptest_table_p20000_p27000_p27600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p27000_p27700', 'Check that table partman_test.id_taptest_table_p20000_p27000_p27700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p27000_p27800', 'Check that table partman_test.id_taptest_table_p20000_p27000_p27800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p27000_p27900', 'Check that table partman_test.id_taptest_table_p20000_p27000_p27900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p28000_p28000', 'Check that table partman_test.id_taptest_table_p20000_p28000_p28000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p28000_p28100', 'Check that table partman_test.id_taptest_table_p20000_p28000_p28100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p28000_p28200', 'Check that table partman_test.id_taptest_table_p20000_p28000_p28200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p28000_p28300', 'Check that table partman_test.id_taptest_table_p20000_p28000_p28300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p28000_p28400', 'Check that table partman_test.id_taptest_table_p20000_p28000_p28400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p28000_p28500', 'Check that table partman_test.id_taptest_table_p20000_p28000_p28500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p28000_p28600', 'Check that table partman_test.id_taptest_table_p20000_p28000_p28600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p28000_p28700', 'Check that table partman_test.id_taptest_table_p20000_p28000_p28700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p28000_p28800', 'Check that table partman_test.id_taptest_table_p20000_p28000_p28800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p28000_p28900', 'Check that table partman_test.id_taptest_table_p20000_p28000_p28900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p29000_p29000', 'Check that table partman_test.id_taptest_table_p20000_p29000_p29000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p29000_p29100', 'Check that table partman_test.id_taptest_table_p20000_p29000_p29100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p29000_p29200', 'Check that table partman_test.id_taptest_table_p20000_p29000_p29200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p29000_p29300', 'Check that table partman_test.id_taptest_table_p20000_p29000_p29300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p29000_p29400', 'Check that table partman_test.id_taptest_table_p20000_p29000_p29400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p29000_p29500', 'Check that table partman_test.id_taptest_table_p20000_p29000_p29500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p29000_p29600', 'Check that table partman_test.id_taptest_table_p20000_p29000_p29600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p29000_p29700', 'Check that table partman_test.id_taptest_table_p20000_p29000_p29700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p29000_p29800', 'Check that table partman_test.id_taptest_table_p20000_p29000_p29800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p29000_p29900', 'Check that table partman_test.id_taptest_table_p20000_p29000_p29900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p210000_p210000_p210000', 'Check that table partman_test.id_taptest_table_p210000_p210000_p210000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p30000_p30000', 'Check that table partman_test.id_taptest_table_p30000_p30000_p30000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p30000_p30100', 'Check that table partman_test.id_taptest_table_p30000_p30000_p30100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p30000_p30200', 'Check that table partman_test.id_taptest_table_p30000_p30000_p30200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p30000_p30300', 'Check that table partman_test.id_taptest_table_p30000_p30000_p30300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p30000_p30400', 'Check that table partman_test.id_taptest_table_p30000_p30000_p30400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p30000_p30500', 'Check that table partman_test.id_taptest_table_p30000_p30000_p30500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p30000_p30600', 'Check that table partman_test.id_taptest_table_p30000_p30000_p30600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p30000_p30700', 'Check that table partman_test.id_taptest_table_p30000_p30000_p30700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p30000_p30800', 'Check that table partman_test.id_taptest_table_p30000_p30000_p30800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p30000_p30900', 'Check that table partman_test.id_taptest_table_p30000_p30000_p30900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p31000_p31000', 'Check that table partman_test.id_taptest_table_p30000_p31000_p31000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p31000_p31100', 'Check that table partman_test.id_taptest_table_p30000_p31000_p31100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p31000_p31200', 'Check that table partman_test.id_taptest_table_p30000_p31000_p31200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p31000_p31300', 'Check that table partman_test.id_taptest_table_p30000_p31000_p31300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p31000_p31400', 'Check that table partman_test.id_taptest_table_p30000_p31000_p31400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p31000_p31500', 'Check that table partman_test.id_taptest_table_p30000_p31000_p31500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p31000_p31600', 'Check that table partman_test.id_taptest_table_p30000_p31000_p31600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p31000_p31700', 'Check that table partman_test.id_taptest_table_p30000_p31000_p31700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p31000_p31800', 'Check that table partman_test.id_taptest_table_p30000_p31000_p31800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p31000_p31900', 'Check that table partman_test.id_taptest_table_p30000_p31000_p31900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p32000_p32000', 'Check that table partman_test.id_taptest_table_p30000_p32000_p32000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p32000_p32100', 'Check that table partman_test.id_taptest_table_p30000_p32000_p32100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p32000_p32200', 'Check that table partman_test.id_taptest_table_p30000_p32000_p32200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p32000_p32300', 'Check that table partman_test.id_taptest_table_p30000_p32000_p32300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p32000_p32400', 'Check that table partman_test.id_taptest_table_p30000_p32000_p32400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p32000_p32500', 'Check that table partman_test.id_taptest_table_p30000_p32000_p32500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p32000_p32600', 'Check that table partman_test.id_taptest_table_p30000_p32000_p32600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p32000_p32700', 'Check that table partman_test.id_taptest_table_p30000_p32000_p32700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p32000_p32800', 'Check that table partman_test.id_taptest_table_p30000_p32000_p32800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p32000_p32900', 'Check that table partman_test.id_taptest_table_p30000_p32000_p32900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p33000_p33000', 'Check that table partman_test.id_taptest_table_p30000_p33000_p33000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p33000_p33100', 'Check that table partman_test.id_taptest_table_p30000_p33000_p33100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p33000_p33200', 'Check that table partman_test.id_taptest_table_p30000_p33000_p33200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p33000_p33300', 'Check that table partman_test.id_taptest_table_p30000_p33000_p33300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p33000_p33400', 'Check that table partman_test.id_taptest_table_p30000_p33000_p33400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p33000_p33500', 'Check that table partman_test.id_taptest_table_p30000_p33000_p33500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p33000_p33600', 'Check that table partman_test.id_taptest_table_p30000_p33000_p33600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p33000_p33700', 'Check that table partman_test.id_taptest_table_p30000_p33000_p33700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p33000_p33800', 'Check that table partman_test.id_taptest_table_p30000_p33000_p33800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p33000_p33900', 'Check that table partman_test.id_taptest_table_p30000_p33000_p33900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p34000_p34000', 'Check that table partman_test.id_taptest_table_p30000_p34000_p34000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p34000_p34100', 'Check that table partman_test.id_taptest_table_p30000_p34000_p34100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p34000_p34200', 'Check that table partman_test.id_taptest_table_p30000_p34000_p34200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p34000_p34300', 'Check that table partman_test.id_taptest_table_p30000_p34000_p34300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p34000_p34400', 'Check that table partman_test.id_taptest_table_p30000_p34000_p34400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p34000_p34500', 'Check that table partman_test.id_taptest_table_p30000_p34000_p34500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p34000_p34600', 'Check that table partman_test.id_taptest_table_p30000_p34000_p34600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p34000_p34700', 'Check that table partman_test.id_taptest_table_p30000_p34000_p34700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p34000_p34800', 'Check that table partman_test.id_taptest_table_p30000_p34000_p34800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p34000_p34900', 'Check that table partman_test.id_taptest_table_p30000_p34000_p34900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p35000_p35000', 'Check that table partman_test.id_taptest_table_p30000_p35000_p35000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p35000_p35100', 'Check that table partman_test.id_taptest_table_p30000_p35000_p35100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p35000_p35200', 'Check that table partman_test.id_taptest_table_p30000_p35000_p35200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p35000_p35300', 'Check that table partman_test.id_taptest_table_p30000_p35000_p35300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p35000_p35400', 'Check that table partman_test.id_taptest_table_p30000_p35000_p35400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p35000_p35500', 'Check that table partman_test.id_taptest_table_p30000_p35000_p35500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p35000_p35600', 'Check that table partman_test.id_taptest_table_p30000_p35000_p35600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p35000_p35700', 'Check that table partman_test.id_taptest_table_p30000_p35000_p35700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p35000_p35800', 'Check that table partman_test.id_taptest_table_p30000_p35000_p35800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p35000_p35900', 'Check that table partman_test.id_taptest_table_p30000_p35000_p35900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p36000_p36000', 'Check that table partman_test.id_taptest_table_p30000_p36000_p36000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p36000_p36100', 'Check that table partman_test.id_taptest_table_p30000_p36000_p36100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p36000_p36200', 'Check that table partman_test.id_taptest_table_p30000_p36000_p36200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p36000_p36300', 'Check that table partman_test.id_taptest_table_p30000_p36000_p36300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p36000_p36400', 'Check that table partman_test.id_taptest_table_p30000_p36000_p36400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p36000_p36500', 'Check that table partman_test.id_taptest_table_p30000_p36000_p36500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p36000_p36600', 'Check that table partman_test.id_taptest_table_p30000_p36000_p36600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p36000_p36700', 'Check that table partman_test.id_taptest_table_p30000_p36000_p36700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p36000_p36800', 'Check that table partman_test.id_taptest_table_p30000_p36000_p36800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p36000_p36900', 'Check that table partman_test.id_taptest_table_p30000_p36000_p36900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p37000_p37000', 'Check that table partman_test.id_taptest_table_p30000_p37000_p37000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p37000_p37100', 'Check that table partman_test.id_taptest_table_p30000_p37000_p37100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p37000_p37200', 'Check that table partman_test.id_taptest_table_p30000_p37000_p37200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p37000_p37300', 'Check that table partman_test.id_taptest_table_p30000_p37000_p37300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p37000_p37400', 'Check that table partman_test.id_taptest_table_p30000_p37000_p37400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p37000_p37500', 'Check that table partman_test.id_taptest_table_p30000_p37000_p37500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p37000_p37600', 'Check that table partman_test.id_taptest_table_p30000_p37000_p37600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p37000_p37700', 'Check that table partman_test.id_taptest_table_p30000_p37000_p37700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p37000_p37800', 'Check that table partman_test.id_taptest_table_p30000_p37000_p37800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p37000_p37900', 'Check that table partman_test.id_taptest_table_p30000_p37000_p37900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p38000_p38000', 'Check that table partman_test.id_taptest_table_p30000_p38000_p38000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p38000_p38100', 'Check that table partman_test.id_taptest_table_p30000_p38000_p38100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p38000_p38200', 'Check that table partman_test.id_taptest_table_p30000_p38000_p38200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p38000_p38300', 'Check that table partman_test.id_taptest_table_p30000_p38000_p38300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p38000_p38400', 'Check that table partman_test.id_taptest_table_p30000_p38000_p38400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p38000_p38500', 'Check that table partman_test.id_taptest_table_p30000_p38000_p38500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p38000_p38600', 'Check that table partman_test.id_taptest_table_p30000_p38000_p38600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p38000_p38700', 'Check that table partman_test.id_taptest_table_p30000_p38000_p38700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p38000_p38800', 'Check that table partman_test.id_taptest_table_p30000_p38000_p38800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p38000_p38900', 'Check that table partman_test.id_taptest_table_p30000_p38000_p38900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p39000_p39000', 'Check that table partman_test.id_taptest_table_p30000_p39000_p39000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p39000_p39100', 'Check that table partman_test.id_taptest_table_p30000_p39000_p39100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p39000_p39200', 'Check that table partman_test.id_taptest_table_p30000_p39000_p39200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p39000_p39300', 'Check that table partman_test.id_taptest_table_p30000_p39000_p39300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p39000_p39400', 'Check that table partman_test.id_taptest_table_p30000_p39000_p39400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p39000_p39500', 'Check that table partman_test.id_taptest_table_p30000_p39000_p39500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p39000_p39600', 'Check that table partman_test.id_taptest_table_p30000_p39000_p39600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p39000_p39700', 'Check that table partman_test.id_taptest_table_p30000_p39000_p39700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p39000_p39800', 'Check that table partman_test.id_taptest_table_p30000_p39000_p39800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p39000_p39900', 'Check that table partman_test.id_taptest_table_p30000_p39000_p39900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p40000_p40000', 'Check that table partman_test.id_taptest_table_p40000_p40000_p40000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p40000_p40100', 'Check that table partman_test.id_taptest_table_p40000_p40000_p40100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p40000_p40200', 'Check that table partman_test.id_taptest_table_p40000_p40000_p40200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p40000_p40300', 'Check that table partman_test.id_taptest_table_p40000_p40000_p40300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p40000_p40400', 'Check that table partman_test.id_taptest_table_p40000_p40000_p40400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p40000_p40500', 'Check that table partman_test.id_taptest_table_p40000_p40000_p40500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p40000_p40600', 'Check that table partman_test.id_taptest_table_p40000_p40000_p40600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p40000_p40700', 'Check that table partman_test.id_taptest_table_p40000_p40000_p40700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p40000_p40800', 'Check that table partman_test.id_taptest_table_p40000_p40000_p40800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p40000_p40900', 'Check that table partman_test.id_taptest_table_p40000_p40000_p40900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p41000_p41000', 'Check that table partman_test.id_taptest_table_p40000_p41000_p41000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p41000_p41100', 'Check that table partman_test.id_taptest_table_p40000_p41000_p41100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p41000_p41200', 'Check that table partman_test.id_taptest_table_p40000_p41000_p41200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p41000_p41300', 'Check that table partman_test.id_taptest_table_p40000_p41000_p41300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p41000_p41400', 'Check that table partman_test.id_taptest_table_p40000_p41000_p41400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p41000_p41500', 'Check that table partman_test.id_taptest_table_p40000_p41000_p41500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p41000_p41600', 'Check that table partman_test.id_taptest_table_p40000_p41000_p41600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p41000_p41700', 'Check that table partman_test.id_taptest_table_p40000_p41000_p41700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p41000_p41800', 'Check that table partman_test.id_taptest_table_p40000_p41000_p41800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p41000_p41900', 'Check that table partman_test.id_taptest_table_p40000_p41000_p41900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p42000_p42000', 'Check that table partman_test.id_taptest_table_p40000_p42000_p42000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p42000_p42100', 'Check that table partman_test.id_taptest_table_p40000_p42000_p42100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p42000_p42200', 'Check that table partman_test.id_taptest_table_p40000_p42000_p42200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p42000_p42300', 'Check that table partman_test.id_taptest_table_p40000_p42000_p42300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p42000_p42400', 'Check that table partman_test.id_taptest_table_p40000_p42000_p42400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p42000_p42500', 'Check that table partman_test.id_taptest_table_p40000_p42000_p42500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p42000_p42600', 'Check that table partman_test.id_taptest_table_p40000_p42000_p42600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p42000_p42700', 'Check that table partman_test.id_taptest_table_p40000_p42000_p42700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p42000_p42800', 'Check that table partman_test.id_taptest_table_p40000_p42000_p42800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p42000_p42900', 'Check that table partman_test.id_taptest_table_p40000_p42000_p42900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p43000_p43000', 'Check that table partman_test.id_taptest_table_p40000_p43000_p43000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p43000_p43100', 'Check that table partman_test.id_taptest_table_p40000_p43000_p43100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p43000_p43200', 'Check that table partman_test.id_taptest_table_p40000_p43000_p43200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p43000_p43300', 'Check that table partman_test.id_taptest_table_p40000_p43000_p43300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p43000_p43400', 'Check that table partman_test.id_taptest_table_p40000_p43000_p43400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p43000_p43500', 'Check that table partman_test.id_taptest_table_p40000_p43000_p43500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p43000_p43600', 'Check that table partman_test.id_taptest_table_p40000_p43000_p43600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p43000_p43700', 'Check that table partman_test.id_taptest_table_p40000_p43000_p43700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p43000_p43800', 'Check that table partman_test.id_taptest_table_p40000_p43000_p43800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p43000_p43900', 'Check that table partman_test.id_taptest_table_p40000_p43000_p43900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p44000_p44000', 'Check that table partman_test.id_taptest_table_p40000_p44000_p44000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p44000_p44100', 'Check that table partman_test.id_taptest_table_p40000_p44000_p44100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p44000_p44200', 'Check that table partman_test.id_taptest_table_p40000_p44000_p44200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p44000_p44300', 'Check that table partman_test.id_taptest_table_p40000_p44000_p44300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p44000_p44400', 'Check that table partman_test.id_taptest_table_p40000_p44000_p44400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p44000_p44500', 'Check that table partman_test.id_taptest_table_p40000_p44000_p44500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p44000_p44600', 'Check that table partman_test.id_taptest_table_p40000_p44000_p44600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p44000_p44700', 'Check that table partman_test.id_taptest_table_p40000_p44000_p44700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p44000_p44800', 'Check that table partman_test.id_taptest_table_p40000_p44000_p44800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p44000_p44900', 'Check that table partman_test.id_taptest_table_p40000_p44000_p44900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p45000_p45000', 'Check that table partman_test.id_taptest_table_p40000_p45000_p45000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p45000_p45100', 'Check that table partman_test.id_taptest_table_p40000_p45000_p45100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p45000_p45200', 'Check that table partman_test.id_taptest_table_p40000_p45000_p45200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p45000_p45300', 'Check that table partman_test.id_taptest_table_p40000_p45000_p45300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p45000_p45400', 'Check that table partman_test.id_taptest_table_p40000_p45000_p45400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p45000_p45500', 'Check that table partman_test.id_taptest_table_p40000_p45000_p45500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p45000_p45600', 'Check that table partman_test.id_taptest_table_p40000_p45000_p45600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p45000_p45700', 'Check that table partman_test.id_taptest_table_p40000_p45000_p45700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p45000_p45800', 'Check that table partman_test.id_taptest_table_p40000_p45000_p45800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p45000_p45900', 'Check that table partman_test.id_taptest_table_p40000_p45000_p45900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p46000_p46000', 'Check that table partman_test.id_taptest_table_p40000_p46000_p46000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p46000_p46100', 'Check that table partman_test.id_taptest_table_p40000_p46000_p46100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p46000_p46200', 'Check that table partman_test.id_taptest_table_p40000_p46000_p46200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p46000_p46300', 'Check that table partman_test.id_taptest_table_p40000_p46000_p46300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p46000_p46400', 'Check that table partman_test.id_taptest_table_p40000_p46000_p46400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p46000_p46500', 'Check that table partman_test.id_taptest_table_p40000_p46000_p46500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p46000_p46600', 'Check that table partman_test.id_taptest_table_p40000_p46000_p46600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p46000_p46700', 'Check that table partman_test.id_taptest_table_p40000_p46000_p46700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p46000_p46800', 'Check that table partman_test.id_taptest_table_p40000_p46000_p46800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p46000_p46900', 'Check that table partman_test.id_taptest_table_p40000_p46000_p46900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p47000_p47000', 'Check that table partman_test.id_taptest_table_p40000_p47000_p47000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p47000_p47100', 'Check that table partman_test.id_taptest_table_p40000_p47000_p47100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p47000_p47200', 'Check that table partman_test.id_taptest_table_p40000_p47000_p47200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p47000_p47300', 'Check that table partman_test.id_taptest_table_p40000_p47000_p47300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p47000_p47400', 'Check that table partman_test.id_taptest_table_p40000_p47000_p47400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p47000_p47500', 'Check that table partman_test.id_taptest_table_p40000_p47000_p47500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p47000_p47600', 'Check that table partman_test.id_taptest_table_p40000_p47000_p47600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p47000_p47700', 'Check that table partman_test.id_taptest_table_p40000_p47000_p47700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p47000_p47800', 'Check that table partman_test.id_taptest_table_p40000_p47000_p47800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p47000_p47900', 'Check that table partman_test.id_taptest_table_p40000_p47000_p47900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p48000_p48000', 'Check that table partman_test.id_taptest_table_p40000_p48000_p48000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p48000_p48100', 'Check that table partman_test.id_taptest_table_p40000_p48000_p48100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p48000_p48200', 'Check that table partman_test.id_taptest_table_p40000_p48000_p48200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p48000_p48300', 'Check that table partman_test.id_taptest_table_p40000_p48000_p48300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p48000_p48400', 'Check that table partman_test.id_taptest_table_p40000_p48000_p48400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p48000_p48500', 'Check that table partman_test.id_taptest_table_p40000_p48000_p48500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p48000_p48600', 'Check that table partman_test.id_taptest_table_p40000_p48000_p48600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p48000_p48700', 'Check that table partman_test.id_taptest_table_p40000_p48000_p48700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p48000_p48800', 'Check that table partman_test.id_taptest_table_p40000_p48000_p48800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p48000_p48900', 'Check that table partman_test.id_taptest_table_p40000_p48000_p48900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p49000_p49000', 'Check that table partman_test.id_taptest_table_p40000_p49000_p49000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p49000_p49100', 'Check that table partman_test.id_taptest_table_p40000_p49000_p49100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p49000_p49200', 'Check that table partman_test.id_taptest_table_p40000_p49000_p49200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p49000_p49300', 'Check that table partman_test.id_taptest_table_p40000_p49000_p49300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p49000_p49400', 'Check that table partman_test.id_taptest_table_p40000_p49000_p49400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p49000_p49500', 'Check that table partman_test.id_taptest_table_p40000_p49000_p49500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p49000_p49600', 'Check that table partman_test.id_taptest_table_p40000_p49000_p49600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p49000_p49700', 'Check that table partman_test.id_taptest_table_p40000_p49000_p49700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p49000_p49800', 'Check that table partman_test.id_taptest_table_p40000_p49000_p49800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p49000_p49900', 'Check that table partman_test.id_taptest_table_p40000_p49000_p49900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p50000_p50000', 'Check that table partman_test.id_taptest_table_p50000_p50000_p50000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p50000_p50100', 'Check that table partman_test.id_taptest_table_p50000_p50000_p50100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p50000_p50200', 'Check that table partman_test.id_taptest_table_p50000_p50000_p50200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p50000_p50300', 'Check that table partman_test.id_taptest_table_p50000_p50000_p50300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p50000_p50400', 'Check that table partman_test.id_taptest_table_p50000_p50000_p50400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p50000_p50500', 'Check that table partman_test.id_taptest_table_p50000_p50000_p50500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p50000_p50600', 'Check that table partman_test.id_taptest_table_p50000_p50000_p50600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p50000_p50700', 'Check that table partman_test.id_taptest_table_p50000_p50000_p50700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p50000_p50800', 'Check that table partman_test.id_taptest_table_p50000_p50000_p50800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p50000_p50900', 'Check that table partman_test.id_taptest_table_p50000_p50000_p50900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p51000_p51000', 'Check that table partman_test.id_taptest_table_p50000_p51000_p51000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p51000_p51100', 'Check that table partman_test.id_taptest_table_p50000_p51000_p51100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p51000_p51200', 'Check that table partman_test.id_taptest_table_p50000_p51000_p51200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p51000_p51300', 'Check that table partman_test.id_taptest_table_p50000_p51000_p51300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p51000_p51400', 'Check that table partman_test.id_taptest_table_p50000_p51000_p51400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p51000_p51500', 'Check that table partman_test.id_taptest_table_p50000_p51000_p51500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p51000_p51600', 'Check that table partman_test.id_taptest_table_p50000_p51000_p51600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p51000_p51700', 'Check that table partman_test.id_taptest_table_p50000_p51000_p51700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p51000_p51800', 'Check that table partman_test.id_taptest_table_p50000_p51000_p51800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p51000_p51900', 'Check that table partman_test.id_taptest_table_p50000_p51000_p51900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p52000_p52000', 'Check that table partman_test.id_taptest_table_p50000_p52000_p52000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p52000_p52100', 'Check that table partman_test.id_taptest_table_p50000_p52000_p52100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p52000_p52200', 'Check that table partman_test.id_taptest_table_p50000_p52000_p52200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p52000_p52300', 'Check that table partman_test.id_taptest_table_p50000_p52000_p52300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p52000_p52400', 'Check that table partman_test.id_taptest_table_p50000_p52000_p52400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p52000_p52500', 'Check that table partman_test.id_taptest_table_p50000_p52000_p52500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p52000_p52600', 'Check that table partman_test.id_taptest_table_p50000_p52000_p52600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p52000_p52700', 'Check that table partman_test.id_taptest_table_p50000_p52000_p52700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p52000_p52800', 'Check that table partman_test.id_taptest_table_p50000_p52000_p52800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p52000_p52900', 'Check that table partman_test.id_taptest_table_p50000_p52000_p52900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p53000_p53000', 'Check that table partman_test.id_taptest_table_p50000_p53000_p53000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p53000_p53100', 'Check that table partman_test.id_taptest_table_p50000_p53000_p53100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p53000_p53200', 'Check that table partman_test.id_taptest_table_p50000_p53000_p53200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p53000_p53300', 'Check that table partman_test.id_taptest_table_p50000_p53000_p53300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p53000_p53400', 'Check that table partman_test.id_taptest_table_p50000_p53000_p53400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p53000_p53500', 'Check that table partman_test.id_taptest_table_p50000_p53000_p53500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p53000_p53600', 'Check that table partman_test.id_taptest_table_p50000_p53000_p53600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p53000_p53700', 'Check that table partman_test.id_taptest_table_p50000_p53000_p53700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p53000_p53800', 'Check that table partman_test.id_taptest_table_p50000_p53000_p53800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p53000_p53900', 'Check that table partman_test.id_taptest_table_p50000_p53000_p53900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p54000_p54000', 'Check that table partman_test.id_taptest_table_p50000_p54000_p54000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p54000_p54100', 'Check that table partman_test.id_taptest_table_p50000_p54000_p54100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p54000_p54200', 'Check that table partman_test.id_taptest_table_p50000_p54000_p54200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p54000_p54300', 'Check that table partman_test.id_taptest_table_p50000_p54000_p54300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p54000_p54400', 'Check that table partman_test.id_taptest_table_p50000_p54000_p54400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p54000_p54500', 'Check that table partman_test.id_taptest_table_p50000_p54000_p54500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p54000_p54600', 'Check that table partman_test.id_taptest_table_p50000_p54000_p54600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p54000_p54700', 'Check that table partman_test.id_taptest_table_p50000_p54000_p54700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p54000_p54800', 'Check that table partman_test.id_taptest_table_p50000_p54000_p54800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p54000_p54900', 'Check that table partman_test.id_taptest_table_p50000_p54000_p54900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p55000_p55000', 'Check that table partman_test.id_taptest_table_p50000_p55000_p55000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p55000_p55100', 'Check that table partman_test.id_taptest_table_p50000_p55000_p55100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p55000_p55200', 'Check that table partman_test.id_taptest_table_p50000_p55000_p55200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p55000_p55300', 'Check that table partman_test.id_taptest_table_p50000_p55000_p55300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p55000_p55400', 'Check that table partman_test.id_taptest_table_p50000_p55000_p55400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p55000_p55500', 'Check that table partman_test.id_taptest_table_p50000_p55000_p55500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p55000_p55600', 'Check that table partman_test.id_taptest_table_p50000_p55000_p55600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p55000_p55700', 'Check that table partman_test.id_taptest_table_p50000_p55000_p55700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p55000_p55800', 'Check that table partman_test.id_taptest_table_p50000_p55000_p55800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p55000_p55900', 'Check that table partman_test.id_taptest_table_p50000_p55000_p55900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p56000_p56000', 'Check that table partman_test.id_taptest_table_p50000_p56000_p56000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p56000_p56100', 'Check that table partman_test.id_taptest_table_p50000_p56000_p56100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p56000_p56200', 'Check that table partman_test.id_taptest_table_p50000_p56000_p56200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p56000_p56300', 'Check that table partman_test.id_taptest_table_p50000_p56000_p56300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p56000_p56400', 'Check that table partman_test.id_taptest_table_p50000_p56000_p56400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p56000_p56500', 'Check that table partman_test.id_taptest_table_p50000_p56000_p56500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p56000_p56600', 'Check that table partman_test.id_taptest_table_p50000_p56000_p56600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p56000_p56700', 'Check that table partman_test.id_taptest_table_p50000_p56000_p56700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p56000_p56800', 'Check that table partman_test.id_taptest_table_p50000_p56000_p56800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p56000_p56900', 'Check that table partman_test.id_taptest_table_p50000_p56000_p56900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p57000_p57000', 'Check that table partman_test.id_taptest_table_p50000_p57000_p57000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p57000_p57100', 'Check that table partman_test.id_taptest_table_p50000_p57000_p57100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p57000_p57200', 'Check that table partman_test.id_taptest_table_p50000_p57000_p57200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p57000_p57300', 'Check that table partman_test.id_taptest_table_p50000_p57000_p57300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p57000_p57400', 'Check that table partman_test.id_taptest_table_p50000_p57000_p57400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p57000_p57500', 'Check that table partman_test.id_taptest_table_p50000_p57000_p57500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p57000_p57600', 'Check that table partman_test.id_taptest_table_p50000_p57000_p57600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p57000_p57700', 'Check that table partman_test.id_taptest_table_p50000_p57000_p57700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p57000_p57800', 'Check that table partman_test.id_taptest_table_p50000_p57000_p57800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p57000_p57900', 'Check that table partman_test.id_taptest_table_p50000_p57000_p57900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p58000_p58000', 'Check that table partman_test.id_taptest_table_p50000_p58000_p58000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p58000_p58100', 'Check that table partman_test.id_taptest_table_p50000_p58000_p58100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p58000_p58200', 'Check that table partman_test.id_taptest_table_p50000_p58000_p58200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p58000_p58300', 'Check that table partman_test.id_taptest_table_p50000_p58000_p58300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p58000_p58400', 'Check that table partman_test.id_taptest_table_p50000_p58000_p58400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p58000_p58500', 'Check that table partman_test.id_taptest_table_p50000_p58000_p58500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p58000_p58600', 'Check that table partman_test.id_taptest_table_p50000_p58000_p58600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p58000_p58700', 'Check that table partman_test.id_taptest_table_p50000_p58000_p58700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p58000_p58800', 'Check that table partman_test.id_taptest_table_p50000_p58000_p58800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p58000_p58900', 'Check that table partman_test.id_taptest_table_p50000_p58000_p58900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p59000_p59000', 'Check that table partman_test.id_taptest_table_p50000_p59000_p59000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p59000_p59100', 'Check that table partman_test.id_taptest_table_p50000_p59000_p59100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p59000_p59200', 'Check that table partman_test.id_taptest_table_p50000_p59000_p59200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p59000_p59300', 'Check that table partman_test.id_taptest_table_p50000_p59000_p59300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p59000_p59400', 'Check that table partman_test.id_taptest_table_p50000_p59000_p59400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p59000_p59500', 'Check that table partman_test.id_taptest_table_p50000_p59000_p59500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p59000_p59600', 'Check that table partman_test.id_taptest_table_p50000_p59000_p59600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p59000_p59700', 'Check that table partman_test.id_taptest_table_p50000_p59000_p59700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p59000_p59800', 'Check that table partman_test.id_taptest_table_p50000_p59000_p59800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p59000_p59900', 'Check that table partman_test.id_taptest_table_p50000_p59000_p59900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p60000_p60000', 'Check that table partman_test.id_taptest_table_p60000_p60000_p60000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p60000_p60100', 'Check that table partman_test.id_taptest_table_p60000_p60000_p60100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p60000_p60200', 'Check that table partman_test.id_taptest_table_p60000_p60000_p60200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p60000_p60300', 'Check that table partman_test.id_taptest_table_p60000_p60000_p60300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p60000_p60400', 'Check that table partman_test.id_taptest_table_p60000_p60000_p60400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p60000_p60500', 'Check that table partman_test.id_taptest_table_p60000_p60000_p60500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p60000_p60600', 'Check that table partman_test.id_taptest_table_p60000_p60000_p60600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p60000_p60700', 'Check that table partman_test.id_taptest_table_p60000_p60000_p60700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p60000_p60800', 'Check that table partman_test.id_taptest_table_p60000_p60000_p60800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p60000_p60900', 'Check that table partman_test.id_taptest_table_p60000_p60000_p60900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p61000_p61000', 'Check that table partman_test.id_taptest_table_p60000_p61000_p61000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p61000_p61100', 'Check that table partman_test.id_taptest_table_p60000_p61000_p61100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p61000_p61200', 'Check that table partman_test.id_taptest_table_p60000_p61000_p61200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p61000_p61300', 'Check that table partman_test.id_taptest_table_p60000_p61000_p61300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p61000_p61400', 'Check that table partman_test.id_taptest_table_p60000_p61000_p61400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p61000_p61500', 'Check that table partman_test.id_taptest_table_p60000_p61000_p61500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p61000_p61600', 'Check that table partman_test.id_taptest_table_p60000_p61000_p61600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p61000_p61700', 'Check that table partman_test.id_taptest_table_p60000_p61000_p61700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p61000_p61800', 'Check that table partman_test.id_taptest_table_p60000_p61000_p61800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p61000_p61900', 'Check that table partman_test.id_taptest_table_p60000_p61000_p61900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p62000_p62000', 'Check that table partman_test.id_taptest_table_p60000_p62000_p62000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p62000_p62100', 'Check that table partman_test.id_taptest_table_p60000_p62000_p62100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p62000_p62200', 'Check that table partman_test.id_taptest_table_p60000_p62000_p62200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p62000_p62300', 'Check that table partman_test.id_taptest_table_p60000_p62000_p62300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p62000_p62400', 'Check that table partman_test.id_taptest_table_p60000_p62000_p62400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p62000_p62500', 'Check that table partman_test.id_taptest_table_p60000_p62000_p62500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p62000_p62600', 'Check that table partman_test.id_taptest_table_p60000_p62000_p62600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p62000_p62700', 'Check that table partman_test.id_taptest_table_p60000_p62000_p62700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p62000_p62800', 'Check that table partman_test.id_taptest_table_p60000_p62000_p62800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p62000_p62900', 'Check that table partman_test.id_taptest_table_p60000_p62000_p62900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p63000_p63000', 'Check that table partman_test.id_taptest_table_p60000_p63000_p63000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p63000_p63100', 'Check that table partman_test.id_taptest_table_p60000_p63000_p63100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p63000_p63200', 'Check that table partman_test.id_taptest_table_p60000_p63000_p63200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p63000_p63300', 'Check that table partman_test.id_taptest_table_p60000_p63000_p63300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p63000_p63400', 'Check that table partman_test.id_taptest_table_p60000_p63000_p63400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p63000_p63500', 'Check that table partman_test.id_taptest_table_p60000_p63000_p63500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p63000_p63600', 'Check that table partman_test.id_taptest_table_p60000_p63000_p63600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p63000_p63700', 'Check that table partman_test.id_taptest_table_p60000_p63000_p63700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p63000_p63800', 'Check that table partman_test.id_taptest_table_p60000_p63000_p63800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p63000_p63900', 'Check that table partman_test.id_taptest_table_p60000_p63000_p63900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p64000_p64000', 'Check that table partman_test.id_taptest_table_p60000_p64000_p64000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p64000_p64100', 'Check that table partman_test.id_taptest_table_p60000_p64000_p64100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p64000_p64200', 'Check that table partman_test.id_taptest_table_p60000_p64000_p64200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p64000_p64300', 'Check that table partman_test.id_taptest_table_p60000_p64000_p64300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p64000_p64400', 'Check that table partman_test.id_taptest_table_p60000_p64000_p64400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p64000_p64500', 'Check that table partman_test.id_taptest_table_p60000_p64000_p64500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p64000_p64600', 'Check that table partman_test.id_taptest_table_p60000_p64000_p64600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p64000_p64700', 'Check that table partman_test.id_taptest_table_p60000_p64000_p64700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p64000_p64800', 'Check that table partman_test.id_taptest_table_p60000_p64000_p64800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p64000_p64900', 'Check that table partman_test.id_taptest_table_p60000_p64000_p64900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p65000_p65000', 'Check that table partman_test.id_taptest_table_p60000_p65000_p65000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p65000_p65100', 'Check that table partman_test.id_taptest_table_p60000_p65000_p65100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p65000_p65200', 'Check that table partman_test.id_taptest_table_p60000_p65000_p65200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p65000_p65300', 'Check that table partman_test.id_taptest_table_p60000_p65000_p65300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p65000_p65400', 'Check that table partman_test.id_taptest_table_p60000_p65000_p65400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p65000_p65500', 'Check that table partman_test.id_taptest_table_p60000_p65000_p65500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p65000_p65600', 'Check that table partman_test.id_taptest_table_p60000_p65000_p65600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p65000_p65700', 'Check that table partman_test.id_taptest_table_p60000_p65000_p65700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p65000_p65800', 'Check that table partman_test.id_taptest_table_p60000_p65000_p65800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p65000_p65900', 'Check that table partman_test.id_taptest_table_p60000_p65000_p65900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p66000_p66000', 'Check that table partman_test.id_taptest_table_p60000_p66000_p66000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p66000_p66100', 'Check that table partman_test.id_taptest_table_p60000_p66000_p66100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p66000_p66200', 'Check that table partman_test.id_taptest_table_p60000_p66000_p66200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p66000_p66300', 'Check that table partman_test.id_taptest_table_p60000_p66000_p66300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p66000_p66400', 'Check that table partman_test.id_taptest_table_p60000_p66000_p66400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p66000_p66500', 'Check that table partman_test.id_taptest_table_p60000_p66000_p66500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p66000_p66600', 'Check that table partman_test.id_taptest_table_p60000_p66000_p66600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p66000_p66700', 'Check that table partman_test.id_taptest_table_p60000_p66000_p66700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p66000_p66800', 'Check that table partman_test.id_taptest_table_p60000_p66000_p66800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p66000_p66900', 'Check that table partman_test.id_taptest_table_p60000_p66000_p66900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p67000_p67000', 'Check that table partman_test.id_taptest_table_p60000_p67000_p67000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p67000_p67100', 'Check that table partman_test.id_taptest_table_p60000_p67000_p67100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p67000_p67200', 'Check that table partman_test.id_taptest_table_p60000_p67000_p67200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p67000_p67300', 'Check that table partman_test.id_taptest_table_p60000_p67000_p67300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p67000_p67400', 'Check that table partman_test.id_taptest_table_p60000_p67000_p67400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p67000_p67500', 'Check that table partman_test.id_taptest_table_p60000_p67000_p67500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p67000_p67600', 'Check that table partman_test.id_taptest_table_p60000_p67000_p67600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p67000_p67700', 'Check that table partman_test.id_taptest_table_p60000_p67000_p67700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p67000_p67800', 'Check that table partman_test.id_taptest_table_p60000_p67000_p67800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p67000_p67900', 'Check that table partman_test.id_taptest_table_p60000_p67000_p67900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p68000_p68000', 'Check that table partman_test.id_taptest_table_p60000_p68000_p68000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p68000_p68100', 'Check that table partman_test.id_taptest_table_p60000_p68000_p68100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p68000_p68200', 'Check that table partman_test.id_taptest_table_p60000_p68000_p68200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p68000_p68300', 'Check that table partman_test.id_taptest_table_p60000_p68000_p68300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p68000_p68400', 'Check that table partman_test.id_taptest_table_p60000_p68000_p68400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p68000_p68500', 'Check that table partman_test.id_taptest_table_p60000_p68000_p68500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p68000_p68600', 'Check that table partman_test.id_taptest_table_p60000_p68000_p68600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p68000_p68700', 'Check that table partman_test.id_taptest_table_p60000_p68000_p68700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p68000_p68800', 'Check that table partman_test.id_taptest_table_p60000_p68000_p68800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p68000_p68900', 'Check that table partman_test.id_taptest_table_p60000_p68000_p68900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p69000_p69000', 'Check that table partman_test.id_taptest_table_p60000_p69000_p69000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p69000_p69100', 'Check that table partman_test.id_taptest_table_p60000_p69000_p69100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p69000_p69200', 'Check that table partman_test.id_taptest_table_p60000_p69000_p69200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p69000_p69300', 'Check that table partman_test.id_taptest_table_p60000_p69000_p69300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p69000_p69400', 'Check that table partman_test.id_taptest_table_p60000_p69000_p69400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p69000_p69500', 'Check that table partman_test.id_taptest_table_p60000_p69000_p69500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p69000_p69600', 'Check that table partman_test.id_taptest_table_p60000_p69000_p69600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p69000_p69700', 'Check that table partman_test.id_taptest_table_p60000_p69000_p69700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p69000_p69800', 'Check that table partman_test.id_taptest_table_p60000_p69000_p69800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p69000_p69900', 'Check that table partman_test.id_taptest_table_p60000_p69000_p69900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p70000_p70000', 'Check that table partman_test.id_taptest_table_p70000_p70000_p70000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p70000_p70100', 'Check that table partman_test.id_taptest_table_p70000_p70000_p70100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p70000_p70200', 'Check that table partman_test.id_taptest_table_p70000_p70000_p70200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p70000_p70300', 'Check that table partman_test.id_taptest_table_p70000_p70000_p70300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p70000_p70400', 'Check that table partman_test.id_taptest_table_p70000_p70000_p70400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p70000_p70500', 'Check that table partman_test.id_taptest_table_p70000_p70000_p70500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p70000_p70600', 'Check that table partman_test.id_taptest_table_p70000_p70000_p70600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p70000_p70700', 'Check that table partman_test.id_taptest_table_p70000_p70000_p70700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p70000_p70800', 'Check that table partman_test.id_taptest_table_p70000_p70000_p70800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p70000_p70900', 'Check that table partman_test.id_taptest_table_p70000_p70000_p70900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p71000_p71000', 'Check that table partman_test.id_taptest_table_p70000_p71000_p71000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p71000_p71100', 'Check that table partman_test.id_taptest_table_p70000_p71000_p71100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p71000_p71200', 'Check that table partman_test.id_taptest_table_p70000_p71000_p71200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p71000_p71300', 'Check that table partman_test.id_taptest_table_p70000_p71000_p71300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p71000_p71400', 'Check that table partman_test.id_taptest_table_p70000_p71000_p71400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p71000_p71500', 'Check that table partman_test.id_taptest_table_p70000_p71000_p71500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p71000_p71600', 'Check that table partman_test.id_taptest_table_p70000_p71000_p71600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p71000_p71700', 'Check that table partman_test.id_taptest_table_p70000_p71000_p71700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p71000_p71800', 'Check that table partman_test.id_taptest_table_p70000_p71000_p71800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p71000_p71900', 'Check that table partman_test.id_taptest_table_p70000_p71000_p71900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p72000_p72000', 'Check that table partman_test.id_taptest_table_p70000_p72000_p72000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p72000_p72100', 'Check that table partman_test.id_taptest_table_p70000_p72000_p72100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p72000_p72200', 'Check that table partman_test.id_taptest_table_p70000_p72000_p72200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p72000_p72300', 'Check that table partman_test.id_taptest_table_p70000_p72000_p72300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p72000_p72400', 'Check that table partman_test.id_taptest_table_p70000_p72000_p72400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p72000_p72500', 'Check that table partman_test.id_taptest_table_p70000_p72000_p72500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p72000_p72600', 'Check that table partman_test.id_taptest_table_p70000_p72000_p72600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p72000_p72700', 'Check that table partman_test.id_taptest_table_p70000_p72000_p72700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p72000_p72800', 'Check that table partman_test.id_taptest_table_p70000_p72000_p72800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p72000_p72900', 'Check that table partman_test.id_taptest_table_p70000_p72000_p72900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p73000_p73000', 'Check that table partman_test.id_taptest_table_p70000_p73000_p73000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p73000_p73100', 'Check that table partman_test.id_taptest_table_p70000_p73000_p73100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p73000_p73200', 'Check that table partman_test.id_taptest_table_p70000_p73000_p73200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p73000_p73300', 'Check that table partman_test.id_taptest_table_p70000_p73000_p73300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p73000_p73400', 'Check that table partman_test.id_taptest_table_p70000_p73000_p73400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p73000_p73500', 'Check that table partman_test.id_taptest_table_p70000_p73000_p73500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p73000_p73600', 'Check that table partman_test.id_taptest_table_p70000_p73000_p73600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p73000_p73700', 'Check that table partman_test.id_taptest_table_p70000_p73000_p73700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p73000_p73800', 'Check that table partman_test.id_taptest_table_p70000_p73000_p73800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p73000_p73900', 'Check that table partman_test.id_taptest_table_p70000_p73000_p73900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p74000_p74000', 'Check that table partman_test.id_taptest_table_p70000_p74000_p74000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p74000_p74100', 'Check that table partman_test.id_taptest_table_p70000_p74000_p74100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p74000_p74200', 'Check that table partman_test.id_taptest_table_p70000_p74000_p74200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p74000_p74300', 'Check that table partman_test.id_taptest_table_p70000_p74000_p74300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p74000_p74400', 'Check that table partman_test.id_taptest_table_p70000_p74000_p74400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p74000_p74500', 'Check that table partman_test.id_taptest_table_p70000_p74000_p74500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p74000_p74600', 'Check that table partman_test.id_taptest_table_p70000_p74000_p74600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p74000_p74700', 'Check that table partman_test.id_taptest_table_p70000_p74000_p74700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p74000_p74800', 'Check that table partman_test.id_taptest_table_p70000_p74000_p74800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p74000_p74900', 'Check that table partman_test.id_taptest_table_p70000_p74000_p74900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p75000_p75000', 'Check that table partman_test.id_taptest_table_p70000_p75000_p75000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p75000_p75100', 'Check that table partman_test.id_taptest_table_p70000_p75000_p75100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p75000_p75200', 'Check that table partman_test.id_taptest_table_p70000_p75000_p75200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p75000_p75300', 'Check that table partman_test.id_taptest_table_p70000_p75000_p75300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p75000_p75400', 'Check that table partman_test.id_taptest_table_p70000_p75000_p75400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p75000_p75500', 'Check that table partman_test.id_taptest_table_p70000_p75000_p75500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p75000_p75600', 'Check that table partman_test.id_taptest_table_p70000_p75000_p75600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p75000_p75700', 'Check that table partman_test.id_taptest_table_p70000_p75000_p75700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p75000_p75800', 'Check that table partman_test.id_taptest_table_p70000_p75000_p75800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p75000_p75900', 'Check that table partman_test.id_taptest_table_p70000_p75000_p75900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p76000_p76000', 'Check that table partman_test.id_taptest_table_p70000_p76000_p76000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p76000_p76100', 'Check that table partman_test.id_taptest_table_p70000_p76000_p76100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p76000_p76200', 'Check that table partman_test.id_taptest_table_p70000_p76000_p76200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p76000_p76300', 'Check that table partman_test.id_taptest_table_p70000_p76000_p76300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p76000_p76400', 'Check that table partman_test.id_taptest_table_p70000_p76000_p76400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p76000_p76500', 'Check that table partman_test.id_taptest_table_p70000_p76000_p76500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p76000_p76600', 'Check that table partman_test.id_taptest_table_p70000_p76000_p76600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p76000_p76700', 'Check that table partman_test.id_taptest_table_p70000_p76000_p76700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p76000_p76800', 'Check that table partman_test.id_taptest_table_p70000_p76000_p76800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p76000_p76900', 'Check that table partman_test.id_taptest_table_p70000_p76000_p76900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p77000_p77000', 'Check that table partman_test.id_taptest_table_p70000_p77000_p77000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p77000_p77100', 'Check that table partman_test.id_taptest_table_p70000_p77000_p77100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p77000_p77200', 'Check that table partman_test.id_taptest_table_p70000_p77000_p77200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p77000_p77300', 'Check that table partman_test.id_taptest_table_p70000_p77000_p77300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p77000_p77400', 'Check that table partman_test.id_taptest_table_p70000_p77000_p77400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p77000_p77500', 'Check that table partman_test.id_taptest_table_p70000_p77000_p77500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p77000_p77600', 'Check that table partman_test.id_taptest_table_p70000_p77000_p77600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p77000_p77700', 'Check that table partman_test.id_taptest_table_p70000_p77000_p77700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p77000_p77800', 'Check that table partman_test.id_taptest_table_p70000_p77000_p77800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p77000_p77900', 'Check that table partman_test.id_taptest_table_p70000_p77000_p77900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p78000_p78000', 'Check that table partman_test.id_taptest_table_p70000_p78000_p78000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p78000_p78100', 'Check that table partman_test.id_taptest_table_p70000_p78000_p78100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p78000_p78200', 'Check that table partman_test.id_taptest_table_p70000_p78000_p78200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p78000_p78300', 'Check that table partman_test.id_taptest_table_p70000_p78000_p78300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p78000_p78400', 'Check that table partman_test.id_taptest_table_p70000_p78000_p78400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p78000_p78500', 'Check that table partman_test.id_taptest_table_p70000_p78000_p78500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p78000_p78600', 'Check that table partman_test.id_taptest_table_p70000_p78000_p78600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p78000_p78700', 'Check that table partman_test.id_taptest_table_p70000_p78000_p78700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p78000_p78800', 'Check that table partman_test.id_taptest_table_p70000_p78000_p78800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p78000_p78900', 'Check that table partman_test.id_taptest_table_p70000_p78000_p78900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p79000_p79000', 'Check that table partman_test.id_taptest_table_p70000_p79000_p79000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p79000_p79100', 'Check that table partman_test.id_taptest_table_p70000_p79000_p79100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p79000_p79200', 'Check that table partman_test.id_taptest_table_p70000_p79000_p79200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p79000_p79300', 'Check that table partman_test.id_taptest_table_p70000_p79000_p79300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p79000_p79400', 'Check that table partman_test.id_taptest_table_p70000_p79000_p79400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p79000_p79500', 'Check that table partman_test.id_taptest_table_p70000_p79000_p79500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p79000_p79600', 'Check that table partman_test.id_taptest_table_p70000_p79000_p79600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p79000_p79700', 'Check that table partman_test.id_taptest_table_p70000_p79000_p79700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p79000_p79800', 'Check that table partman_test.id_taptest_table_p70000_p79000_p79800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p79000_p79900', 'Check that table partman_test.id_taptest_table_p70000_p79000_p79900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p80000_p80000', 'Check that table partman_test.id_taptest_table_p80000_p80000_p80000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p80000_p80100', 'Check that table partman_test.id_taptest_table_p80000_p80000_p80100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p80000_p80200', 'Check that table partman_test.id_taptest_table_p80000_p80000_p80200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p80000_p80300', 'Check that table partman_test.id_taptest_table_p80000_p80000_p80300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p80000_p80400', 'Check that table partman_test.id_taptest_table_p80000_p80000_p80400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p80000_p80500', 'Check that table partman_test.id_taptest_table_p80000_p80000_p80500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p80000_p80600', 'Check that table partman_test.id_taptest_table_p80000_p80000_p80600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p80000_p80700', 'Check that table partman_test.id_taptest_table_p80000_p80000_p80700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p80000_p80800', 'Check that table partman_test.id_taptest_table_p80000_p80000_p80800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p80000_p80900', 'Check that table partman_test.id_taptest_table_p80000_p80000_p80900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p81000_p81000', 'Check that table partman_test.id_taptest_table_p80000_p81000_p81000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p81000_p81100', 'Check that table partman_test.id_taptest_table_p80000_p81000_p81100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p81000_p81200', 'Check that table partman_test.id_taptest_table_p80000_p81000_p81200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p81000_p81300', 'Check that table partman_test.id_taptest_table_p80000_p81000_p81300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p81000_p81400', 'Check that table partman_test.id_taptest_table_p80000_p81000_p81400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p81000_p81500', 'Check that table partman_test.id_taptest_table_p80000_p81000_p81500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p81000_p81600', 'Check that table partman_test.id_taptest_table_p80000_p81000_p81600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p81000_p81700', 'Check that table partman_test.id_taptest_table_p80000_p81000_p81700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p81000_p81800', 'Check that table partman_test.id_taptest_table_p80000_p81000_p81800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p81000_p81900', 'Check that table partman_test.id_taptest_table_p80000_p81000_p81900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p82000_p82000', 'Check that table partman_test.id_taptest_table_p80000_p82000_p82000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p82000_p82100', 'Check that table partman_test.id_taptest_table_p80000_p82000_p82100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p82000_p82200', 'Check that table partman_test.id_taptest_table_p80000_p82000_p82200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p82000_p82300', 'Check that table partman_test.id_taptest_table_p80000_p82000_p82300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p82000_p82400', 'Check that table partman_test.id_taptest_table_p80000_p82000_p82400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p82000_p82500', 'Check that table partman_test.id_taptest_table_p80000_p82000_p82500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p82000_p82600', 'Check that table partman_test.id_taptest_table_p80000_p82000_p82600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p82000_p82700', 'Check that table partman_test.id_taptest_table_p80000_p82000_p82700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p82000_p82800', 'Check that table partman_test.id_taptest_table_p80000_p82000_p82800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p82000_p82900', 'Check that table partman_test.id_taptest_table_p80000_p82000_p82900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p83000_p83000', 'Check that table partman_test.id_taptest_table_p80000_p83000_p83000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p83000_p83100', 'Check that table partman_test.id_taptest_table_p80000_p83000_p83100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p83000_p83200', 'Check that table partman_test.id_taptest_table_p80000_p83000_p83200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p83000_p83300', 'Check that table partman_test.id_taptest_table_p80000_p83000_p83300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p83000_p83400', 'Check that table partman_test.id_taptest_table_p80000_p83000_p83400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p83000_p83500', 'Check that table partman_test.id_taptest_table_p80000_p83000_p83500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p83000_p83600', 'Check that table partman_test.id_taptest_table_p80000_p83000_p83600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p83000_p83700', 'Check that table partman_test.id_taptest_table_p80000_p83000_p83700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p83000_p83800', 'Check that table partman_test.id_taptest_table_p80000_p83000_p83800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p83000_p83900', 'Check that table partman_test.id_taptest_table_p80000_p83000_p83900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p84000_p84000', 'Check that table partman_test.id_taptest_table_p80000_p84000_p84000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p84000_p84100', 'Check that table partman_test.id_taptest_table_p80000_p84000_p84100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p84000_p84200', 'Check that table partman_test.id_taptest_table_p80000_p84000_p84200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p84000_p84300', 'Check that table partman_test.id_taptest_table_p80000_p84000_p84300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p84000_p84400', 'Check that table partman_test.id_taptest_table_p80000_p84000_p84400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p84000_p84500', 'Check that table partman_test.id_taptest_table_p80000_p84000_p84500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p84000_p84600', 'Check that table partman_test.id_taptest_table_p80000_p84000_p84600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p84000_p84700', 'Check that table partman_test.id_taptest_table_p80000_p84000_p84700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p84000_p84800', 'Check that table partman_test.id_taptest_table_p80000_p84000_p84800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p84000_p84900', 'Check that table partman_test.id_taptest_table_p80000_p84000_p84900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p85000_p85000', 'Check that table partman_test.id_taptest_table_p80000_p85000_p85000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p85000_p85100', 'Check that table partman_test.id_taptest_table_p80000_p85000_p85100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p85000_p85200', 'Check that table partman_test.id_taptest_table_p80000_p85000_p85200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p85000_p85300', 'Check that table partman_test.id_taptest_table_p80000_p85000_p85300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p85000_p85400', 'Check that table partman_test.id_taptest_table_p80000_p85000_p85400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p85000_p85500', 'Check that table partman_test.id_taptest_table_p80000_p85000_p85500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p85000_p85600', 'Check that table partman_test.id_taptest_table_p80000_p85000_p85600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p85000_p85700', 'Check that table partman_test.id_taptest_table_p80000_p85000_p85700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p85000_p85800', 'Check that table partman_test.id_taptest_table_p80000_p85000_p85800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p85000_p85900', 'Check that table partman_test.id_taptest_table_p80000_p85000_p85900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p86000_p86000', 'Check that table partman_test.id_taptest_table_p80000_p86000_p86000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p86000_p86100', 'Check that table partman_test.id_taptest_table_p80000_p86000_p86100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p86000_p86200', 'Check that table partman_test.id_taptest_table_p80000_p86000_p86200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p86000_p86300', 'Check that table partman_test.id_taptest_table_p80000_p86000_p86300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p86000_p86400', 'Check that table partman_test.id_taptest_table_p80000_p86000_p86400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p86000_p86500', 'Check that table partman_test.id_taptest_table_p80000_p86000_p86500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p86000_p86600', 'Check that table partman_test.id_taptest_table_p80000_p86000_p86600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p86000_p86700', 'Check that table partman_test.id_taptest_table_p80000_p86000_p86700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p86000_p86800', 'Check that table partman_test.id_taptest_table_p80000_p86000_p86800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p86000_p86900', 'Check that table partman_test.id_taptest_table_p80000_p86000_p86900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p87000_p87000', 'Check that table partman_test.id_taptest_table_p80000_p87000_p87000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p87000_p87100', 'Check that table partman_test.id_taptest_table_p80000_p87000_p87100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p87000_p87200', 'Check that table partman_test.id_taptest_table_p80000_p87000_p87200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p87000_p87300', 'Check that table partman_test.id_taptest_table_p80000_p87000_p87300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p87000_p87400', 'Check that table partman_test.id_taptest_table_p80000_p87000_p87400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p87000_p87500', 'Check that table partman_test.id_taptest_table_p80000_p87000_p87500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p87000_p87600', 'Check that table partman_test.id_taptest_table_p80000_p87000_p87600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p87000_p87700', 'Check that table partman_test.id_taptest_table_p80000_p87000_p87700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p87000_p87800', 'Check that table partman_test.id_taptest_table_p80000_p87000_p87800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p87000_p87900', 'Check that table partman_test.id_taptest_table_p80000_p87000_p87900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p88000_p88000', 'Check that table partman_test.id_taptest_table_p80000_p88000_p88000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p88000_p88100', 'Check that table partman_test.id_taptest_table_p80000_p88000_p88100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p88000_p88200', 'Check that table partman_test.id_taptest_table_p80000_p88000_p88200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p88000_p88300', 'Check that table partman_test.id_taptest_table_p80000_p88000_p88300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p88000_p88400', 'Check that table partman_test.id_taptest_table_p80000_p88000_p88400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p88000_p88500', 'Check that table partman_test.id_taptest_table_p80000_p88000_p88500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p88000_p88600', 'Check that table partman_test.id_taptest_table_p80000_p88000_p88600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p88000_p88700', 'Check that table partman_test.id_taptest_table_p80000_p88000_p88700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p88000_p88800', 'Check that table partman_test.id_taptest_table_p80000_p88000_p88800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p88000_p88900', 'Check that table partman_test.id_taptest_table_p80000_p88000_p88900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p89000_p89000', 'Check that table partman_test.id_taptest_table_p80000_p89000_p89000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p89000_p89100', 'Check that table partman_test.id_taptest_table_p80000_p89000_p89100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p89000_p89200', 'Check that table partman_test.id_taptest_table_p80000_p89000_p89200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p89000_p89300', 'Check that table partman_test.id_taptest_table_p80000_p89000_p89300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p89000_p89400', 'Check that table partman_test.id_taptest_table_p80000_p89000_p89400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p89000_p89500', 'Check that table partman_test.id_taptest_table_p80000_p89000_p89500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p89000_p89600', 'Check that table partman_test.id_taptest_table_p80000_p89000_p89600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p89000_p89700', 'Check that table partman_test.id_taptest_table_p80000_p89000_p89700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p89000_p89800', 'Check that table partman_test.id_taptest_table_p80000_p89000_p89800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p89000_p89900', 'Check that table partman_test.id_taptest_table_p80000_p89000_p89900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p90000_p90000', 'Check that table partman_test.id_taptest_table_p90000_p90000_p90000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p90000_p90100', 'Check that table partman_test.id_taptest_table_p90000_p90000_p90100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p90000_p90200', 'Check that table partman_test.id_taptest_table_p90000_p90000_p90200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p90000_p90300', 'Check that table partman_test.id_taptest_table_p90000_p90000_p90300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p90000_p90400', 'Check that table partman_test.id_taptest_table_p90000_p90000_p90400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p90000_p90500', 'Check that table partman_test.id_taptest_table_p90000_p90000_p90500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p90000_p90600', 'Check that table partman_test.id_taptest_table_p90000_p90000_p90600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p90000_p90700', 'Check that table partman_test.id_taptest_table_p90000_p90000_p90700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p90000_p90800', 'Check that table partman_test.id_taptest_table_p90000_p90000_p90800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p90000_p90900', 'Check that table partman_test.id_taptest_table_p90000_p90000_p90900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p91000_p91000', 'Check that table partman_test.id_taptest_table_p90000_p91000_p91000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p91000_p91100', 'Check that table partman_test.id_taptest_table_p90000_p91000_p91100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p91000_p91200', 'Check that table partman_test.id_taptest_table_p90000_p91000_p91200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p91000_p91300', 'Check that table partman_test.id_taptest_table_p90000_p91000_p91300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p91000_p91400', 'Check that table partman_test.id_taptest_table_p90000_p91000_p91400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p91000_p91500', 'Check that table partman_test.id_taptest_table_p90000_p91000_p91500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p91000_p91600', 'Check that table partman_test.id_taptest_table_p90000_p91000_p91600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p91000_p91700', 'Check that table partman_test.id_taptest_table_p90000_p91000_p91700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p91000_p91800', 'Check that table partman_test.id_taptest_table_p90000_p91000_p91800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p91000_p91900', 'Check that table partman_test.id_taptest_table_p90000_p91000_p91900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p92000_p92000', 'Check that table partman_test.id_taptest_table_p90000_p92000_p92000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p92000_p92100', 'Check that table partman_test.id_taptest_table_p90000_p92000_p92100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p92000_p92200', 'Check that table partman_test.id_taptest_table_p90000_p92000_p92200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p92000_p92300', 'Check that table partman_test.id_taptest_table_p90000_p92000_p92300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p92000_p92400', 'Check that table partman_test.id_taptest_table_p90000_p92000_p92400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p92000_p92500', 'Check that table partman_test.id_taptest_table_p90000_p92000_p92500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p92000_p92600', 'Check that table partman_test.id_taptest_table_p90000_p92000_p92600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p92000_p92700', 'Check that table partman_test.id_taptest_table_p90000_p92000_p92700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p92000_p92800', 'Check that table partman_test.id_taptest_table_p90000_p92000_p92800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p92000_p92900', 'Check that table partman_test.id_taptest_table_p90000_p92000_p92900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p93000_p93000', 'Check that table partman_test.id_taptest_table_p90000_p93000_p93000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p93000_p93100', 'Check that table partman_test.id_taptest_table_p90000_p93000_p93100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p93000_p93200', 'Check that table partman_test.id_taptest_table_p90000_p93000_p93200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p93000_p93300', 'Check that table partman_test.id_taptest_table_p90000_p93000_p93300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p93000_p93400', 'Check that table partman_test.id_taptest_table_p90000_p93000_p93400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p93000_p93500', 'Check that table partman_test.id_taptest_table_p90000_p93000_p93500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p93000_p93600', 'Check that table partman_test.id_taptest_table_p90000_p93000_p93600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p93000_p93700', 'Check that table partman_test.id_taptest_table_p90000_p93000_p93700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p93000_p93800', 'Check that table partman_test.id_taptest_table_p90000_p93000_p93800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p93000_p93900', 'Check that table partman_test.id_taptest_table_p90000_p93000_p93900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p94000_p94000', 'Check that table partman_test.id_taptest_table_p90000_p94000_p94000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p94000_p94100', 'Check that table partman_test.id_taptest_table_p90000_p94000_p94100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p94000_p94200', 'Check that table partman_test.id_taptest_table_p90000_p94000_p94200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p94000_p94300', 'Check that table partman_test.id_taptest_table_p90000_p94000_p94300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p94000_p94400', 'Check that table partman_test.id_taptest_table_p90000_p94000_p94400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p94000_p94500', 'Check that table partman_test.id_taptest_table_p90000_p94000_p94500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p94000_p94600', 'Check that table partman_test.id_taptest_table_p90000_p94000_p94600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p94000_p94700', 'Check that table partman_test.id_taptest_table_p90000_p94000_p94700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p94000_p94800', 'Check that table partman_test.id_taptest_table_p90000_p94000_p94800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p94000_p94900', 'Check that table partman_test.id_taptest_table_p90000_p94000_p94900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p95000_p95000', 'Check that table partman_test.id_taptest_table_p90000_p95000_p95000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p95000_p95100', 'Check that table partman_test.id_taptest_table_p90000_p95000_p95100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p95000_p95200', 'Check that table partman_test.id_taptest_table_p90000_p95000_p95200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p95000_p95300', 'Check that table partman_test.id_taptest_table_p90000_p95000_p95300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p95000_p95400', 'Check that table partman_test.id_taptest_table_p90000_p95000_p95400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p95000_p95500', 'Check that table partman_test.id_taptest_table_p90000_p95000_p95500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p95000_p95600', 'Check that table partman_test.id_taptest_table_p90000_p95000_p95600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p95000_p95700', 'Check that table partman_test.id_taptest_table_p90000_p95000_p95700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p95000_p95800', 'Check that table partman_test.id_taptest_table_p90000_p95000_p95800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p95000_p95900', 'Check that table partman_test.id_taptest_table_p90000_p95000_p95900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p96000_p96000', 'Check that table partman_test.id_taptest_table_p90000_p96000_p96000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p96000_p96100', 'Check that table partman_test.id_taptest_table_p90000_p96000_p96100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p96000_p96200', 'Check that table partman_test.id_taptest_table_p90000_p96000_p96200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p96000_p96300', 'Check that table partman_test.id_taptest_table_p90000_p96000_p96300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p96000_p96400', 'Check that table partman_test.id_taptest_table_p90000_p96000_p96400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p96000_p96500', 'Check that table partman_test.id_taptest_table_p90000_p96000_p96500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p96000_p96600', 'Check that table partman_test.id_taptest_table_p90000_p96000_p96600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p96000_p96700', 'Check that table partman_test.id_taptest_table_p90000_p96000_p96700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p96000_p96800', 'Check that table partman_test.id_taptest_table_p90000_p96000_p96800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p96000_p96900', 'Check that table partman_test.id_taptest_table_p90000_p96000_p96900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p97000_p97000', 'Check that table partman_test.id_taptest_table_p90000_p97000_p97000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p97000_p97100', 'Check that table partman_test.id_taptest_table_p90000_p97000_p97100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p97000_p97200', 'Check that table partman_test.id_taptest_table_p90000_p97000_p97200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p97000_p97300', 'Check that table partman_test.id_taptest_table_p90000_p97000_p97300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p97000_p97400', 'Check that table partman_test.id_taptest_table_p90000_p97000_p97400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p97000_p97500', 'Check that table partman_test.id_taptest_table_p90000_p97000_p97500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p97000_p97600', 'Check that table partman_test.id_taptest_table_p90000_p97000_p97600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p97000_p97700', 'Check that table partman_test.id_taptest_table_p90000_p97000_p97700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p97000_p97800', 'Check that table partman_test.id_taptest_table_p90000_p97000_p97800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p97000_p97900', 'Check that table partman_test.id_taptest_table_p90000_p97000_p97900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p98000_p98000', 'Check that table partman_test.id_taptest_table_p90000_p98000_p98000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p98000_p98100', 'Check that table partman_test.id_taptest_table_p90000_p98000_p98100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p98000_p98200', 'Check that table partman_test.id_taptest_table_p90000_p98000_p98200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p98000_p98300', 'Check that table partman_test.id_taptest_table_p90000_p98000_p98300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p98000_p98400', 'Check that table partman_test.id_taptest_table_p90000_p98000_p98400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p98000_p98500', 'Check that table partman_test.id_taptest_table_p90000_p98000_p98500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p98000_p98600', 'Check that table partman_test.id_taptest_table_p90000_p98000_p98600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p98000_p98700', 'Check that table partman_test.id_taptest_table_p90000_p98000_p98700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p98000_p98800', 'Check that table partman_test.id_taptest_table_p90000_p98000_p98800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p98000_p98900', 'Check that table partman_test.id_taptest_table_p90000_p98000_p98900 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p99000_p99000', 'Check that table partman_test.id_taptest_table_p90000_p99000_p99000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p99000_p99100', 'Check that table partman_test.id_taptest_table_p90000_p99000_p99100 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p99000_p99200', 'Check that table partman_test.id_taptest_table_p90000_p99000_p99200 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p99000_p99300', 'Check that table partman_test.id_taptest_table_p90000_p99000_p99300 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p99000_p99400', 'Check that table partman_test.id_taptest_table_p90000_p99000_p99400 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p99000_p99500', 'Check that table partman_test.id_taptest_table_p90000_p99000_p99500 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p99000_p99600', 'Check that table partman_test.id_taptest_table_p90000_p99000_p99600 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p99000_p99700', 'Check that table partman_test.id_taptest_table_p90000_p99000_p99700 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p99000_p99800', 'Check that table partman_test.id_taptest_table_p90000_p99000_p99800 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p99000_p99900', 'Check that table partman_test.id_taptest_table_p90000_p99000_p99900 no longer exists'); -- Recheck that all parents partitions have their data SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table is still empty'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[107500], 'Check count from parent table'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p0', 'Check that parent table id_taptest_table_p0 is still empty '); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p0_p0', ARRAY[999], 'Check count from id_taptest_table_p0_p0'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p0_p1000', ARRAY[1000], 'Check count from id_taptest_table_p0_p1000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p0_p2000', ARRAY[1000], 'Check count from id_taptest_table_p0_p2000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p0_p3000', ARRAY[1000], 'Check count from id_taptest_table_p0_p3000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p0_p4000', ARRAY[1000], 'Check count from id_taptest_table_p0_p4000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p0_p5000', ARRAY[1000], 'Check count from id_taptest_table_p0_p5000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p0_p6000', ARRAY[1000], 'Check count from id_taptest_table_p0_p6000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p0_p7000', ARRAY[1000], 'Check count from id_taptest_table_p0_p7000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p0_p8000', ARRAY[1000], 'Check count from id_taptest_table_p0_p8000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p0_p9000', ARRAY[1000], 'Check count from id_taptest_table_p0_p9000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10000', 'Check that parent table id_taptest_table_p10000 is still empty '); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p10000_p10000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p10000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p10000_p11000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p11000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p10000_p12000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p12000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p10000_p13000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p13000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p10000_p14000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p14000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p10000_p15000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p15000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p10000_p16000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p16000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p10000_p17000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p17000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p10000_p18000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p18000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p10000_p19000', ARRAY[1000], 'Check count from id_taptest_table_p10000_p19000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p20000', 'Check that parent table id_taptest_table_p20000 is still empty '); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p20000_p20000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p20000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p20000_p21000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p21000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p20000_p22000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p22000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p20000_p23000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p23000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p20000_p24000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p24000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p20000_p25000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p25000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p20000_p26000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p26000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p20000_p27000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p27000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p20000_p28000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p28000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p20000_p29000', ARRAY[1000], 'Check count from id_taptest_table_p20000_p29000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p30000', 'Check that parent table id_taptest_table_p30000 is still empty '); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p30000_p30000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p30000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p30000_p31000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p31000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p30000_p32000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p32000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p30000_p33000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p33000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p30000_p34000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p34000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p30000_p35000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p35000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p30000_p36000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p36000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p30000_p37000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p37000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p30000_p38000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p38000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p30000_p39000', ARRAY[1000], 'Check count from id_taptest_table_p30000_p39000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p40000', 'Check that parent table id_taptest_table_p40000 is still empty '); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p40000_p40000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p40000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p40000_p41000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p41000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p40000_p42000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p42000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p40000_p43000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p43000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p40000_p44000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p44000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p40000_p45000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p45000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p40000_p46000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p46000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p40000_p47000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p47000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p40000_p48000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p48000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p40000_p49000', ARRAY[1000], 'Check count from id_taptest_table_p40000_p49000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p50000', 'Check that parent table id_taptest_table_p50000 is still empty '); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p50000_p50000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p50000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p50000_p51000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p51000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p50000_p52000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p52000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p50000_p53000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p53000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p50000_p54000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p54000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p50000_p55000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p55000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p50000_p56000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p56000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p50000_p57000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p57000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p50000_p58000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p58000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p50000_p59000', ARRAY[1000], 'Check count from id_taptest_table_p50000_p59000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p60000', 'Check that parent table id_taptest_table_p60000 is still empty '); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p60000_p60000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p60000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p60000_p61000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p61000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p60000_p62000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p62000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p60000_p63000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p63000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p60000_p64000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p64000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p60000_p65000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p65000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p60000_p66000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p66000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p60000_p67000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p67000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p60000_p68000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p68000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p60000_p69000', ARRAY[1000], 'Check count from id_taptest_table_p60000_p69000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p70000', 'Check that parent table id_taptest_table_p70000 is still empty '); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p70000_p70000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p70000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p70000_p71000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p71000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p70000_p72000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p72000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p70000_p73000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p73000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p70000_p74000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p74000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p70000_p75000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p75000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p70000_p76000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p76000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p70000_p77000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p77000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p70000_p78000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p78000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p70000_p79000', ARRAY[1000], 'Check count from id_taptest_table_p70000_p79000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p80000', 'Check that parent table id_taptest_table_p80000 is still empty '); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p80000_p80000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p80000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p80000_p81000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p81000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p80000_p82000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p82000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p80000_p83000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p83000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p80000_p84000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p84000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p80000_p85000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p85000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p80000_p86000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p86000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p80000_p87000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p87000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p80000_p88000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p88000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p80000_p89000', ARRAY[1000], 'Check count from id_taptest_table_p80000_p89000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p90000', 'Check that parent table id_taptest_table_p90000 is still empty '); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p90000_p90000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p90000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p90000_p91000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p91000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p90000_p92000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p92000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p90000_p93000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p93000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p90000_p94000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p94000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p90000_p95000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p95000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p90000_p96000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p96000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p90000_p97000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p97000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p90000_p98000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p98000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p90000_p99000', ARRAY[1000], 'Check count from id_taptest_table_p90000_p99000'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p100000', 'Check that parent table id_taptest_table_p100000 is still empty '); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p100000_p100000', ARRAY[1000], 'Check count from id_taptest_table_p100000_p100000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p100000_p101000', ARRAY[1000], 'Check count from id_taptest_table_p100000_p100000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p100000_p102000', ARRAY[1000], 'Check count from id_taptest_table_p100000_p100000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p100000_p103000', ARRAY[1000], 'Check count from id_taptest_table_p100000_p100000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p100000_p104000', ARRAY[1000], 'Check count from id_taptest_table_p100000_p100000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p100000_p105000', ARRAY[1000], 'Check count from id_taptest_table_p100000_p100000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p100000_p106000', ARRAY[1000], 'Check count from id_taptest_table_p100000_p100000'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_p100000_p107000', ARRAY[501], 'Check count from id_taptest_table_p100000_p100000'); -- Undo sub-parent partitions SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p0'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[9999], 'Undo partitioning for parent table partman_test.id_taptest_table_p0'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p10000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[10000], 'Undo partitioning for parent table partman_test.id_taptest_table_p10000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p20000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[10000], 'Undo partitioning for parent table partman_test.id_taptest_table_p20000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p30000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[10000], 'Undo partitioning for parent table partman_test.id_taptest_table_p30000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p40000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[10000], 'Undo partitioning for parent table partman_test.id_taptest_table_p40000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p50000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[10000], 'Undo partitioning for parent table partman_test.id_taptest_table_p50000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p60000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[10000], 'Undo partitioning for parent table partman_test.id_taptest_table_p60000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p70000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[10000], 'Undo partitioning for parent table partman_test.id_taptest_table_p70000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p80000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[10000], 'Undo partitioning for parent table partman_test.id_taptest_table_p80000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p90000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[10000], 'Undo partitioning for parent table partman_test.id_taptest_table_p90000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p100000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[7501], 'Undo partitioning for parent table partman_test.id_taptest_table_p100000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p110000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p110000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p120000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p120000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p130000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p130000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p140000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p140000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p150000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p150000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p160000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p160000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p170000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p170000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p180000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p180000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p190000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p190000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p200000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p200000'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table_p210000'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[0], 'Undo partitioning for parent table partman_test.id_taptest_table_p210000'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p0', 'Check that table partman_test.id_taptest_table_p0_p0 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p1000', 'Check that table partman_test.id_taptest_table_p0_p1000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p2000', 'Check that table partman_test.id_taptest_table_p0_p2000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p3000', 'Check that table partman_test.id_taptest_table_p0_p3000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p4000', 'Check that table partman_test.id_taptest_table_p0_p4000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p5000', 'Check that table partman_test.id_taptest_table_p0_p5000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p6000', 'Check that table partman_test.id_taptest_table_p0_p6000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p7000', 'Check that table partman_test.id_taptest_table_p0_p7000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p8000', 'Check that table partman_test.id_taptest_table_p0_p8000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p0_p9000', 'Check that table partman_test.id_taptest_table_p0_p9000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p100000', 'Check that table partman_test.id_taptest_table_p100000_p100000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p101000', 'Check that table partman_test.id_taptest_table_p100000_p101000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p102000', 'Check that table partman_test.id_taptest_table_p100000_p102000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p103000', 'Check that table partman_test.id_taptest_table_p100000_p103000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p104000', 'Check that table partman_test.id_taptest_table_p100000_p104000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p105000', 'Check that table partman_test.id_taptest_table_p100000_p105000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p106000', 'Check that table partman_test.id_taptest_table_p100000_p106000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p107000', 'Check that table partman_test.id_taptest_table_p100000_p107000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p108000', 'Check that table partman_test.id_taptest_table_p100000_p108000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000_p109000', 'Check that table partman_test.id_taptest_table_p100000_p109000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p10000', 'Check that table partman_test.id_taptest_table_p10000_p10000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p11000', 'Check that table partman_test.id_taptest_table_p10000_p11000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p12000', 'Check that table partman_test.id_taptest_table_p10000_p12000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p13000', 'Check that table partman_test.id_taptest_table_p10000_p13000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p14000', 'Check that table partman_test.id_taptest_table_p10000_p14000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p15000', 'Check that table partman_test.id_taptest_table_p10000_p15000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p16000', 'Check that table partman_test.id_taptest_table_p10000_p16000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p17000', 'Check that table partman_test.id_taptest_table_p10000_p17000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p18000', 'Check that table partman_test.id_taptest_table_p10000_p18000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000_p19000', 'Check that table partman_test.id_taptest_table_p10000_p19000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p110000_p110000', 'Check that table partman_test.id_taptest_table_p110000_p110000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p120000_p120000', 'Check that table partman_test.id_taptest_table_p120000_p120000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p130000_p130000', 'Check that table partman_test.id_taptest_table_p130000_p130000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p140000_p140000', 'Check that table partman_test.id_taptest_table_p140000_p140000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p150000_p150000', 'Check that table partman_test.id_taptest_table_p150000_p150000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p160000_p160000', 'Check that table partman_test.id_taptest_table_p160000_p160000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p170000_p170000', 'Check that table partman_test.id_taptest_table_p170000_p170000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p180000_p180000', 'Check that table partman_test.id_taptest_table_p180000_p180000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p190000_p190000', 'Check that table partman_test.id_taptest_table_p190000_p190000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p200000_p200000', 'Check that table partman_test.id_taptest_table_p200000_p200000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p20000', 'Check that table partman_test.id_taptest_table_p20000_p20000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p21000', 'Check that table partman_test.id_taptest_table_p20000_p21000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p22000', 'Check that table partman_test.id_taptest_table_p20000_p22000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p23000', 'Check that table partman_test.id_taptest_table_p20000_p23000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p24000', 'Check that table partman_test.id_taptest_table_p20000_p24000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p25000', 'Check that table partman_test.id_taptest_table_p20000_p25000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p26000', 'Check that table partman_test.id_taptest_table_p20000_p26000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p27000', 'Check that table partman_test.id_taptest_table_p20000_p27000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p28000', 'Check that table partman_test.id_taptest_table_p20000_p28000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000_p29000', 'Check that table partman_test.id_taptest_table_p20000_p29000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p210000_p210000', 'Check that table partman_test.id_taptest_table_p210000_p210000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p30000', 'Check that table partman_test.id_taptest_table_p30000_p30000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p31000', 'Check that table partman_test.id_taptest_table_p30000_p31000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p32000', 'Check that table partman_test.id_taptest_table_p30000_p32000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p33000', 'Check that table partman_test.id_taptest_table_p30000_p33000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p34000', 'Check that table partman_test.id_taptest_table_p30000_p34000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p35000', 'Check that table partman_test.id_taptest_table_p30000_p35000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p36000', 'Check that table partman_test.id_taptest_table_p30000_p36000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p37000', 'Check that table partman_test.id_taptest_table_p30000_p37000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p38000', 'Check that table partman_test.id_taptest_table_p30000_p38000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000_p39000', 'Check that table partman_test.id_taptest_table_p30000_p39000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p40000', 'Check that table partman_test.id_taptest_table_p40000_p40000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p41000', 'Check that table partman_test.id_taptest_table_p40000_p41000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p42000', 'Check that table partman_test.id_taptest_table_p40000_p42000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p43000', 'Check that table partman_test.id_taptest_table_p40000_p43000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p44000', 'Check that table partman_test.id_taptest_table_p40000_p44000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p45000', 'Check that table partman_test.id_taptest_table_p40000_p45000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p46000', 'Check that table partman_test.id_taptest_table_p40000_p46000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p47000', 'Check that table partman_test.id_taptest_table_p40000_p47000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p48000', 'Check that table partman_test.id_taptest_table_p40000_p48000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000_p49000', 'Check that table partman_test.id_taptest_table_p40000_p49000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p50000', 'Check that table partman_test.id_taptest_table_p50000_p50000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p51000', 'Check that table partman_test.id_taptest_table_p50000_p51000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p52000', 'Check that table partman_test.id_taptest_table_p50000_p52000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p53000', 'Check that table partman_test.id_taptest_table_p50000_p53000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p54000', 'Check that table partman_test.id_taptest_table_p50000_p54000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p55000', 'Check that table partman_test.id_taptest_table_p50000_p55000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p56000', 'Check that table partman_test.id_taptest_table_p50000_p56000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p57000', 'Check that table partman_test.id_taptest_table_p50000_p57000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p58000', 'Check that table partman_test.id_taptest_table_p50000_p58000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000_p59000', 'Check that table partman_test.id_taptest_table_p50000_p59000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p60000', 'Check that table partman_test.id_taptest_table_p60000_p60000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p61000', 'Check that table partman_test.id_taptest_table_p60000_p61000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p62000', 'Check that table partman_test.id_taptest_table_p60000_p62000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p63000', 'Check that table partman_test.id_taptest_table_p60000_p63000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p64000', 'Check that table partman_test.id_taptest_table_p60000_p64000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p65000', 'Check that table partman_test.id_taptest_table_p60000_p65000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p66000', 'Check that table partman_test.id_taptest_table_p60000_p66000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p67000', 'Check that table partman_test.id_taptest_table_p60000_p67000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p68000', 'Check that table partman_test.id_taptest_table_p60000_p68000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000_p69000', 'Check that table partman_test.id_taptest_table_p60000_p69000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p70000', 'Check that table partman_test.id_taptest_table_p70000_p70000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p71000', 'Check that table partman_test.id_taptest_table_p70000_p71000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p72000', 'Check that table partman_test.id_taptest_table_p70000_p72000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p73000', 'Check that table partman_test.id_taptest_table_p70000_p73000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p74000', 'Check that table partman_test.id_taptest_table_p70000_p74000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p75000', 'Check that table partman_test.id_taptest_table_p70000_p75000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p76000', 'Check that table partman_test.id_taptest_table_p70000_p76000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p77000', 'Check that table partman_test.id_taptest_table_p70000_p77000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p78000', 'Check that table partman_test.id_taptest_table_p70000_p78000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000_p79000', 'Check that table partman_test.id_taptest_table_p70000_p79000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p80000', 'Check that table partman_test.id_taptest_table_p80000_p80000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p81000', 'Check that table partman_test.id_taptest_table_p80000_p81000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p82000', 'Check that table partman_test.id_taptest_table_p80000_p82000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p83000', 'Check that table partman_test.id_taptest_table_p80000_p83000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p84000', 'Check that table partman_test.id_taptest_table_p80000_p84000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p85000', 'Check that table partman_test.id_taptest_table_p80000_p85000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p86000', 'Check that table partman_test.id_taptest_table_p80000_p86000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p87000', 'Check that table partman_test.id_taptest_table_p80000_p87000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p88000', 'Check that table partman_test.id_taptest_table_p80000_p88000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000_p89000', 'Check that table partman_test.id_taptest_table_p80000_p89000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p90000', 'Check that table partman_test.id_taptest_table_p90000_p90000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p91000', 'Check that table partman_test.id_taptest_table_p90000_p91000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p92000', 'Check that table partman_test.id_taptest_table_p90000_p92000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p93000', 'Check that table partman_test.id_taptest_table_p90000_p93000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p94000', 'Check that table partman_test.id_taptest_table_p90000_p94000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p95000', 'Check that table partman_test.id_taptest_table_p90000_p95000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p96000', 'Check that table partman_test.id_taptest_table_p90000_p96000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p97000', 'Check that table partman_test.id_taptest_table_p90000_p97000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p98000', 'Check that table partman_test.id_taptest_table_p90000_p98000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000_p99000', 'Check that table partman_test.id_taptest_table_p90000_p99000 no longer exists'); SELECT results_eq('SELECT rows_undone::int FROM undo_partition_native(''partman_test.id_taptest_table'', ''partman_test.undo_taptest'', 20, p_keep_table := false)::int', ARRAY[107500], 'Undo partitioning for final parent table partman_test.id_taptest_table'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table', ARRAY[107500], 'Check count from final, non-partitioned table'); SELECT hasnt_table('partman_test.id_taptest_table_p0', 'Check that table partman_test.id_taptest_table_p0 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p10000', 'Check that table partman_test.id_taptest_table_p10000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p100000', 'Check that table partman_test.id_taptest_table_p100000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p110000', 'Check that table partman_test.id_taptest_table_p110000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p120000', 'Check that table partman_test.id_taptest_table_p120000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p130000', 'Check that table partman_test.id_taptest_table_p130000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p140000', 'Check that table partman_test.id_taptest_table_p140000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p150000', 'Check that table partman_test.id_taptest_table_p150000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p160000', 'Check that table partman_test.id_taptest_table_p160000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p170000', 'Check that table partman_test.id_taptest_table_p170000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p180000', 'Check that table partman_test.id_taptest_table_p180000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p190000', 'Check that table partman_test.id_taptest_table_p190000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p20000', 'Check that table partman_test.id_taptest_table_p20000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p200000', 'Check that table partman_test.id_taptest_table_p200000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p210000', 'Check that table partman_test.id_taptest_table_p210000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p30000', 'Check that table partman_test.id_taptest_table_p30000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p40000', 'Check that table partman_test.id_taptest_table_p40000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p50000', 'Check that table partman_test.id_taptest_table_p50000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p60000', 'Check that table partman_test.id_taptest_table_p60000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p70000', 'Check that table partman_test.id_taptest_table_p70000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p80000', 'Check that table partman_test.id_taptest_table_p80000 no longer exists'); SELECT hasnt_table('partman_test.id_taptest_table_p90000', 'Check that table partman_test.id_taptest_table_p90000 no longer exists'); SELECT is_empty('SELECT * FROM part_config WHERE parent_table LIKE ''partman_test.id_taptest_table%''', 'Check that part_config has all test tables removed'); SELECT is_empty('SELECT * FROM part_config_sub WHERE sub_parent LIKE ''partman_test.id_taptest_table%''', 'Check that part_config_sub has all test tables removed'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test_native/not_working_yet/test-time-time-time-subpart-native.sql000066400000000000000000001016231412761706100316740ustar00rootroot00000000000000-- ########## NATIVE TIME PARENT / TIME SUBPARENT / TIME SUB-SUB-PARENT DYNAMIC ########## -- Currently tests 23, 39, 47 & 67 may fail around new years boundaries -- Additional tests: Make sure option for handling exceptions in triggers works, test stop_sub_partition() \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(79); CREATE SCHEMA partman_test; CREATE TABLE partman_test.time_taptest_table (col1 int primary key, col2 text, col3 timestamptz NOT NULL DEFAULT now()); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); -- yearly SELECT create_parent('partman_test.time_taptest_table', 'col3', 'native', 'yearly', p_premake := 2); -- Make sure optimize values can be different UPDATE part_config SET optimize_trigger = 5, optimize_constraint = 10, trigger_exception_handling = true WHERE parent_table = 'partman_test.time_taptest_table'; SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||' does not exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 years'::interval, 'YYYY')||' does not exist'); -- Move data from parent SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table'')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved'); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that yearly 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'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')); -- monthly SELECT create_sub_parent('partman_test.time_taptest_table', 'col3', 'partman', 'monthly', p_premake := 2); UPDATE part_config_sub SET sub_trigger_exception_handling = true WHERE sub_parent = 'partman_test.time_taptest_table'; -- Make sure optimize values can be different UPDATE part_config_sub SET sub_optimize_trigger = 5, sub_optimize_constraint = 10, sub_retention_keep_table = false WHERE sub_parent = 'partman_test.time_taptest_table'; SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 month'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'2 months'::interval, 'YYYY_MM')||' exists'); -- Next test starts failing in Oct since the next year's partition was made and must contain one child table (january) SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM')||' does not exist (this test may fail around year boundary. See comment in test code)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'1 month'::interval, 'YYYY_MM')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'2 months'::interval, 'YYYY_MM')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'3 months'::interval, 'YYYY_MM')||' does not exist'); -- Check that previous and future years had the minimal partition made -- year +/- 1 tests may fail around year boundary. Tables may or may not exist depending on premake. That's fine. Should be ok for further in the future. SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')||'_01', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')||'_01 exists (this test may fail around year boundary. See comment in test code)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')||'_02', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')||'_02 does not exists (this test may fail around year boundary. See comment in test code)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_01', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_01 exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_02', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_02 does not exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_01', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_01 exists (this test may fail around year boundary. See comment in test code)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_02', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_02 exists (this test may fail around year boundary. See comment in test code)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_01', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'2 year'::interval, 'YYYY')||'_01 exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_02', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'2 year'::interval, 'YYYY')||'_02 exists'); SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||''')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved (yearly subparent)'); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY'), 'Check data got moved out of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')); -- Check subpart config SELECT results_eq('SELECT sub_parent FROM part_config_sub ORDER BY sub_parent', ARRAY['partman_test.time_taptest_table'], 'Check that part_config_sub has all tables configured as needed'); -- daily SELECT results_eq('SELECT create_sub_parent(''partman_test.time_taptest_table_p''||to_char(CURRENT_TIMESTAMP, ''YYYY''), ''col3'', ''partman'', ''daily'', p_premake := 2)', ARRAY[true], 'Subpartitioning partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||' should return true'); UPDATE part_config_sub SET sub_trigger_exception_handling = true WHERE sub_parent = 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY'); SELECT results_eq('SELECT create_sub_parent(''partman_test.time_taptest_table_p''||to_char(CURRENT_TIMESTAMP+''1 year''::interval, ''YYYY''), ''col3'', ''partman'', ''daily'', p_premake := 2)', ARRAY[true], 'Subpartitioning partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY')||' should return true'); UPDATE part_config_sub SET sub_trigger_exception_handling = true WHERE sub_parent = 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY'); SELECT results_eq('SELECT create_sub_parent(''partman_test.time_taptest_table_p''||to_char(CURRENT_TIMESTAMP+''2 years''::interval, ''YYYY''), ''col3'', ''partman'', ''daily'', p_premake := 2)', ARRAY[true], 'Subpartitioning partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||' should return true'); UPDATE part_config_sub SET sub_trigger_exception_handling = true WHERE sub_parent = 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY'); SELECT results_eq('SELECT create_sub_parent(''partman_test.time_taptest_table_p''||to_char(CURRENT_TIMESTAMP-''1 year''::interval, ''YYYY''), ''col3'', ''partman'', ''daily'', p_premake := 2)', ARRAY[true], 'Subpartitioning partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||' should return true'); UPDATE part_config_sub SET sub_trigger_exception_handling = true WHERE sub_parent = 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY'); SELECT results_eq('SELECT create_sub_parent(''partman_test.time_taptest_table_p''||to_char(CURRENT_TIMESTAMP-''2 years''::interval, ''YYYY''), ''col3'', ''partman'', ''daily'', p_premake := 2)', ARRAY[true], 'Subpartitioning partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||' should return true'); UPDATE part_config_sub SET sub_trigger_exception_handling = true WHERE sub_parent = 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')||' exists')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM')||'_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')||'_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' does not exists'); -- This test may fail around the end of the year or the end of some months since the minimal partition for the next year or next month was created. That's fine SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' does not exist (this test may fail around year or month boundaries. See comment in test code)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM')||'_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')||'_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); -- Check that previous and future years had the minimal partition made -- year +/- 1 tests may fail around year boundary. Tables may or may not exist depending on premake. That's fine. Should be ok for further in the future. SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 years'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP+'1 years'::interval, 'YYYY')||'_01_01', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 years'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP+'1 years'::interval, 'YYYY')||'_01_01 exists (this test may fail around year boundary. See comment in test code)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 years'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP+'1 years'::interval, 'YYYY')||'_01_02', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 years'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP+'1 years'::interval, 'YYYY')||'_01_02 does not exists (this test may fail around year boundary. See comment in test code)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_01_01', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_01_01 exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_01_02', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')||'_01_02 does not exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_01_01', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_01_01 exists (this test may fail around year boundary. See comment in test code)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_01_02', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY')||'_01_02 exists (this test may fail around year boundary. See comment in test code)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_01_01', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'2 year'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_01_01 exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_01_02', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP-'2 year'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY')||'_01_02 does not exists'); SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')||''')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved (monthly subparent)'); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM'), 'Check data got moved out of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); -- Check subpart config SELECT results_eq('SELECT sub_parent FROM part_config_sub ORDER BY sub_parent', ARRAY['partman_test.time_taptest_table', 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY'), 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY'), 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY'), 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY'), 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY')], 'Check that part_config_sub has all tables configured as needed'); INSERT INTO partman_test.time_taptest_table (col1, col2, col3) VALUES (generate_series(11,20), 'stuff', CURRENT_TIMESTAMP+'1 day'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check new data did not go into parent time_taptest_table'); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day', 'YYYY'), 'Check new data did not go into subparent time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day', 'YYYY')); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day', 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 day', 'YYYY_MM'), 'Check new data did not go into subparent time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day', 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 day', 'YYYY_MM')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day', 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 day', 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP+'1 day', 'YYYY_MM_DD'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day', 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'1 day', 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP+'1 day', 'YYYY_MM_DD')); UPDATE part_config SET premake = 3 WHERE parent_table LIKE 'partman_test.time_taptest_table%' AND partition_type = 'partman'; SELECT run_maintenance(); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||' exists'); -- This test may fail if the next year's subpartition was already made and +3 months is into the next year, which may have been made by the condition that guarantees there is always at least 1 child table in a subpartition. SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 months'::interval, 'YYYY_MM')||' exists. This test may fail in months near the end of the year.'); -- This test may fail if the next month's subpartition was already made and +3 days is after the 1st of the month, which may have been made by the condition that guarantees there is always at least 1 child table in a subpartition. SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' exists. This test may fail depending on the day of the month it is run.'); -- Check that future year had the minimal partition made SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_01', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_01 exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_01_01', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_01_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_01_01 exists'); -- Check subpart config SELECT results_eq('SELECT sub_parent FROM part_config_sub ORDER BY sub_parent', ARRAY['partman_test.time_taptest_table', 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 years'::interval, 'YYYY'), 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 year'::interval, 'YYYY'), 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY'), 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 year'::interval, 'YYYY'), 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 years'::interval, 'YYYY'), 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')], 'Check that part_config_sub has all tables configured as needed'); INSERT INTO partman_test.time_taptest_table (col1, col2, col3) VALUES (generate_series(21,30), 'stuff', CURRENT_TIMESTAMP+'3 years'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check new data did not go into parent time_taptest_table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY')||'. Data should have gone here since monthly subpartition for it does not exist. This test may fail in January since that monthly partition should exist.'); -- Move data from yearly parent table and create appropriate monthly child for it SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||''')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'. This test may fail in January since that monthly partition should exist.'); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY'), 'Check new data did not go into subparent time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY_MM'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY_MM')||'. Data should have gone here since daily subpartition for it does not exist.'); -- Move data from monthly parent table and create appropriate daily child for it SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY_MM')||''')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY_MM')); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY_MM'), 'Check new data did not go into subparent time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY_MM')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY_MM_DD'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY_MM')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years', 'YYYY_MM_DD')); -- Check that stopping subpartitioning works SELECT stop_sub_partition('partman_test.time_taptest_table'); SELECT run_maintenance(); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY')||' does exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'4 year'::interval, 'YYYY')||'_01', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'4 year'::interval, 'YYYY')||'_01 does not exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY')||' does exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'5 year'::interval, 'YYYY')||'_01', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'5 year'::interval, 'YYYY')||'_01 does not exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY')||' does exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'6 year'::interval, 'YYYY')||'_01', 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'6 year'::interval, 'YYYY')||'_01 does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 years'::interval, 'YYYY')||' does not exist'); /* -- Disabled test for now. New years makes testing undo functions hard. Would be calling undo on year+1_01 twice and second one would fail. SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[30], 'Check count from top parent'); SELECT throws_ok('SELECT undo_partition_time(''partman_test.time_taptest_table_p''||to_char(CURRENT_TIMESTAMP+''3 years''::interval, ''YYYY''), 20, p_keep_table := false)', 'P0001', 'Child table for this parent has child table(s) itself (partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_p'||to_char(CURRENT_TIMESTAMP+'3 years'::interval, 'YYYY')||'_01). Run undo partitioning on this table or remove inheritance first to ensure all data is properly moved to parent CONTEXT: SQL statement "SELECT undo_partition_time(''partman_test.time_taptest_table_p''||to_char(CURRENT_TIMESTAMP+''3 years''::interval, ''YYYY''), 20, p_keep_table := false)" PL/pgSQL function throws_ok(text,character,text,text) line 16 at EXECUTE statement DETAIL: HINT: ', 'Check that undoing partitions is prevented if subpartitions still exist'); */ SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test_native/test-dump-definition.sql000066400000000000000000000174501412761706100237620ustar00rootroot00000000000000\set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(7); -- Sanity check that new features get adding to definition dumping. SELECT bag_eq( E'SELECT attname FROM pg_catalog.pg_attribute a JOIN pg_catalog.pg_class c ON a.attrelid = c.oid WHERE c.relname = ''part_config'' AND NOT attisdropped AND attnum > 0', ARRAY[ 'parent_table', 'control', 'partition_type', 'partition_interval', 'constraint_cols', 'premake', 'optimize_trigger', 'optimize_constraint', 'epoch', 'inherit_fk', 'retention', 'retention_schema', 'retention_keep_table', 'retention_keep_index', 'infinite_time_partitions', 'datetime_string', 'automatic_maintenance', 'jobmon', 'sub_partition_set_full', 'undo_in_progress', 'trigger_exception_handling', 'upsert', 'trigger_return_null', 'template_table', 'publications', 'inherit_privileges', 'constraint_valid', 'subscription_refresh', 'drop_cascade_fk' ]::TEXT[], 'When adding a new column to part_config please ensure it is also added to the dump_partitioned_table_definition function' ); -- Create a partman non-declarative partitioned table. CREATE TABLE public.objects(id SERIAL PRIMARY KEY, t TEXT, created_at TIMESTAMP NOT NULL); SELECT partman.create_parent('public.objects', 'created_at', 'partman', 'weekly', p_premake := 2); -- Update config options you can't set at initial creation. UPDATE partman.part_config SET retention='5 weeks', retention_keep_table = 'f', infinite_time_partitions = 't' WHERE parent_table = 'public.objects'; -- Test output "visually". -- Note that spaces before each line are literal tabs (\t), not spaces SELECT is( (SELECT partman.dump_partitioned_table_definition('public.objects')), E'SELECT partman.create_parent( p_parent_table := ''public.objects'', p_control := ''created_at'', p_type := ''partman'', p_interval := ''weekly'', p_constraint_cols := NULL, p_premake := 2, p_automatic_maintenance := ''on'', p_inherit_fk := ''t'', p_epoch := ''none'', p_upsert := '''', p_publications := NULL, p_trigger_return_null := ''t'', p_template_table := NULL, p_jobmon := ''t'' -- v_start_partition is intentionally ignored as there -- isn''t any obviously correct definition. ); UPDATE partman.part_config SET optimize_trigger = 4, optimize_constraint = 30, retention = ''5 weeks'', retention_schema = NULL, retention_keep_table = ''f'', retention_keep_index = ''t'', infinite_time_partitions = ''t'', datetime_string = ''IYYY"w"IW'', sub_partition_set_full = ''f'', trigger_exception_handling = ''f'', inherit_privileges = ''t'', constraint_valid = ''t'', subscription_refresh = NULL WHERE parent_table = ''public.objects'';' ); -- Test end to end: -- 1. Capture the current config. SELECT part_config AS objects_part_config FROM partman.part_config WHERE parent_table = 'public.objects' \gset SELECT partman.dump_partitioned_table_definition('public.objects') AS var_sql \gset -- 2. Remove partitioning and recreate table. SELECT partman.undo_partition('public.objects', p_keep_table := false); DROP TABLE public.objects; CREATE TABLE public.objects(id SERIAL PRIMARY KEY, t TEXT, created_at TIMESTAMP NOT NULL); SELECT is((SELECT 1 FROM partman.part_config WHERE parent_table = 'public.objects'), NULL); -- 3. Run dumped config. SELECT :'var_sql' \gexec -- 4. Check the current config (it should match step 1). SELECT row_eq( 'SELECT * FROM partman.part_config WHERE parent_table = ''public.objects''', (:'objects_part_config')::partman.part_config ); -- Create a partman declarative partitioned table. CREATE TABLE public.declarative_objects( id SERIAL, t TEXT, created_at TIMESTAMP NOT NULL ) PARTITION BY RANGE (created_at); SELECT partman.create_parent('public.declarative_objects', 'created_at', 'native', 'weekly', p_premake := 2, p_start_partition := (NOW() - '4 weeks'::INTERVAL)::TEXT); -- Update config options you can't set at initial creation. UPDATE partman.part_config SET retention='5 weeks', retention_keep_table = 'f', infinite_time_partitions = 't', constraint_valid = 'f', inherit_privileges = 't' WHERE parent_table = 'public.declarative_objects'; -- Test output "visually" (with p_ignore_template_table = true). SELECT partman.dump_partitioned_table_definition('public.declarative_objects', p_ignore_template_table := true); -- -- -- Test output "visually" (with default p_ignore_template_table = false). -- -- Note that spaces before each line are literal tabs (\t), not spaces SELECT is( (SELECT partman.dump_partitioned_table_definition('public.declarative_objects')), E'SELECT partman.create_parent( p_parent_table := ''public.declarative_objects'', p_control := ''created_at'', p_type := ''native'', p_interval := ''7 days'', p_constraint_cols := NULL, p_premake := 2, p_automatic_maintenance := ''on'', p_inherit_fk := ''t'', p_epoch := ''none'', p_upsert := '''', p_publications := NULL, p_trigger_return_null := ''t'', p_template_table := ''partman.template_public_declarative_objects'', p_jobmon := ''t'' -- v_start_partition is intentionally ignored as there -- isn''t any obviously correct definition. ); UPDATE partman.part_config SET optimize_trigger = 4, optimize_constraint = 30, retention = ''5 weeks'', retention_schema = NULL, retention_keep_table = ''f'', retention_keep_index = ''t'', infinite_time_partitions = ''t'', datetime_string = ''IYYY"w"IW'', sub_partition_set_full = ''f'', trigger_exception_handling = ''f'', inherit_privileges = ''t'', constraint_valid = ''f'', subscription_refresh = NULL WHERE parent_table = ''public.declarative_objects'';' ); -- Test end to end (with p_ignore_template_table = true): -- 1. Capture the current config. SELECT part_config AS declarative_objects_part_config FROM partman.part_config WHERE parent_table = 'public.declarative_objects' \gset SELECT partman.dump_partitioned_table_definition('public.declarative_objects', p_ignore_template_table := true) AS var_sql \gset -- 2. Remove partitioning and recreate table. CREATE TABLE public.old_declarative_objects(id SERIAL, t TEXT, created_at TIMESTAMP NOT NULL); SELECT partman.undo_partition('public.declarative_objects', p_keep_table := false, p_target_table := 'public.old_declarative_objects'); DROP TABLE public.declarative_objects; CREATE TABLE public.declarative_objects( id SERIAL, t TEXT, created_at TIMESTAMP NOT NULL ) PARTITION BY RANGE (created_at); -- 3. Run dumped config. SELECT :'var_sql' \gexec -- 4. Check the current config (it should match step 1). SELECT row_eq( 'SELECT * FROM partman.part_config WHERE parent_table = ''public.declarative_objects''', (:'declarative_objects_part_config')::partman.part_config ); -- Test end to end (with default p_ignore_template_table = false): -- 1. Capture the current config. SELECT part_config AS declarative_objects_part_config FROM partman.part_config WHERE parent_table = 'public.declarative_objects' \gset SELECT partman.dump_partitioned_table_definition('public.declarative_objects') AS var_sql \gset -- 2. Remove partitioning and recreate table. SELECT partman.undo_partition('public.declarative_objects', p_keep_table := false, p_target_table := 'public.old_declarative_objects'); DROP TABLE public.declarative_objects; CREATE TABLE public.declarative_objects( id SERIAL, t TEXT, created_at TIMESTAMP NOT NULL ) PARTITION BY RANGE (created_at); -- 3. Run dumped config (after creating template table). CREATE TABLE partman.template_public_declarative_objects( id SERIAL, t TEXT, created_at TIMESTAMP NOT NULL ); SELECT :'var_sql' \gexec -- 4. Check the current config (it should match step 1). SELECT row_eq( 'SELECT * FROM partman.part_config WHERE parent_table = ''public.declarative_objects''', (:'declarative_objects_part_config')::partman.part_config ); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test_native/test-id-gap-fill.sql000066400000000000000000000204631412761706100227520ustar00rootroot00000000000000-- ########## ID TESTS ########## -- Additional tests: backfill gap in child tables, start with higher number -- Test using a pre-created template table and passing to create_parent. Should allow indexes to be made for initial children. \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(44); CREATE SCHEMA partman_test; CREATE TABLE partman_test.id_taptest_table ( col1 bigint , col2 text not null default 'stuff' , col3 timestamptz DEFAULT now() , col4 text) PARTITION BY RANGE (col1); -- Template table CREATE UNLOGGED TABLE partman_test.template_id_taptest_table (LIKE partman_test.id_taptest_table); DO $pg11_objects_check$ BEGIN IF current_setting('server_version_num')::int >= 110000 THEN -- Create on parent table ALTER TABLE partman_test.id_taptest_table ADD PRIMARY KEY (col1); CREATE INDEX ON partman_test.id_taptest_table (col3); ELSE -- Create on template table ALTER TABLE partman_test.template_id_taptest_table ADD PRIMARY KEY (col1); END IF; END $pg11_objects_check$; -- Always create the index on the template also so that we can test excluding duplicates. CREATE INDEX ON partman_test.template_id_taptest_table (col3); -- Regular unique indexes do not work on native in PG11 if the partition key isn't included CREATE UNIQUE INDEX ON partman_test.template_id_taptest_table (col4); SELECT create_parent('partman_test.id_taptest_table', 'col1', 'native', '10', p_jobmon := false, p_start_partition := '3000000000', p_template_table := 'partman_test.template_id_taptest_table'); INSERT INTO partman_test.id_taptest_table (col1, col4) VALUES (generate_series(3000000001,3000000009), 'stuff'||generate_series(3000000001,3000000009)); SELECT has_table('partman_test', 'id_taptest_table_p3000000000', 'Check id_taptest_table_p3000000000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p3000000010', 'Check id_taptest_table_p3000000010 exists'); SELECT has_table('partman_test', 'id_taptest_table_p3000000020', 'Check id_taptest_table_p3000000020 exists'); SELECT has_table('partman_test', 'id_taptest_table_p3000000030', 'Check id_taptest_table_p3000000030 exists'); SELECT has_table('partman_test', 'id_taptest_table_p3000000040', 'Check id_taptest_table_p3000000040 exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p3000000050', 'Check id_taptest_table_p3000000050 doesn''t exists yet'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000000', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000000'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000010', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000010'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000020', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000020'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000030', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000030'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000040', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000040'); SELECT is_indexed('partman_test', 'id_taptest_table_p3000000000', 'col4', 'Check that unique index was inherited to id_taptest_table_p3000000000'); SELECT is_indexed('partman_test', 'id_taptest_table_p3000000010', 'col4', 'Check that unique index was inherited to id_taptest_table_p3000000010'); SELECT is_indexed('partman_test', 'id_taptest_table_p3000000020', 'col4', 'Check that unique index was inherited to id_taptest_table_p3000000020'); SELECT is_indexed('partman_test', 'id_taptest_table_p3000000030', 'col4', 'Check that unique index was inherited to id_taptest_table_p3000000030'); SELECT is_indexed('partman_test', 'id_taptest_table_p3000000040', 'col4', 'Check that unique index was inherited to id_taptest_table_p3000000040'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[9], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p3000000000', ARRAY[9], 'Check count from id_taptest_table_p3000000000'); SELECT run_maintenance(); INSERT INTO partman_test.id_taptest_table (col1, col4) VALUES (generate_series(3000000010,3000000025), 'stuff'||generate_series(3000000010,3000000025)); -- Run again to make new partition based on latest data SELECT run_maintenance(); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p3000000010', ARRAY[10], 'Check count from id_taptest_table_p3000000010'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p3000000020', ARRAY[6], 'Check count from id_taptest_table_p3000000020'); SELECT has_table('partman_test', 'id_taptest_table_p3000000050', 'Check id_taptest_table_p3000000050 exists'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p3000000050''::regclass', ARRAY['u'], 'Check that id_taptest_table_p3000000050 is unlogged'); SELECT has_table('partman_test', 'id_taptest_table_p3000000060', 'Check id_taptest_table_p3000000060 exists yet'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p3000000060''::regclass', ARRAY['u'], 'Check that id_taptest_table_p3000000060 is unlogged'); SELECT hasnt_table('partman_test', 'id_taptest_table_p3000000070', 'Check id_taptest_table_p3000000070 doesn''t exists yet'); INSERT INTO partman_test.id_taptest_table (col1, col4) VALUES (generate_series(3000000026,3000000038), 'stuff'||generate_series(3000000026,3000000038)); SELECT run_maintenance(); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[38], 'Check count from id_taptest_table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p3000000020', ARRAY[10], 'Check count from id_taptest_table_p3000000020'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p3000000030', ARRAY[9], 'Check count from id_taptest_table_p3000000030'); SELECT has_table('partman_test', 'id_taptest_table_p3000000070', 'Check id_taptest_table_p3000000070 exists'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p3000000070''::regclass', ARRAY['u'], 'Check that id_taptest_table_p3000000070 is unlogged'); SELECT hasnt_table('partman_test', 'id_taptest_table_p3000000080', 'Check id_taptest_table_p3000000080 doesn''t exists yet'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000060', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000060'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000070', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000070'); DROP TABLE partman_test.id_taptest_table_p3000000020; DROP TABLE partman_test.id_taptest_table_p3000000040; DROP TABLE partman_test.id_taptest_table_p3000000050; DROP TABLE partman_test.id_taptest_table_p3000000060; SELECT hasnt_table('partman_test', 'id_taptest_table_p3000000020', 'Check id_taptest_table_p3000000020 was dropped'); SELECT hasnt_table('partman_test', 'id_taptest_table_p3000000040', 'Check id_taptest_table_p3000000040 was dropped'); SELECT hasnt_table('partman_test', 'id_taptest_table_p3000000050', 'Check id_taptest_table_p3000000050 was dropped'); SELECT hasnt_table('partman_test', 'id_taptest_table_p3000000060', 'Check id_taptest_table_p3000000060 was dropped'); SELECT partman.partition_gap_fill('partman_test.id_taptest_table'); SELECT has_table('partman_test', 'id_taptest_table_p3000000020', 'Check id_taptest_table_p3000000020 was recreated'); SELECT has_table('partman_test', 'id_taptest_table_p3000000040', 'Check id_taptest_table_p3000000040 was recreated'); SELECT has_table('partman_test', 'id_taptest_table_p3000000050', 'Check id_taptest_table_p3000000050 was recreated'); SELECT has_table('partman_test', 'id_taptest_table_p3000000060', 'Check id_taptest_table_p3000000060 was recreated'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test_native/test-id-native-mixed-case.sql000066400000000000000000000612041412761706100245600ustar00rootroot00000000000000-- ########## ID DYNAMIC TESTS ########## -- Additional tests: turn off pg_jobmon logging, UNLOGGED, PUBLIC role, start with higher number, inherit FK, inherit privileges -- Test using a pre-created template table and passing to create_parent. Should allow indexes to be made for initial children. -- Tests that foreign keys and normal indexes for PG10 use the template and for PG11 they use the parent. Also since this is id partitioning, we can use the partition key for primary key, so that should work from parent on PG11 as well. \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(120); CREATE SCHEMA "Partman_Test"; CREATE SCHEMA "Partman_Retention_Test"; CREATE ROLE "Partman_Basic"; CREATE ROLE "Partman_Revoke"; CREATE ROLE "Partman_Owner"; CREATE TABLE "Partman_Test"."Fk_Test_Reference" ("Col2" text unique not null); INSERT INTO "Partman_Test"."Fk_Test_Reference" VALUES ('stuff'); CREATE UNLOGGED TABLE "Partman_Test"."ID_Taptest_Table" ( "Col1" bigint , "Col2" text not null default 'stuff' , "Col3" timestamptz DEFAULT now() , "Col4" text) PARTITION BY RANGE ("Col1"); CREATE TABLE "Partman_Test"."Undo_Taptest" (LIKE "Partman_Test"."ID_Taptest_Table" INCLUDING ALL); GRANT SELECT,INSERT,UPDATE ON "Partman_Test"."ID_Taptest_Table" TO "Partman_Basic", PUBLIC; GRANT ALL ON "Partman_Test"."ID_Taptest_Table" TO "Partman_Revoke"; -- Template table CREATE UNLOGGED TABLE "Partman_Test"."Template_ID_Taptest_Table" (LIKE "Partman_Test"."ID_Taptest_Table"); DO $pg11_objects_check$ BEGIN IF current_setting('server_version_num')::int >= 110000 THEN -- Create on parent table ALTER TABLE "Partman_Test"."ID_Taptest_Table" ADD PRIMARY KEY ("Col1"); ALTER TABLE "Partman_Test"."ID_Taptest_Table" ADD FOREIGN KEY ("Col2") REFERENCES "Partman_Test"."Fk_Test_Reference"("Col2"); CREATE INDEX ON "Partman_Test"."ID_Taptest_Table" ("Col3"); ELSE -- Create on template table ALTER TABLE "Partman_Test"."Template_ID_Taptest_Table" ADD PRIMARY KEY ("Col1"); ALTER TABLE "Partman_Test"."Template_ID_Taptest_Table" ADD FOREIGN KEY ("Col2") REFERENCES "Partman_Test"."Fk_Test_Reference"("Col2"); END IF; END $pg11_objects_check$; -- Always create the index on teh template also so that we can test excluding duplicates. CREATE INDEX ON "Partman_Test"."Template_ID_Taptest_Table" ("Col3"); -- Regular unique indexes do not work on native in PG11 if the partition key isn't included CREATE UNIQUE INDEX ON "Partman_Test"."Template_ID_Taptest_Table" ("Col4"); SELECT create_parent('Partman_Test.ID_Taptest_Table', 'Col1', 'native', '10', p_jobmon := false, p_start_partition := '3000000000', p_template_table := 'Partman_Test.Template_ID_Taptest_Table'); UPDATE part_config SET inherit_privileges = TRUE; SELECT reapply_privileges('Partman_Test.ID_Taptest_Table'); INSERT INTO "Partman_Test"."ID_Taptest_Table" ("Col1", "Col4") VALUES (generate_series(3000000001,3000000009), 'stuff'||generate_series(3000000001,3000000009)); SELECT has_table('Partman_Test', 'ID_Taptest_Table_p3000000000', 'Check ID_Taptest_Table_p3000000000 exists'); SELECT has_table('Partman_Test', 'ID_Taptest_Table_p3000000010', 'Check ID_Taptest_Table_p3000000010 exists'); SELECT has_table('Partman_Test', 'ID_Taptest_Table_p3000000020', 'Check ID_Taptest_Table_p3000000020 exists'); SELECT has_table('Partman_Test', 'ID_Taptest_Table_p3000000030', 'Check ID_Taptest_Table_p3000000030 exists'); SELECT has_table('Partman_Test', 'ID_Taptest_Table_p3000000040', 'Check ID_Taptest_Table_p3000000040 exists'); SELECT hasnt_table('Partman_Test', 'ID_Taptest_Table_p3000000050', 'Check ID_Taptest_Table_p3000000050 doesn''t exists yet'); SELECT col_is_pk('Partman_Test', 'ID_Taptest_Table_p3000000000', ARRAY['Col1'], 'Check for primary key in ID_Taptest_Table_p3000000000'); SELECT col_is_pk('Partman_Test', 'ID_Taptest_Table_p3000000010', ARRAY['Col1'], 'Check for primary key in ID_Taptest_Table_p3000000010'); SELECT col_is_pk('Partman_Test', 'ID_Taptest_Table_p3000000020', ARRAY['Col1'], 'Check for primary key in ID_Taptest_Table_p3000000020'); SELECT col_is_pk('Partman_Test', 'ID_Taptest_Table_p3000000030', ARRAY['Col1'], 'Check for primary key in ID_Taptest_Table_p3000000030'); SELECT col_is_pk('Partman_Test', 'ID_Taptest_Table_p3000000040', ARRAY['Col1'], 'Check for primary key in ID_Taptest_Table_p3000000040'); SELECT col_is_fk('Partman_Test', 'ID_Taptest_Table_p3000000000', 'Col2', 'Check that foreign key was inherited to ID_Taptest_Table_p3000000000'); SELECT col_is_fk('Partman_Test', 'ID_Taptest_Table_p3000000010', 'Col2', 'Check that foreign key was inherited to ID_Taptest_Table_p3000000010'); SELECT col_is_fk('Partman_Test', 'ID_Taptest_Table_p3000000020', 'Col2', 'Check that foreign key was inherited to ID_Taptest_Table_p3000000020'); SELECT col_is_fk('Partman_Test', 'ID_Taptest_Table_p3000000030', 'Col2', 'Check that foreign key was inherited to ID_Taptest_Table_p3000000030'); SELECT col_is_fk('Partman_Test', 'ID_Taptest_Table_p3000000040', 'Col2', 'Check that foreign key was inherited to ID_Taptest_Table_p3000000040'); -- is_indexed is broken for mixed case so have to manually mark as identity objects https://github.com/theory/pgtap/issues/247 SELECT is_indexed('Partman_Test', 'ID_Taptest_Table_p3000000000', '"Col4"', 'Check that unique index was inherited to ID_Taptest_Table_p3000000000'); SELECT is_indexed('Partman_Test', 'ID_Taptest_Table_p3000000010', '"Col4"', 'Check that unique index was inherited to ID_Taptest_Table_p3000000010'); SELECT is_indexed('Partman_Test', 'ID_Taptest_Table_p3000000020', '"Col4"', 'Check that unique index was inherited to ID_Taptest_Table_p3000000020'); SELECT is_indexed('Partman_Test', 'ID_Taptest_Table_p3000000030', '"Col4"', 'Check that unique index was inherited to ID_Taptest_Table_p3000000030'); SELECT is_indexed('Partman_Test', 'ID_Taptest_Table_p3000000040', '"Col4"', 'Check that unique index was inherited to ID_Taptest_Table_p3000000040'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000000', 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of ID_Taptest_Table_p3000000000'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000010', 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of ID_Taptest_Table_p3000000010'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000020', 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of ID_Taptest_Table_p3000000020'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000030', 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of ID_Taptest_Table_p3000000030'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000040', 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of ID_Taptest_Table_p3000000040'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000000', 'Partman_Revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check Partman_Revoke privileges of ID_Taptest_Table_p3000000000'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000010', 'Partman_Revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check Partman_Revoke privileges of ID_Taptest_Table_p3000000010'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000020', 'Partman_Revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check Partman_Revoke privileges of ID_Taptest_Table_p3000000020'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000030', 'Partman_Revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check Partman_Revoke privileges of ID_Taptest_Table_p3000000030'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000040', 'Partman_Revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check Partman_Revoke privileges of ID_Taptest_Table_p3000000040'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''"Partman_Test"."ID_Taptest_Table"''::regclass', ARRAY['u'], 'Check that parent table is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''"Partman_Test"."ID_Taptest_Table_p3000000000"''::regclass', ARRAY['u'], 'Check that ID_Taptest_Table_p3000000000 is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''"Partman_Test"."ID_Taptest_Table_p3000000010"''::regclass', ARRAY['u'], 'Check that ID_Taptest_Table_p3000000010 is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''"Partman_Test"."ID_Taptest_Table_p3000000020"''::regclass', ARRAY['u'], 'Check that ID_Taptest_Table_p3000000020 is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''"Partman_Test"."ID_Taptest_Table_p3000000030"''::regclass', ARRAY['u'], 'Check that ID_Taptest_Table_p3000000030 is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''"Partman_Test"."ID_Taptest_Table_p3000000040"''::regclass', ARRAY['u'], 'Check that ID_Taptest_Table_p3000000040 is unlogged'); SELECT is_empty('SELECT * FROM ONLY "Partman_Test"."ID_Taptest_Table"', 'Check that parent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM "Partman_Test"."ID_Taptest_Table"', ARRAY[9], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM "Partman_Test"."ID_Taptest_Table_p3000000000"', ARRAY[9], 'Check count from ID_Taptest_Table_p3000000000'); REVOKE INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON "Partman_Test"."ID_Taptest_Table" FROM "Partman_Revoke", PUBLIC; SELECT run_maintenance(); INSERT INTO "Partman_Test"."ID_Taptest_Table" ("Col1", "Col4") VALUES (generate_series(3000000010,3000000025), 'stuff'||generate_series(3000000010,3000000025)); -- Run again to make new partition based on latest data SELECT run_maintenance(); SELECT is_empty('SELECT * FROM ONLY "Partman_Test"."ID_Taptest_Table"', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM "Partman_Test"."ID_Taptest_Table_p3000000010"', ARRAY[10], 'Check count from ID_Taptest_Table_p3000000010'); SELECT results_eq('SELECT count(*)::int FROM "Partman_Test"."ID_Taptest_Table_p3000000020"', ARRAY[6], 'Check count from ID_Taptest_Table_p3000000020'); SELECT has_table('Partman_Test', 'ID_Taptest_Table_p3000000050', 'Check ID_Taptest_Table_p3000000050 exists'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''"Partman_Test"."ID_Taptest_Table_p3000000050"''::regclass', ARRAY['u'], 'Check that ID_Taptest_Table_p3000000050 is unlogged'); SELECT has_table('Partman_Test', 'ID_Taptest_Table_p3000000060', 'Check ID_Taptest_Table_p3000000060 exists yet'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''"Partman_Test"."ID_Taptest_Table_p3000000060"''::regclass', ARRAY['u'], 'Check that ID_Taptest_Table_p3000000060 is unlogged'); SELECT hasnt_table('Partman_Test', 'ID_Taptest_Table_p3000000070', 'Check ID_Taptest_Table_p3000000070 doesn''t exists yet'); --SELECT col_is_pk('Partman_Test', 'ID_Taptest_Table_p3000000050', ARRAY['Col1'], 'Check for primary key in ID_Taptest_Table_p3000000050'); --SELECT col_isnt_fk('Partman_Test', 'ID_Taptest_Table_p3000000050', 'Col2', 'Check that foreign key was NOT inherited to ID_Taptest_Table_p3000000050'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000000', 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of ID_Taptest_Table_p3000000000'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000010', 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of ID_Taptest_Table_p3000000010'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000020', 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of ID_Taptest_Table_p3000000020'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000030', 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of ID_Taptest_Table_p3000000030'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000040', 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of ID_Taptest_Table_p3000000040'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000050', 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of ID_Taptest_Table_p3000000050'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000050', 'Partman_Revoke', ARRAY['SELECT'], 'Check Partman_Revoke privileges of ID_Taptest_Table_p3000000050'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000060', 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of ID_Taptest_Table_p3000000060'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000060', 'Partman_Revoke', ARRAY['SELECT'], 'Check Partman_Revoke privileges of ID_Taptest_Table_p3000000060'); GRANT DELETE ON "Partman_Test"."ID_Taptest_Table" TO "Partman_Basic"; REVOKE ALL ON "Partman_Test"."ID_Taptest_Table" FROM "Partman_Revoke", PUBLIC; ALTER TABLE "Partman_Test"."ID_Taptest_Table" OWNER TO "Partman_Owner"; INSERT INTO "Partman_Test"."ID_Taptest_Table" ("Col1", "Col4") VALUES (generate_series(3000000026,3000000038), 'stuff'||generate_series(3000000026,3000000038)); SELECT run_maintenance(); SELECT is_empty('SELECT * FROM ONLY "Partman_Test"."ID_Taptest_Table"', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM "Partman_Test"."ID_Taptest_Table"', ARRAY[38], 'Check count from id_taptest_table'); SELECT results_eq('SELECT count(*)::int FROM "Partman_Test"."ID_Taptest_Table_p3000000020"', ARRAY[10], 'Check count from ID_Taptest_Table_p3000000020'); SELECT results_eq('SELECT count(*)::int FROM "Partman_Test"."ID_Taptest_Table_p3000000030"', ARRAY[9], 'Check count from ID_Taptest_Table_p3000000030'); SELECT has_table('Partman_Test', 'ID_Taptest_Table_p3000000070', 'Check ID_Taptest_Table_p3000000070 exists'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''"Partman_Test"."ID_Taptest_Table_p3000000070"''::regclass', ARRAY['u'], 'Check that ID_Taptest_Table_p3000000070 is unlogged'); SELECT hasnt_table('Partman_Test', 'ID_Taptest_Table_p3000000080', 'Check ID_Taptest_Table_p3000000080 doesn''t exists yet'); SELECT col_is_pk('Partman_Test', 'ID_Taptest_Table_p3000000060', ARRAY['Col1'], 'Check for primary key in ID_Taptest_Table_p3000000060'); SELECT col_is_pk('Partman_Test', 'ID_Taptest_Table_p3000000070', ARRAY['Col1'], 'Check for primary key in ID_Taptest_Table_p3000000070'); SELECT col_is_fk('Partman_Test', 'ID_Taptest_Table_p3000000060', 'Col2', 'Check that foreign key was inherited to ID_Taptest_Table_p3000000060'); SELECT col_is_fk('Partman_Test', 'ID_Taptest_Table_p3000000070', 'Col2', 'Check that foreign key was inherited to ID_Taptest_Table_p3000000070'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000000', 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of ID_Taptest_Table_p3000000000'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000010', 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of ID_Taptest_Table_p3000000010'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000020', 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of ID_Taptest_Table_p3000000020'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000030', 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of ID_Taptest_Table_p3000000030'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000040', 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of ID_Taptest_Table_p3000000040'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000050', 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of ID_Taptest_Table_p3000000050'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000050', 'Partman_Revoke', ARRAY['SELECT'], 'Check Partman_Revoke privileges of ID_Taptest_Table_p3000000050'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000060', 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of ID_Taptest_Table_p3000000060'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000060', 'Partman_Revoke', ARRAY['SELECT'], 'Check Partman_Revoke privileges of ID_Taptest_Table_p3000000060'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000070', 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check Partman_Basic privileges of ID_Taptest_Table_p3000000070'); SELECT table_owner_is ('Partman_Test', 'ID_Taptest_Table_p3000000070', 'Partman_Owner', 'Check that ownership change worked for ID_Taptest_Table_p3000000070'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000070', 'Partman_Revoke', '{}'::text[], 'Check Partman_Revoke has no privileges on ID_Taptest_Table_p3000000070'); -- Requires default table option --INSERT INTO "Partman_Test"."ID_Taptest_Table" ("Col1", "Col4") VALUES (generate_series(3000000200,3000000210), 'stuff'||generate_series(3000000200,3000000210)); --SELECT run_maintenance(); --SELECT results_eq('SELECT count(*)::int FROM ONLY "Partman_Test"."ID_Taptest_Table"', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT reapply_privileges('Partman_Test.ID_Taptest_Table'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000000', 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check Partman_Basic privileges of ID_Taptest_Table_p3000000000'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000010', 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check Partman_Basic privileges of ID_Taptest_Table_p3000000010'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000020', 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check Partman_Basic privileges of ID_Taptest_Table_p3000000020'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000030', 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check Partman_Basic privileges of ID_Taptest_Table_p3000000030'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000040', 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check Partman_Basic privileges of ID_Taptest_Table_p3000000040'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000050', 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check Partman_Basic privileges of ID_Taptest_Table_p3000000050'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000060', 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check Partman_Basic privileges of ID_Taptest_Table_p3000000060'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000070', 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check Partman_Basic privileges of ID_Taptest_Table_p3000000070'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000000', 'Partman_Revoke', '{}'::text[], 'Check Partman_Revoke has no privileges on ID_Taptest_Table_p3000000000'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000010', 'Partman_Revoke', '{}'::text[], 'Check Partman_Revoke has no privileges on ID_Taptest_Table_p3000000010'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000020', 'Partman_Revoke', '{}'::text[], 'Check Partman_Revoke has no privileges on ID_Taptest_Table_p3000000020'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000030', 'Partman_Revoke', '{}'::text[], 'Check Partman_Revoke has no privileges on ID_Taptest_Table_p3000000030'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000040', 'Partman_Revoke', '{}'::text[], 'Check Partman_Revoke has no privileges on ID_Taptest_Table_p3000000040'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000050', 'Partman_Revoke', '{}'::text[], 'Check Partman_Revoke has no privileges on ID_Taptest_Table_p3000000050'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000060', 'Partman_Revoke', '{}'::text[], 'Check Partman_Revoke has no privileges on ID_Taptest_Table_p3000000060'); SELECT table_privs_are('Partman_Test', 'ID_Taptest_Table_p3000000070', 'Partman_Revoke', '{}'::text[], 'Check Partman_Revoke has no privileges on ID_Taptest_Table_p3000000070'); SELECT table_owner_is ('Partman_Test', 'ID_Taptest_Table_p3000000000', 'Partman_Owner', 'Check that ownership change worked for ID_Taptest_Table_p3000000000'); SELECT table_owner_is ('Partman_Test', 'ID_Taptest_Table_p3000000010', 'Partman_Owner', 'Check that ownership change worked for ID_Taptest_Table_p3000000010'); SELECT table_owner_is ('Partman_Test', 'ID_Taptest_Table_p3000000020', 'Partman_Owner', 'Check that ownership change worked for ID_Taptest_Table_p3000000020'); SELECT table_owner_is ('Partman_Test', 'ID_Taptest_Table_p3000000030', 'Partman_Owner', 'Check that ownership change worked for ID_Taptest_Table_p3000000030'); SELECT table_owner_is ('Partman_Test', 'ID_Taptest_Table_p3000000040', 'Partman_Owner', 'Check that ownership change worked for ID_Taptest_Table_p3000000040'); SELECT table_owner_is ('Partman_Test', 'ID_Taptest_Table_p3000000050', 'Partman_Owner', 'Check that ownership change worked for ID_Taptest_Table_p3000000050'); SELECT table_owner_is ('Partman_Test', 'ID_Taptest_Table_p3000000060', 'Partman_Owner', 'Check that ownership change worked for ID_Taptest_Table_p3000000060'); SELECT table_owner_is ('Partman_Test', 'ID_Taptest_Table_p3000000070', 'Partman_Owner', 'Check that ownership change worked for ID_Taptest_Table_p3000000070'); -- Max value is 38 above SELECT drop_partition_id('Partman_Test.ID_Taptest_Table', '20', p_keep_table := false); SELECT hasnt_table('Partman_Test', 'ID_Taptest_Table_p3000000000', 'Check ID_Taptest_Table_p3000000000 doesn''t exists anymore'); UPDATE part_config SET retention = '10' WHERE parent_table = 'Partman_Test.ID_Taptest_Table'; SELECT drop_partition_id('Partman_Test.ID_Taptest_Table', p_retention_schema := 'Partman_Retention_Test'); SELECT hasnt_table('Partman_Test', 'ID_Taptest_Table_p3000000010', 'Check ID_Taptest_Table_p3000000010 doesn''t exists anymore'); SELECT has_table('Partman_Retention_Test', 'ID_Taptest_Table_p3000000010', 'Check ID_Taptest_Table_p3000000010 got moved to new schema'); SELECT undo_partition('Partman_Test.ID_Taptest_Table', p_target_table := 'Partman_Test.Undo_Taptest', p_keep_table := false); SELECT hasnt_table('Partman_Test', 'ID_Taptest_Table_p3000000020', 'Check ID_Taptest_Table_p3000000020 does not exist'); -- Test keeping the rest of the tables SELECT undo_partition('Partman_Test.ID_Taptest_Table', 10, p_target_table := 'Partman_Test.Undo_Taptest'); SELECT results_eq('SELECT count(*)::int FROM ONLY "Partman_Test"."Undo_Taptest"', ARRAY[19], 'Check count from parent table after undo'); SELECT has_table('Partman_Test', 'ID_Taptest_Table_p3000000030', 'Check ID_Taptest_Table_p3000000030 still exists'); SELECT is_empty('SELECT * FROM "Partman_Test"."ID_Taptest_Table_p3000000030"', 'Check child table had its data removed ID_Taptest_Table_p3000000030'); SELECT has_table('Partman_Test', 'ID_Taptest_Table_p3000000040', 'Check ID_Taptest_Table_p3000000040 still exists'); SELECT is_empty('SELECT * FROM "Partman_Test"."ID_Taptest_Table_p3000000040"', 'Check child table had its data removed ID_Taptest_Table_p3000000040'); SELECT has_table('Partman_Test', 'ID_Taptest_Table_p3000000050', 'Check ID_Taptest_Table_p3000000050 still exists'); SELECT is_empty('SELECT * FROM "Partman_Test"."ID_Taptest_Table_p3000000050"', 'Check child table had its data removed ID_Taptest_Table_p3000000050'); SELECT has_table('Partman_Test', 'ID_Taptest_Table_p3000000060', 'Check ID_Taptest_Table_p3000000060 still exists'); SELECT is_empty('SELECT * FROM "Partman_Test"."ID_Taptest_Table_p3000000060"', 'Check child table had its data removed ID_Taptest_Table_p3000000060'); SELECT has_table('Partman_Test', 'ID_Taptest_Table_p3000000070', 'Check ID_Taptest_Table_p3000000070 still exists'); SELECT is_empty('SELECT * FROM "Partman_Test"."ID_Taptest_Table_p3000000070"', 'Check child table had its data removed ID_Taptest_Table_p3000000070'); SELECT hasnt_table('Partman_Test', 'template_id_taptest_table', 'Check that template table was dropped'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test_native/test-id-native.sql000066400000000000000000000643451412761706100225540ustar00rootroot00000000000000-- ########## ID DYNAMIC TESTS ########## -- Additional tests: turn off pg_jobmon logging, UNLOGGED, PUBLIC role, start with higher number, inherit FK, inherit privileges -- Test using a pre-created template table and passing to create_parent. Should allow indexes to be made for initial children. -- Tests that foreign keys and normal indexes for PG10 use the template and for PG11 they use the parent. Also since this is id partitioning, we can use the partition key for primary key, so that should work from parent on PG11 as well. -- TODO Uncomment default child tests when PG10 support is dropped \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(120); CREATE SCHEMA partman_test; CREATE SCHEMA partman_retention_test; CREATE ROLE partman_basic; CREATE ROLE partman_revoke; CREATE ROLE partman_owner; CREATE TABLE partman_test.fk_test_reference (col2 text unique not null); INSERT INTO partman_test.fk_test_reference VALUES ('stuff'); CREATE UNLOGGED TABLE partman_test.id_taptest_table ( col1 bigint , col2 text not null default 'stuff' , col3 timestamptz DEFAULT now() , col4 text) PARTITION BY RANGE (col1); CREATE TABLE partman_test.undo_taptest (LIKE partman_test.id_taptest_table INCLUDING ALL); GRANT SELECT,INSERT,UPDATE ON partman_test.id_taptest_table TO partman_basic, PUBLIC; GRANT ALL ON partman_test.id_taptest_table TO partman_revoke; -- Template table CREATE UNLOGGED TABLE partman_test.template_id_taptest_table (LIKE partman_test.id_taptest_table); DO $pg11_objects_check$ BEGIN IF current_setting('server_version_num')::int >= 110000 THEN -- Create on parent table ALTER TABLE partman_test.id_taptest_table ADD PRIMARY KEY (col1); ALTER TABLE partman_test.id_taptest_table ADD FOREIGN KEY (col2) REFERENCES partman_test.fk_test_reference(col2); CREATE INDEX ON partman_test.id_taptest_table (col3); ELSE -- Create on template table ALTER TABLE partman_test.template_id_taptest_table ADD PRIMARY KEY (col1); ALTER TABLE partman_test.template_id_taptest_table ADD FOREIGN KEY (col2) REFERENCES partman_test.fk_test_reference(col2); END IF; END $pg11_objects_check$; -- Always create the index on teh template also so that we can test excluding duplicates. CREATE INDEX ON partman_test.template_id_taptest_table (col3); -- Regular unique indexes do not work on native in PG11 if the partition key isn't included CREATE UNIQUE INDEX ON partman_test.template_id_taptest_table (col4); SELECT create_parent('partman_test.id_taptest_table', 'col1', 'native', '10', p_jobmon := false, p_start_partition := '3000000000', p_template_table := 'partman_test.template_id_taptest_table'); UPDATE part_config SET inherit_privileges = TRUE; SELECT reapply_privileges('partman_test.id_taptest_table'); INSERT INTO partman_test.id_taptest_table (col1, col4) VALUES (generate_series(3000000001,3000000009), 'stuff'||generate_series(3000000001,3000000009)); SELECT has_table('partman_test', 'id_taptest_table_p3000000000', 'Check id_taptest_table_p3000000000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p3000000010', 'Check id_taptest_table_p3000000010 exists'); SELECT has_table('partman_test', 'id_taptest_table_p3000000020', 'Check id_taptest_table_p3000000020 exists'); SELECT has_table('partman_test', 'id_taptest_table_p3000000030', 'Check id_taptest_table_p3000000030 exists'); SELECT has_table('partman_test', 'id_taptest_table_p3000000040', 'Check id_taptest_table_p3000000040 exists'); --SELECT has_table('partman_test', 'id_taptest_table_default', 'Check id_taptest_table_default exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p3000000050', 'Check id_taptest_table_p3000000050 doesn''t exists yet'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000000', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000000'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000010', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000010'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000020', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000020'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000030', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000030'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000040', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000040'); --SELECT col_is_pk('partman_test', 'id_taptest_table_default', ARRAY['col1'], 'Check for primary key in id_taptest_table_default'); SELECT col_is_fk('partman_test', 'id_taptest_table_p3000000000', 'col2', 'Check that foreign key was inherited to id_taptest_table_p3000000000'); SELECT col_is_fk('partman_test', 'id_taptest_table_p3000000010', 'col2', 'Check that foreign key was inherited to id_taptest_table_p3000000010'); SELECT col_is_fk('partman_test', 'id_taptest_table_p3000000020', 'col2', 'Check that foreign key was inherited to id_taptest_table_p3000000020'); SELECT col_is_fk('partman_test', 'id_taptest_table_p3000000030', 'col2', 'Check that foreign key was inherited to id_taptest_table_p3000000030'); SELECT col_is_fk('partman_test', 'id_taptest_table_p3000000040', 'col2', 'Check that foreign key was inherited to id_taptest_table_p3000000040'); --SELECT col_is_fk('partman_test', 'id_taptest_table_default', 'col2', 'Check that foreign key was inherited to id_taptest_table_default'); SELECT is_indexed('partman_test', 'id_taptest_table_p3000000000', 'col4', 'Check that unique index was inherited to id_taptest_table_p3000000000'); SELECT is_indexed('partman_test', 'id_taptest_table_p3000000010', 'col4', 'Check that unique index was inherited to id_taptest_table_p3000000010'); SELECT is_indexed('partman_test', 'id_taptest_table_p3000000020', 'col4', 'Check that unique index was inherited to id_taptest_table_p3000000020'); SELECT is_indexed('partman_test', 'id_taptest_table_p3000000030', 'col4', 'Check that unique index was inherited to id_taptest_table_p3000000030'); SELECT is_indexed('partman_test', 'id_taptest_table_p3000000040', 'col4', 'Check that unique index was inherited to id_taptest_table_p3000000040'); --SELECT is_indexed('partman_test', 'id_taptest_table_default', 'col4', 'Check that unique index was inherited to id_taptest_table_default'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000000', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000000'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000010', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000010'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000020', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000020'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000030', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000030'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000040', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000040'); --SELECT table_privs_are('partman_test', 'id_taptest_table_default', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_default'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000000', 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of id_taptest_table_p3000000000'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000010', 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of id_taptest_table_p3000000010'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000020', 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of id_taptest_table_p3000000020'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000030', 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of id_taptest_table_p3000000030'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000040', 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of id_taptest_table_p3000000040'); --SELECT table_privs_are('partman_test', 'id_taptest_table_default', 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of id_taptest_table_default'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table''::regclass', ARRAY['u'], 'Check that parent table is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p3000000000''::regclass', ARRAY['u'], 'Check that id_taptest_table_p3000000000 is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p3000000010''::regclass', ARRAY['u'], 'Check that id_taptest_table_p3000000010 is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p3000000020''::regclass', ARRAY['u'], 'Check that id_taptest_table_p3000000020 is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p3000000030''::regclass', ARRAY['u'], 'Check that id_taptest_table_p3000000030 is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p3000000040''::regclass', ARRAY['u'], 'Check that id_taptest_table_p3000000040 is unlogged'); --SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_default''::regclass', ARRAY['u'], 'Check that id_taptest_table_default is unlogged'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had data moved to partition'); --SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_default', 'Check that default table has no data'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[9], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p3000000000', ARRAY[9], 'Check count from id_taptest_table_p3000000000'); REVOKE INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON partman_test.id_taptest_table FROM partman_revoke, PUBLIC; SELECT run_maintenance(); INSERT INTO partman_test.id_taptest_table (col1, col4) VALUES (generate_series(3000000010,3000000025), 'stuff'||generate_series(3000000010,3000000025)); -- Run again to make new partition based on latest data SELECT run_maintenance(); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p3000000010', ARRAY[10], 'Check count from id_taptest_table_p3000000010'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p3000000020', ARRAY[6], 'Check count from id_taptest_table_p3000000020'); SELECT has_table('partman_test', 'id_taptest_table_p3000000050', 'Check id_taptest_table_p3000000050 exists'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p3000000050''::regclass', ARRAY['u'], 'Check that id_taptest_table_p3000000050 is unlogged'); SELECT has_table('partman_test', 'id_taptest_table_p3000000060', 'Check id_taptest_table_p3000000060 exists yet'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p3000000060''::regclass', ARRAY['u'], 'Check that id_taptest_table_p3000000060 is unlogged'); SELECT hasnt_table('partman_test', 'id_taptest_table_p3000000070', 'Check id_taptest_table_p3000000070 doesn''t exists yet'); --SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000050', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000050'); --SELECT col_isnt_fk('partman_test', 'id_taptest_table_p3000000050', 'col2', 'Check that foreign key was NOT inherited to id_taptest_table_p3000000050'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000000', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000000'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000010', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000010'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000020', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000020'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000030', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000030'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000040', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000040'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000050', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000050'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000050', 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of id_taptest_table_p3000000050'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000060', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000060'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000060', 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of id_taptest_table_p3000000060'); GRANT DELETE ON partman_test.id_taptest_table TO partman_basic; REVOKE ALL ON partman_test.id_taptest_table FROM partman_revoke, PUBLIC; ALTER TABLE partman_test.id_taptest_table OWNER TO partman_owner; INSERT INTO partman_test.id_taptest_table (col1, col4) VALUES (generate_series(3000000026,3000000038), 'stuff'||generate_series(3000000026,3000000038)); SELECT run_maintenance(); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[38], 'Check count from id_taptest_table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p3000000020', ARRAY[10], 'Check count from id_taptest_table_p3000000020'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p3000000030', ARRAY[9], 'Check count from id_taptest_table_p3000000030'); SELECT has_table('partman_test', 'id_taptest_table_p3000000070', 'Check id_taptest_table_p3000000070 exists'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p3000000070''::regclass', ARRAY['u'], 'Check that id_taptest_table_p3000000070 is unlogged'); SELECT hasnt_table('partman_test', 'id_taptest_table_p3000000080', 'Check id_taptest_table_p3000000080 doesn''t exists yet'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000060', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000060'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000070', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000070'); SELECT col_is_fk('partman_test', 'id_taptest_table_p3000000060', 'col2', 'Check that foreign key was inherited to id_taptest_table_p3000000060'); SELECT col_is_fk('partman_test', 'id_taptest_table_p3000000070', 'col2', 'Check that foreign key was inherited to id_taptest_table_p3000000070'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000000', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000000'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000010', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000010'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000020', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000020'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000030', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000030'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000040', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000040'); --SELECT table_privs_are('partman_test', 'id_taptest_table_default', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_default'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000050', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000050'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000050', 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of id_taptest_table_p3000000050'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000060', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000060'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000060', 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of id_taptest_table_p3000000060'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000070', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p3000000070'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p3000000070', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p3000000070'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000070', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p3000000070'); -- Requires default table option --INSERT INTO partman_test.id_taptest_table (col1, col4) VALUES (generate_series(3000000200,3000000210), 'stuff'||generate_series(3000000200,3000000210)); --SELECT run_maintenance(); --SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT reapply_privileges('partman_test.id_taptest_table'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000000', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p3000000000'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000010', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p3000000010'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000020', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p3000000020'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000030', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p3000000030'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000040', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p3000000040'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000050', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p3000000050'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000060', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p3000000060'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000070', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p3000000070'); --SELECT table_privs_are('partman_test', 'id_taptest_table_default', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_default'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000000', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p3000000000'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000010', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p3000000010'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000020', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p3000000020'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000030', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p3000000030'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000040', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p3000000040'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000050', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p3000000050'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000060', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p3000000060'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000070', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p3000000070'); --SELECT table_privs_are('partman_test', 'id_taptest_table_default', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_default'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p3000000000', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p3000000000'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p3000000010', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p3000000010'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p3000000020', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p3000000020'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p3000000030', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p3000000030'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p3000000040', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p3000000040'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p3000000050', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p3000000050'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p3000000060', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p3000000060'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p3000000070', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p3000000070'); --SELECT table_owner_is ('partman_test', 'id_taptest_table_default', 'partman_owner', 'Check that ownership change worked for id_taptest_table_default'); -- Max value is 38 above SELECT drop_partition_id('partman_test.id_taptest_table', '20', p_keep_table := false); SELECT hasnt_table('partman_test', 'id_taptest_table_p3000000000', 'Check id_taptest_table_p3000000000 doesn''t exists anymore'); UPDATE part_config SET retention = '10' WHERE parent_table = 'partman_test.id_taptest_table'; SELECT drop_partition_id('partman_test.id_taptest_table', p_retention_schema := 'partman_retention_test'); SELECT hasnt_table('partman_test', 'id_taptest_table_p3000000010', 'Check id_taptest_table_p3000000010 doesn''t exists anymore'); SELECT has_table('partman_retention_test', 'id_taptest_table_p3000000010', 'Check id_taptest_table_p3000000010 got moved to new schema'); SELECT undo_partition('partman_test.id_taptest_table', p_target_table := 'partman_test.undo_taptest', p_keep_table := false); SELECT hasnt_table('partman_test', 'id_taptest_table_p3000000020', 'Check id_taptest_table_p3000000020 does not exist'); -- Test keeping the rest of the tables SELECT undo_partition('partman_test.id_taptest_table', 10, p_target_table := 'partman_test.undo_taptest'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.undo_taptest', ARRAY[19], 'Check count from parent table after undo'); SELECT has_table('partman_test', 'id_taptest_table_p3000000030', 'Check id_taptest_table_p3000000030 still exists'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p3000000030', 'Check child table had its data removed id_taptest_table_p3000000030'); SELECT has_table('partman_test', 'id_taptest_table_p3000000040', 'Check id_taptest_table_p3000000040 still exists'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p3000000040', 'Check child table had its data removed id_taptest_table_p3000000040'); SELECT has_table('partman_test', 'id_taptest_table_p3000000050', 'Check id_taptest_table_p3000000050 still exists'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p3000000050', 'Check child table had its data removed id_taptest_table_p3000000050'); SELECT has_table('partman_test', 'id_taptest_table_p3000000060', 'Check id_taptest_table_p3000000060 still exists'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p3000000060', 'Check child table had its data removed id_taptest_table_p3000000060'); SELECT has_table('partman_test', 'id_taptest_table_p3000000070', 'Check id_taptest_table_p3000000070 still exists'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p3000000070', 'Check child table had its data removed id_taptest_table_p3000000070'); SELECT hasnt_table('partman_test', 'template_id_taptest_table', 'Check that template table was dropped'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test_native/test-id-time-subpart-custom-start-native.sql000066400000000000000000002157621412761706100276320ustar00rootroot00000000000000-- ########## NATIVE ID PARENT / TIME SUBPARENT TESTS ########## -- Additional tests: no pg_jobmon -- Test using a pre-created template table and passing to create_parent. Should allow indexes to be made for initial children. -- Test that FK and normal index on PG11 uses parent and <=PG10 uses template -- Can be used to test nonsuperuser is working \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(324); CREATE SCHEMA partman_test; CREATE TABLE partman_test.fk_test_reference (col2 text unique not null); INSERT INTO partman_test.fk_test_reference VALUES ('stuff'); -- Restore back when native supports primary key --CREATE TABLE partman_test.id_taptest_table (col1 int primary key, col2 text, col3 timestamptz NOT NULL DEFAULT now()); CREATE TABLE partman_test.id_taptest_table ( col1 int , col2 text DEFAULT 'stuff' , col3 timestamptz NOT NULL DEFAULT now()) PARTITION BY RANGE (col1); CREATE TABLE partman_test.undo_taptest (LIKE partman_test.id_taptest_table INCLUDING ALL); -- Template table CREATE TABLE partman_test.template_id_taptest_table (LIKE partman_test.id_taptest_table); -- Primary keys do not work on native in PG11 if the partition key isn't included. -- While col1 is the partition key on the top level, subpartition is time and it won't work there ALTER TABLE partman_test.template_id_taptest_table ADD PRIMARY KEY (col1); DO $pg11_objects_check$ BEGIN IF current_setting('server_version_num')::int >= 110000 THEN -- Create on parent table CREATE INDEX ON partman_test.id_taptest_table (col3); ALTER TABLE partman_test.id_taptest_table ADD FOREIGN KEY (col2) REFERENCES partman_test.fk_test_reference(col2); ELSE -- Create on template table CREATE INDEX ON partman_test.template_id_taptest_table (col3); ALTER TABLE partman_test.template_id_taptest_table ADD FOREIGN KEY (col2) REFERENCES partman_test.fk_test_reference(col2); END IF; END $pg11_objects_check$; SELECT partman.create_parent('partman_test.id_taptest_table', 'col1', 'native', '10', '{"col3"}', p_jobmon := false, p_template_table := 'partman_test.template_id_taptest_table'); INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(1,9)); SELECT is_partitioned('partman_test', 'id_taptest_table', 'Check that id_taptest_table is natively partitioned'); SELECT has_table('partman_test', 'id_taptest_table_p0', 'Check id_taptest_table_p0 exists'); SELECT has_table('partman_test', 'id_taptest_table_p10', 'Check id_taptest_table_p10 exists'); SELECT has_table('partman_test', 'id_taptest_table_p20', 'Check id_taptest_table_p20 exists'); SELECT has_table('partman_test', 'id_taptest_table_p30', 'Check id_taptest_table_p30 exists'); SELECT has_table('partman_test', 'id_taptest_table_p40', 'Check id_taptest_table_p40 exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50', 'Check id_taptest_table_p50 doesn''t exists yet'); SELECT col_is_pk('partman_test', 'id_taptest_table_p0', ARRAY['col1'], 'Check for primary key in id_taptest_table_p0'); SELECT col_is_pk('partman_test', 'id_taptest_table_p10', ARRAY['col1'], 'Check for primary key in id_taptest_table_p10'); SELECT col_is_pk('partman_test', 'id_taptest_table_p20', ARRAY['col1'], 'Check for primary key in id_taptest_table_p20'); SELECT col_is_pk('partman_test', 'id_taptest_table_p30', ARRAY['col1'], 'Check for primary key in id_taptest_table_p30'); SELECT col_is_pk('partman_test', 'id_taptest_table_p40', ARRAY['col1'], 'Check for primary key in id_taptest_table_p40'); SELECT col_is_fk('partman_test', 'id_taptest_table_p0', ARRAY['col2'], 'Check for foreign key in id_taptest_table_p0'); SELECT col_is_fk('partman_test', 'id_taptest_table_p10', ARRAY['col2'], 'Check for foreign key in id_taptest_table_p10'); SELECT col_is_fk('partman_test', 'id_taptest_table_p20', ARRAY['col2'], 'Check for foreign key in id_taptest_table_p20'); SELECT col_is_fk('partman_test', 'id_taptest_table_p30', ARRAY['col2'], 'Check for foreign key in id_taptest_table_p30'); SELECT col_is_fk('partman_test', 'id_taptest_table_p40', ARRAY['col2'], 'Check for foreign key in id_taptest_table_p40'); SELECT has_index('partman_test', 'id_taptest_table_p0', 'id_taptest_table_p0_col3_idx', ARRAY['col3'], 'Check for index in id_taptest_table_p0'); SELECT has_index('partman_test', 'id_taptest_table_p10', 'id_taptest_table_p10_col3_idx', ARRAY['col3'], 'Check for index key in id_taptest_table_p10'); SELECT has_index('partman_test', 'id_taptest_table_p20', 'id_taptest_table_p20_col3_idx', ARRAY['col3'], 'Check for index key in id_taptest_table_p20'); SELECT has_index('partman_test', 'id_taptest_table_p30', 'id_taptest_table_p30_col3_idx', ARRAY['col3'], 'Check for index key in id_taptest_table_p30'); SELECT has_index('partman_test', 'id_taptest_table_p40', 'id_taptest_table_p40_col3_idx', ARRAY['col3'], 'Check for index key in id_taptest_table_p40'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[9], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0', ARRAY[9], 'Check count from id_taptest_table_p0'); -- Create subpartition (start sub partitions 2 days before premake value) SELECT partman.create_sub_parent('partman_test.id_taptest_table', 'col3', 'native', 'daily', p_native_check := 'yes', p_start_partition := (CURRENT_TIMESTAMP - '6 days'::interval)::text ); --Reinsert data due to child table destruction INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(1,9)); SELECT is_partitioned('partman_test', 'id_taptest_table_p0', 'Check id_taptest_table_p0 is natively partitioned'); SELECT is_partitioned('partman_test', 'id_taptest_table_p10', 'Check id_taptest_table_p10 is natively partitioned'); SELECT is_partitioned('partman_test', 'id_taptest_table_p20', 'Check id_taptest_table_p20 is natively partitioned'); SELECT is_partitioned('partman_test', 'id_taptest_table_p30', 'Check id_taptest_table_p30 is natively partitioned'); SELECT is_partitioned('partman_test', 'id_taptest_table_p40', 'Check id_taptest_table_p40 is natively partitioned'); SELECT isnt_partitioned('partman_test', 'id_taptest_table_p50', 'Check id_taptest_table_p50 doesn''t exists as partition yet'); -- p0 SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'6 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'6 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'7 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'7 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in d_dynamic_table_p0_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'6 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in d_dynamic_table_p0_p'||to_char(CURRENT_TIMESTAMP-'6 day'::interval, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'6 day'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'6 day'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check for no data in top parent'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p0', 'Check for no data in sub-parent'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0', ARRAY[9], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY[9], 'Check count from id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check count from id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' (should be empty)'); -- p10 SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'6 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'6 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'7 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'7 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in d_dynamic_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10', 'Check that subparent table has had data moved to partition'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p10', 'Check count from parent table _p10 (should be empty)'); -- p20 SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'6 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'6 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'7 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'7 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in d_dynamic_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'6 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in d_dynamic_table_p20_p'||to_char(CURRENT_TIMESTAMP-'6 day'::interval, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'6 day'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'6 day'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p20', 'Check that subparent table itself _p20 is empty'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p20', 'Check count from parent table _p20 (should be empty)'); -- p30 SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'6 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'6 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'7 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'7 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in d_dynamic_table_p30_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'6 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in d_dynamic_table_p30_p'||to_char(CURRENT_TIMESTAMP-'6 day'::interval, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'6 day'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'6 day'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p30', 'Check that subparent table itself _p30 is empty'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p30', 'Check count from parent table _p30 (should be empty)'); -- p40 SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'6 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'6 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'7 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'7 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'6 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'6 day'::interval, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'6 day'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'6 day'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p40', 'Check that subparent table itself _p40 is empty'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p40', 'Check count from parent table _p40 (should be empty)'); -- insertion round 2 INSERT INTO partman_test.id_taptest_table (col1, col3) VALUES (generate_series(10,20), CURRENT_TIMESTAMP+'1 day'::interval); SELECT run_maintenance(); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had data moved to partition'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p0', 'Check that subparent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0', ARRAY[9], 'Check count from parent table partman_test.id_taptest_table_p0'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY[9], 'Check count from id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10', 'Check that subparent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10', ARRAY[10], 'Check count from parent table partman_test.id_taptest_table_p10'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY[10], 'Check count from id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p20', 'Check that subparent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20', ARRAY[1], 'Check count from parent table partman_test.id_taptest_table_p20'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY[1], 'Check count from id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); -- p50 SELECT is_partitioned('partman_test', 'id_taptest_table_p50', 'Check id_taptest_table_p50 is natively partitioned'); SELECT has_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT has_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in d_dynamic_table_p50_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had data moved to partition'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p50', 'Check that subparent table itself _p50 is empty'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p50', 'Check count from parent table _p50 (should be empty)'); -- p60 SELECT is_partitioned('partman_test', 'id_taptest_table_p60', 'Check id_taptest_table_p60 is natively partitioned'); SELECT has_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT has_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in d_dynamic_table_p60_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had data moved to partition'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p60', 'Check that subparent table itself _p60 is empty'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p60', 'Check count from parent table _p60 (should be empty)'); SELECT hasnt_table('partman_test', 'id_taptest_table_p70', 'Check id_taptest_table_p70 doesn''t exists yet'); -- make sure new data is where it should be SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10', 'Check count from only parent table _p10 (should be empty)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10', ARRAY[10], 'Check count from subparent table id_taptest_table_p10'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY[10], 'Check count from id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20', ARRAY[1], 'Check count from subparent table id_taptest_table_p20'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY[1], 'Check count from id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); -- Ensure time partitioning works for all sub partitions UPDATE part_config SET premake = 5, optimize_trigger = 5 WHERE parent_table ~ 'partman_test.id_taptest_table_p' AND partition_type = 'native'; SELECT run_maintenance(); -- Check for new time sub-partitions -- Time data only exists for now() in _p0, so only one additional table will be created SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); -- Time data exists for now+1 day in p10, so 2 additional tables will be created SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); -- Time data exists for now+1 day in p20, so 2 additional tables will be created SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); -- No time data has been inserted for p30 and higher, so no more partitions should be created for them SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); -- Test dropping without retention set SELECT drop_partition_time ('partman_test.id_taptest_table_p0', '2 days', p_keep_table := false); SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' still exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'6 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'6 days'::interval, 'YYYY_MM_DD')||' does not exist'); UPDATE part_config SET retention = '10', retention_keep_table = false WHERE parent_table = 'partman_test.id_taptest_table'; UPDATE part_config SET retention = '2 days', retention_keep_table = false WHERE parent_table = 'partman_test.id_taptest_table_p0'; UPDATE part_config SET retention = '2 days', retention_keep_table = false WHERE parent_table = 'partman_test.id_taptest_table_p10'; UPDATE part_config SET retention = '2 days', retention_keep_table = false WHERE parent_table = 'partman_test.id_taptest_table_p20'; UPDATE part_config SET retention = '2 days', retention_keep_table = false WHERE parent_table = 'partman_test.id_taptest_table_p30'; UPDATE part_config SET retention = '2 days', retention_keep_table = false WHERE parent_table = 'partman_test.id_taptest_table_p40'; UPDATE part_config SET retention = '2 days', retention_keep_table = false WHERE parent_table = 'partman_test.id_taptest_table_p50'; UPDATE part_config SET retention = '2 days', retention_keep_table = false WHERE parent_table = 'partman_test.id_taptest_table_p60'; -- Test dropping with it set SELECT run_maintenance(); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'6 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'6 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'6 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'6 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'6 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'6 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'6 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'6 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'6 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'6 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'6 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'6 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT undo_partition('partman_test.id_taptest_table_p10', 20, p_target_table := 'partman_test.undo_taptest', p_keep_table := false); SELECT has_table('partman_test', 'template_id_taptest_table', 'Check that template table was not removed yet'); SELECT undo_partition('partman_test.id_taptest_table_p20', 20, p_target_table := 'partman_test.undo_taptest', p_keep_table := false); SELECT has_table('partman_test', 'template_id_taptest_table', 'Check that template table was not removed yet'); SELECT undo_partition('partman_test.id_taptest_table_p30', 20, p_target_table := 'partman_test.undo_taptest', p_keep_table := false); SELECT has_table('partman_test', 'template_id_taptest_table', 'Check that template table was not removed yet'); SELECT undo_partition('partman_test.id_taptest_table_p40', 20, p_target_table := 'partman_test.undo_taptest', p_keep_table := false); SELECT has_table('partman_test', 'template_id_taptest_table', 'Check that template table was not removed yet'); SELECT undo_partition('partman_test.id_taptest_table_p50', 20, p_target_table := 'partman_test.undo_taptest', p_keep_table := false); SELECT has_table('partman_test', 'template_id_taptest_table', 'Check that template table was not removed yet'); SELECT undo_partition('partman_test.id_taptest_table_p60', 20, p_target_table := 'partman_test.undo_taptest', p_keep_table := false); SELECT has_table('partman_test', 'template_id_taptest_table', 'Check that template table was not removed yet'); SELECT results_eq('SELECT count(*)::int FROM partman_test.undo_taptest', ARRAY[11], 'Check count from target of undo_partition'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.id_taptest_table_p0''', 'Check that partman_test.id_taptest_table_p0 was removed from part_config'); SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.id_taptest_table_p10''', 'Check that partman_test.id_taptest_table_p10 was removed from part_config'); SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.id_taptest_table_p20''', 'Check that partman_test.id_taptest_table_p20 was removed from part_config'); SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.id_taptest_table_p30''', 'Check that partman_test.id_taptest_table_p30 was removed from part_config'); SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.id_taptest_table_p40''', 'Check that partman_test.id_taptest_table_p40 was removed from part_config'); SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.id_taptest_table_p50''', 'Check that partman_test.id_taptest_table_p50 was removed from part_config'); SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.id_taptest_table_p60''', 'Check that partman_test.id_taptest_table_p60 was removed from part_config'); -- Check top parent is still empty SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that top parent table has not had any data moved to it'); -- This should return zero since above subpartitioning should have removed all data SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table'', 20, p_target_table := ''partman_test.undo_taptest'', p_keep_table := false)', ARRAY[0], 'Check that undoing top table partition returns zero rows'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0', 'Check id_taptest_table_p0 does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10', 'Check id_taptest_table_p10 does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20', 'Check id_taptest_table_p20 does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30', 'Check id_taptest_table_p30 does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40', 'Check id_taptest_table_p40 does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50', 'Check id_taptest_table_p50 does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60', 'Check id_taptest_table_p60 does not exist'); SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.id_taptest_table''', 'Check that partman_test.id_taptest_table was removed from part_config'); SELECT hasnt_table('partman_test', 'template_id_taptest_table', 'Check that template table was removed'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.undo_taptest', ARRAY[11], 'Check count from final unpartitioned target table'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test_native/test-id-time-subpart-native.sql000066400000000000000000002020371412761706100251560ustar00rootroot00000000000000-- ########## NATIVE ID PARENT / TIME SUBPARENT TESTS ########## -- Additional tests: no pg_jobmon -- Test using a pre-created template table and passing to create_parent. Should allow indexes to be made for initial children. -- Test that FK and normal index on PG11 uses parent and <=PG10 uses template -- Can be used to test nonsuperuser is working \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(299); CREATE SCHEMA partman_test; CREATE TABLE partman_test.fk_test_reference (col2 text unique not null); INSERT INTO partman_test.fk_test_reference VALUES ('stuff'); -- Restore back when native supports primary key --CREATE TABLE partman_test.id_taptest_table (col1 int primary key, col2 text, col3 timestamptz NOT NULL DEFAULT now()); CREATE TABLE partman_test.id_taptest_table ( col1 int , col2 text DEFAULT 'stuff' , col3 timestamptz NOT NULL DEFAULT now()) PARTITION BY RANGE (col1); CREATE TABLE partman_test.undo_taptest (LIKE partman_test.id_taptest_table INCLUDING ALL); -- Template table CREATE TABLE partman_test.template_id_taptest_table (LIKE partman_test.id_taptest_table); -- Primary keys do not work on native in PG11 if the partition key isn't included. -- While col1 is the partition key on the top level, subpartition is time and it won't work there ALTER TABLE partman_test.template_id_taptest_table ADD PRIMARY KEY (col1); DO $pg11_objects_check$ BEGIN IF current_setting('server_version_num')::int >= 110000 THEN -- Create on parent table CREATE INDEX ON partman_test.id_taptest_table (col3); ALTER TABLE partman_test.id_taptest_table ADD FOREIGN KEY (col2) REFERENCES partman_test.fk_test_reference(col2); ELSE -- Create on template table CREATE INDEX ON partman_test.template_id_taptest_table (col3); ALTER TABLE partman_test.template_id_taptest_table ADD FOREIGN KEY (col2) REFERENCES partman_test.fk_test_reference(col2); END IF; END $pg11_objects_check$; SELECT partman.create_parent('partman_test.id_taptest_table', 'col1', 'native', '10', '{"col3"}', p_jobmon := false, p_template_table := 'partman_test.template_id_taptest_table'); INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(1,9)); SELECT is_partitioned('partman_test', 'id_taptest_table', 'Check that id_taptest_table is natively partitioned'); SELECT has_table('partman_test', 'id_taptest_table_p0', 'Check id_taptest_table_p0 exists'); SELECT has_table('partman_test', 'id_taptest_table_p10', 'Check id_taptest_table_p10 exists'); SELECT has_table('partman_test', 'id_taptest_table_p20', 'Check id_taptest_table_p20 exists'); SELECT has_table('partman_test', 'id_taptest_table_p30', 'Check id_taptest_table_p30 exists'); SELECT has_table('partman_test', 'id_taptest_table_p40', 'Check id_taptest_table_p40 exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50', 'Check id_taptest_table_p50 doesn''t exists yet'); SELECT col_is_pk('partman_test', 'id_taptest_table_p0', ARRAY['col1'], 'Check for primary key in id_taptest_table_p0'); SELECT col_is_pk('partman_test', 'id_taptest_table_p10', ARRAY['col1'], 'Check for primary key in id_taptest_table_p10'); SELECT col_is_pk('partman_test', 'id_taptest_table_p20', ARRAY['col1'], 'Check for primary key in id_taptest_table_p20'); SELECT col_is_pk('partman_test', 'id_taptest_table_p30', ARRAY['col1'], 'Check for primary key in id_taptest_table_p30'); SELECT col_is_pk('partman_test', 'id_taptest_table_p40', ARRAY['col1'], 'Check for primary key in id_taptest_table_p40'); SELECT col_is_fk('partman_test', 'id_taptest_table_p0', ARRAY['col2'], 'Check for foreign key in id_taptest_table_p0'); SELECT col_is_fk('partman_test', 'id_taptest_table_p10', ARRAY['col2'], 'Check for foreign key in id_taptest_table_p10'); SELECT col_is_fk('partman_test', 'id_taptest_table_p20', ARRAY['col2'], 'Check for foreign key in id_taptest_table_p20'); SELECT col_is_fk('partman_test', 'id_taptest_table_p30', ARRAY['col2'], 'Check for foreign key in id_taptest_table_p30'); SELECT col_is_fk('partman_test', 'id_taptest_table_p40', ARRAY['col2'], 'Check for foreign key in id_taptest_table_p40'); SELECT has_index('partman_test', 'id_taptest_table_p0', 'id_taptest_table_p0_col3_idx', ARRAY['col3'], 'Check for index in id_taptest_table_p0'); SELECT has_index('partman_test', 'id_taptest_table_p10', 'id_taptest_table_p10_col3_idx', ARRAY['col3'], 'Check for index key in id_taptest_table_p10'); SELECT has_index('partman_test', 'id_taptest_table_p20', 'id_taptest_table_p20_col3_idx', ARRAY['col3'], 'Check for index key in id_taptest_table_p20'); SELECT has_index('partman_test', 'id_taptest_table_p30', 'id_taptest_table_p30_col3_idx', ARRAY['col3'], 'Check for index key in id_taptest_table_p30'); SELECT has_index('partman_test', 'id_taptest_table_p40', 'id_taptest_table_p40_col3_idx', ARRAY['col3'], 'Check for index key in id_taptest_table_p40'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[9], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0', ARRAY[9], 'Check count from id_taptest_table_p0'); -- Create subpartition SELECT partman.create_sub_parent('partman_test.id_taptest_table', 'col3', 'native', 'daily', p_native_check := 'yes'); --Reinsert data due to child table destruction INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(1,9)); SELECT is_partitioned('partman_test', 'id_taptest_table_p0', 'Check id_taptest_table_p0 is natively partitioned'); SELECT is_partitioned('partman_test', 'id_taptest_table_p10', 'Check id_taptest_table_p10 is natively partitioned'); SELECT is_partitioned('partman_test', 'id_taptest_table_p20', 'Check id_taptest_table_p20 is natively partitioned'); SELECT is_partitioned('partman_test', 'id_taptest_table_p30', 'Check id_taptest_table_p30 is natively partitioned'); SELECT is_partitioned('partman_test', 'id_taptest_table_p40', 'Check id_taptest_table_p40 is natively partitioned'); SELECT isnt_partitioned('partman_test', 'id_taptest_table_p50', 'Check id_taptest_table_p50 doesn''t exists as partition yet'); -- p0 SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in d_dynamic_table_p0_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check for no data in top parent'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p0', 'Check for no data in sub-parent'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0', ARRAY[9], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY[9], 'Check count from id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check count from id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' (should be empty)'); -- p10 SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in d_dynamic_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10', 'Check that subparent table has had data moved to partition'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p10', 'Check count from parent table _p10 (should be empty)'); -- p20 SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in d_dynamic_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p20', 'Check that subparent table itself _p20 is empty'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p20', 'Check count from parent table _p20 (should be empty)'); -- p30 SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in d_dynamic_table_p30_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p30', 'Check that subparent table itself _p30 is empty'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p30', 'Check count from parent table _p30 (should be empty)'); -- p40 SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p40', 'Check that subparent table itself _p40 is empty'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p40', 'Check count from parent table _p40 (should be empty)'); -- insertion round 2 INSERT INTO partman_test.id_taptest_table (col1, col3) VALUES (generate_series(10,20), CURRENT_TIMESTAMP+'1 day'::interval); SELECT run_maintenance(); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had data moved to partition'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p0', 'Check that subparent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0', ARRAY[9], 'Check count from parent table partman_test.id_taptest_table_p0'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY[9], 'Check count from id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10', 'Check that subparent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10', ARRAY[10], 'Check count from parent table partman_test.id_taptest_table_p10'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY[10], 'Check count from id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p20', 'Check that subparent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20', ARRAY[1], 'Check count from parent table partman_test.id_taptest_table_p20'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY[1], 'Check count from id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); -- p50 SELECT is_partitioned('partman_test', 'id_taptest_table_p50', 'Check id_taptest_table_p50 is natively partitioned'); SELECT has_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT has_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in d_dynamic_table_p50_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had data moved to partition'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p50', 'Check that subparent table itself _p50 is empty'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p50', 'Check count from parent table _p50 (should be empty)'); -- p60 SELECT is_partitioned('partman_test', 'id_taptest_table_p60', 'Check id_taptest_table_p60 is natively partitioned'); SELECT has_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT has_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in d_dynamic_table_p60_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had data moved to partition'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p60', 'Check that subparent table itself _p60 is empty'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p60', 'Check count from parent table _p60 (should be empty)'); SELECT hasnt_table('partman_test', 'id_taptest_table_p70', 'Check id_taptest_table_p70 doesn''t exists yet'); -- make sure new data is where it should be SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10', 'Check count from only parent table _p10 (should be empty)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10', ARRAY[10], 'Check count from subparent table id_taptest_table_p10'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY[10], 'Check count from id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20', ARRAY[1], 'Check count from subparent table id_taptest_table_p20'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY[1], 'Check count from id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); -- Ensure time partitioning works for all sub partitions UPDATE part_config SET premake = 5, optimize_trigger = 5 WHERE parent_table ~ 'partman_test.id_taptest_table_p' AND partition_type = 'native'; SELECT run_maintenance(); -- Check for new time sub-partitions -- Time data only exists for now() in _p0, so only one additional table will be created SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); -- Time data exists for now+1 day in p10, so 2 additional tables will be created SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); -- Time data exists for now+1 day in p20, so 2 additional tables will be created SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); -- No time data has been inserted for p30 and higher, so no more partitions should be created for them SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); -- Test dropping without retention set SELECT drop_partition_time ('partman_test.id_taptest_table_p0', '2 days', p_keep_table := false); SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' still exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); UPDATE part_config SET retention = '10', retention_keep_table = false WHERE parent_table = 'partman_test.id_taptest_table'; UPDATE part_config SET retention = '2 days', retention_keep_table = false WHERE parent_table = 'partman_test.id_taptest_table_p0'; UPDATE part_config SET retention = '2 days', retention_keep_table = false WHERE parent_table = 'partman_test.id_taptest_table_p10'; UPDATE part_config SET retention = '2 days', retention_keep_table = false WHERE parent_table = 'partman_test.id_taptest_table_p20'; UPDATE part_config SET retention = '2 days', retention_keep_table = false WHERE parent_table = 'partman_test.id_taptest_table_p30'; UPDATE part_config SET retention = '2 days', retention_keep_table = false WHERE parent_table = 'partman_test.id_taptest_table_p40'; UPDATE part_config SET retention = '2 days', retention_keep_table = false WHERE parent_table = 'partman_test.id_taptest_table_p50'; UPDATE part_config SET retention = '2 days', retention_keep_table = false WHERE parent_table = 'partman_test.id_taptest_table_p60'; -- Test dropping with it set SELECT run_maintenance(); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT undo_partition('partman_test.id_taptest_table_p10', 20, p_target_table := 'partman_test.undo_taptest', p_keep_table := false); SELECT has_table('partman_test', 'template_id_taptest_table', 'Check that template table was not removed yet'); SELECT undo_partition('partman_test.id_taptest_table_p20', 20, p_target_table := 'partman_test.undo_taptest', p_keep_table := false); SELECT has_table('partman_test', 'template_id_taptest_table', 'Check that template table was not removed yet'); SELECT undo_partition('partman_test.id_taptest_table_p30', 20, p_target_table := 'partman_test.undo_taptest', p_keep_table := false); SELECT has_table('partman_test', 'template_id_taptest_table', 'Check that template table was not removed yet'); SELECT undo_partition('partman_test.id_taptest_table_p40', 20, p_target_table := 'partman_test.undo_taptest', p_keep_table := false); SELECT has_table('partman_test', 'template_id_taptest_table', 'Check that template table was not removed yet'); SELECT undo_partition('partman_test.id_taptest_table_p50', 20, p_target_table := 'partman_test.undo_taptest', p_keep_table := false); SELECT has_table('partman_test', 'template_id_taptest_table', 'Check that template table was not removed yet'); SELECT undo_partition('partman_test.id_taptest_table_p60', 20, p_target_table := 'partman_test.undo_taptest', p_keep_table := false); SELECT has_table('partman_test', 'template_id_taptest_table', 'Check that template table was not removed yet'); SELECT results_eq('SELECT count(*)::int FROM partman_test.undo_taptest', ARRAY[11], 'Check count from target of undo_partition'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.id_taptest_table_p0''', 'Check that partman_test.id_taptest_table_p0 was removed from part_config'); SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.id_taptest_table_p10''', 'Check that partman_test.id_taptest_table_p10 was removed from part_config'); SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.id_taptest_table_p20''', 'Check that partman_test.id_taptest_table_p20 was removed from part_config'); SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.id_taptest_table_p30''', 'Check that partman_test.id_taptest_table_p30 was removed from part_config'); SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.id_taptest_table_p40''', 'Check that partman_test.id_taptest_table_p40 was removed from part_config'); SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.id_taptest_table_p50''', 'Check that partman_test.id_taptest_table_p50 was removed from part_config'); SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.id_taptest_table_p60''', 'Check that partman_test.id_taptest_table_p60 was removed from part_config'); -- Check top parent is still empty SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that top parent table has not had any data moved to it'); -- This should return zero since above subpartitioning should have removed all data SELECT results_eq('SELECT rows_undone::int FROM undo_partition(''partman_test.id_taptest_table'', 20, p_target_table := ''partman_test.undo_taptest'', p_keep_table := false)', ARRAY[0], 'Check that undoing top table partition returns zero rows'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0', 'Check id_taptest_table_p0 does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10', 'Check id_taptest_table_p10 does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20', 'Check id_taptest_table_p20 does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30', 'Check id_taptest_table_p30 does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40', 'Check id_taptest_table_p40 does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50', 'Check id_taptest_table_p50 does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60', 'Check id_taptest_table_p60 does not exist'); SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.id_taptest_table''', 'Check that partman_test.id_taptest_table was removed from part_config'); SELECT hasnt_table('partman_test', 'template_id_taptest_table', 'Check that template table was removed'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.undo_taptest', ARRAY[11], 'Check count from final unpartitioned target table'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test_native/test-time-custom-100years-native.sql000066400000000000000000001416511412761706100257640ustar00rootroot00000000000000-- ########## TIME CUSTOM TESTS NATIVE ########## -- Other tests: Use undo_partition_native(), inherit privileges \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(148); CREATE SCHEMA partman_test; CREATE SCHEMA partman_retention_test; CREATE ROLE partman_basic; CREATE ROLE partman_revoke; CREATE ROLE partman_owner; CREATE TABLE partman_test.time_taptest_table (col1 int, col2 text, col3 timestamptz NOT NULL DEFAULT now()) PARTITION BY RANGE (col3); CREATE TABLE partman_test.time_taptest_table_template (LIKE partman_test.time_taptest_table INCLUDING ALL); ALTER TABLE partman_test.time_taptest_table_template ADD PRIMARY KEY (col1); CREATE TABLE partman_test.undo_taptest (LIKE partman_test.time_taptest_table INCLUDING ALL); DO $pg11_objects_check$ BEGIN IF current_setting('server_version_num')::int >= 110000 THEN -- Create on parent table CREATE INDEX ON partman_test.time_taptest_table (col3); ELSE -- Create on template table CREATE INDEX ON partman_test.time_taptest_table_template (col3); END IF; END $pg11_objects_check$; GRANT SELECT,INSERT,UPDATE ON partman_test.time_taptest_table TO partman_basic; GRANT ALL ON partman_test.time_taptest_table TO partman_revoke; SELECT create_parent('partman_test.time_taptest_table', 'col3', 'native', '100 years', p_template_table := 'partman_test.time_taptest_table_template'); UPDATE part_config SET inherit_privileges = TRUE; SELECT reapply_privileges('partman_test.time_taptest_table'); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY')||' does not exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'500 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'500 years'::interval, 'YYYY')||' does not exist'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY')); -- TODO Change this to check the default partition when that is added -- 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(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY')); REVOKE INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON partman_test.time_taptest_table FROM partman_revoke; INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '100 years'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '200 years'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(26,30), CURRENT_TIMESTAMP + '300 years'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(31,37), CURRENT_TIMESTAMP + '400 years'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(40,49), CURRENT_TIMESTAMP - '100 years'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(50,70), CURRENT_TIMESTAMP - '200 years'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(71,85), CURRENT_TIMESTAMP - '300 years'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(86,100), CURRENT_TIMESTAMP - '400 years'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY'), ARRAY[7], 'Check count from time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY'), ARRAY[21], 'Check count from time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY')); UPDATE part_config SET premake = 5 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(101,122), CURRENT_TIMESTAMP + '500 years'::interval); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY')||' does not exist'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY')); -- TODO Change this to check the default partition when that is added -- SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY'), ARRAY[22], 'Check count from time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY')); GRANT DELETE ON partman_test.time_taptest_table TO partman_basic; REVOKE ALL ON partman_test.time_taptest_table FROM partman_revoke; ALTER TABLE partman_test.time_taptest_table OWNER TO partman_owner; UPDATE part_config SET premake = 6 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(123,150), CURRENT_TIMESTAMP + '600 years'::interval); -- TODO Change this to check the default partition when that is added --SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[148], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY'), ARRAY[28], 'Check count from time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY')); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1200 years'::interval, 'YYYY'), 'Check time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1200 years'::interval, 'YYYY')||' does not exist'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY')); -- TODO Change this to check the default partition when that is added --INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '2000 years'::interval); --SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT reapply_privileges('partman_test.time_taptest_table'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY')); SELECT drop_partition_time('partman_test.time_taptest_table', '300 years', p_keep_table := false); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'400 years'::interval, 'YYYY')||' does not exist'); UPDATE part_config SET retention = '200 years'::interval WHERE parent_table = 'partman_test.time_taptest_table'; SELECT drop_partition_time('partman_test.time_taptest_table', p_retention_schema := 'partman_retention_test'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY')||' does not exist'); SELECT has_table('partman_retention_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'300 years'::interval, 'YYYY')||' got moved to new schema'); SELECT undo_partition('partman_test.time_taptest_table', 20, p_target_table := 'partman_test.undo_taptest', p_keep_table := false); SELECT results_eq('SELECT count(*)::int FROM partman_test.undo_taptest', ARRAY[118], 'Check count from target table after undo'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP), 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'100 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)-'200 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'100 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'200 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'300 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'400 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'500 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'600 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'700 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'800 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'900 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1000 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY'), 'Check time_taptest_table_'||to_char(date_trunc('century', CURRENT_TIMESTAMP)+'1100 years'::interval, 'YYYY')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_template', 'Check that template table was dropped'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test_native/test-time-custom-30sec-native.sql000066400000000000000000002355331412761706100253400ustar00rootroot00000000000000-- ########## TIME CUSTOM TESTS NATIVE ########## -- Additional tests: Inherit privileges -- May fail when run in the first or second 30 seconds of the minute due to rounding down to the nearest minute. -- If it does, wait until the next block of 30 seconds starts and try again. -- If it is failing no matter when it is run, please create an issue on Github with a log of your result when running with "pg_prove -ovf" \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(146); CREATE SCHEMA partman_test; CREATE SCHEMA partman_retention_test; CREATE ROLE partman_basic; CREATE ROLE partman_revoke; CREATE ROLE partman_owner; CREATE TABLE partman_test.time_taptest_table (col1 int, col2 text, col3 timestamptz NOT NULL DEFAULT now()) PARTITION BY RANGE (col3); CREATE TABLE partman_test.time_taptest_table_template (LIKE partman_test.time_taptest_table INCLUDING ALL); -- TODO change back to parent table when fixed in future pg version ALTER TABLE partman_test.time_taptest_table_template ADD PRIMARY KEY (col1); CREATE TABLE partman_test.undo_taptest (LIKE partman_test.time_taptest_table INCLUDING ALL); DO $pg11_objects_check$ BEGIN IF current_setting('server_version_num')::int >= 110000 THEN -- Create on parent table CREATE INDEX ON partman_test.time_taptest_table (col3); ELSE -- Create on template table CREATE INDEX ON partman_test.time_taptest_table_template (col3); END IF; END $pg11_objects_check$; GRANT SELECT,INSERT,UPDATE ON partman_test.time_taptest_table TO partman_basic; GRANT ALL ON partman_test.time_taptest_table TO partman_revoke; SELECT create_parent('partman_test.time_taptest_table', 'col3', 'native', '30 seconds', p_template_table := 'partman_test.time_taptest_table_template'); -- Must run_maintenance because when interval time is between 1 hour and 1 minute, the first partition name done by above is always the nearest hour rounded down SELECT run_maintenance(); UPDATE part_config SET inherit_privileges = TRUE; SELECT reapply_privileges('partman_test.time_taptest_table'); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MISS')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' exists'); /* extra previous tables may exist due to new rounding down of the hour. Test left here for manual checking SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'150 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'150 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist'); */ SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 secs'::interval, 'YYYY_MM_DD_HH24MISS')); --TODO REMOVE SELECT partition_data_time('partman_test.time_taptest_table'); -- TODO Change this to check the default partition when that is added -- 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(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS')); REVOKE INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON partman_test.time_taptest_table FROM partman_revoke; INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '30 secs'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '60 secs'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(26,30), CURRENT_TIMESTAMP + '90 secs'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(31,37), CURRENT_TIMESTAMP + '120 secs'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(40,49), CURRENT_TIMESTAMP - '30 secs'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(50,70), CURRENT_TIMESTAMP - '60 secs'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(71,85), CURRENT_TIMESTAMP - '90 secs'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(86,100), CURRENT_TIMESTAMP - '120 secs'::interval); -- TODO Change this to check the default partition when that is added -- SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[7], 'Check count from time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[21], 'Check count from time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 secs'::interval, 'YYYY_MM_DD_HH24MISS')); UPDATE part_config SET premake = 5 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(101,122), CURRENT_TIMESTAMP + '150 secs'::interval); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 secs'::interval, 'YYYY_MM_DD_HH24MISS')); -- TODO Change this to check the default partition when that is added -- SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[22], 'Check count from time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 secs'::interval, 'YYYY_MM_DD_HH24MISS')); GRANT DELETE ON partman_test.time_taptest_table TO partman_basic; REVOKE ALL ON partman_test.time_taptest_table FROM partman_revoke; ALTER TABLE partman_test.time_taptest_table OWNER TO partman_owner; UPDATE part_config SET premake = 6 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(123,150), CURRENT_TIMESTAMP + '180 secs'::interval); -- TODO Change this to check the default partition when that is added -- SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[148], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[28], 'Check count from time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'360 mins'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'360 mins'::interval, 'YYYY_MM_DD_HH24MISS')||' exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 secs'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 secs'::interval, 'YYYY_MM_DD_HH24MISS')); -- TODO Change this to check the default partition when that is added --INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '600 mins'::interval); -- SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT reapply_privileges('partman_test.time_taptest_table'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 secs'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT drop_partition_time('partman_test.time_taptest_table', '90 secs', p_keep_table := false); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'120 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist'); UPDATE part_config SET retention = '60 secs'::interval WHERE parent_table = 'partman_test.time_taptest_table'; SELECT drop_partition_time('partman_test.time_taptest_table', p_retention_schema := 'partman_retention_test'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist'); SELECT has_table('partman_retention_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'90 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' got moved to new schema'); SELECT undo_partition('partman_test.time_taptest_table', 20, p_target_table := 'partman_test.undo_taptest', p_keep_table := false); SELECT results_eq('SELECT count(*)::int FROM partman_test.undo_taptest', ARRAY[118], 'Check count from target table after undo'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0), 'YYYY_MM_DD_HH24MISS')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'30 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)-'60 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'30 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'60 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'90 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'120 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'150 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'180 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'210 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'240 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'270 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'300 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 secs'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_'||to_char(date_trunc('minute', CURRENT_TIMESTAMP) + '30sec'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0)+'330 secs'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_template', 'Check that template table was dropped'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test_native/test-time-daily-native.sql000066400000000000000000002054601412761706100242110ustar00rootroot00000000000000-- ########## TIME DAILY TESTS ########## -- Other tests: check that maintenance catches up if tables are missing -- Test using default template table. Initial child tables will have no indexes. New tables after template has indexes added should. -- The FK tests in this test will fail on PG11+. No way to run specific tests for specific versions easily. Just ignore for now. -- Test publication inheritance \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(232); CREATE SCHEMA partman_test; CREATE SCHEMA partman_retention_test; CREATE ROLE partman_basic; CREATE ROLE partman_revoke; CREATE ROLE partman_owner; CREATE PUBLICATION partman_test_publication; CREATE TABLE partman_test.fk_test_reference (col2 text unique not null); INSERT INTO partman_test.fk_test_reference VALUES ('stuff'); CREATE TABLE partman_test.time_taptest_table (col1 int, col2 text default 'stuff', col3 timestamptz NOT NULL DEFAULT now()) PARTITION BY RANGE (col3); CREATE TABLE partman_test.undo_taptest (LIKE partman_test.time_taptest_table INCLUDING ALL); GRANT SELECT,INSERT,UPDATE ON partman_test.time_taptest_table TO partman_basic; GRANT ALL ON partman_test.time_taptest_table TO partman_revoke; ALTER TABLE partman_test.time_taptest_table OWNER TO partman_owner; SELECT create_parent('partman_test.time_taptest_table', 'col3', 'native', 'daily', p_publications := ARRAY['partman_test_publication']); UPDATE part_config SET inherit_privileges = TRUE; SELECT reapply_privileges('partman_test.time_taptest_table'); SELECT is_partitioned('partman_test', 'time_taptest_table', 'Check that time_taptest_table is natively partitioned'); SELECT has_table('partman', 'template_partman_test_time_taptest_table', 'Check that default template table was created'); SELECT table_owner_is ('partman', 'template_partman_test_time_taptest_table', 'partman_owner', 'Check that template table ownership is set properly'); -- Add inheritable stuff to template table ALTER TABLE partman.template_partman_test_time_taptest_table ADD PRIMARY KEY (col1); ALTER TABLE partman.template_partman_test_time_taptest_table ADD FOREIGN KEY (col2) REFERENCES partman_test.fk_test_reference(col2); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); 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'); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for NO foreign key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for NO foreign key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 day'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for NO foreign key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 day'::interval, 'YYYY_MM_DD')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 day'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for NO foreign key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 day'::interval, 'YYYY_MM_DD')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 day'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for NO foreign key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 day'::interval, 'YYYY_MM_DD')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for NO foreign key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 day'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for NO foreign key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 day'::interval, 'YYYY_MM_DD')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 day'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for NO foreign key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 day'::interval, 'YYYY_MM_DD')); SELECT col_isnt_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 day'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for NO foreign key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of 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 is empty. Should be impossible for native, but leaving test here just cause.'); 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')); SELECT results_eq('SELECT count(*)::int FROM pg_catalog.pg_publication_tables WHERE pubname = ''partman_test_publication''', ARRAY[9], 'Check that all child tables were added to publication'); REVOKE INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON partman_test.time_taptest_table FROM partman_revoke; INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '1 day'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '2 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(26,30), CURRENT_TIMESTAMP + '3 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(31,37), CURRENT_TIMESTAMP + '4 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(40,49), CURRENT_TIMESTAMP - '1 day'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(50,70), CURRENT_TIMESTAMP - '2 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(71,85), CURRENT_TIMESTAMP - '3 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(86,100), CURRENT_TIMESTAMP - '4 days'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY[10], '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[5], '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[5], '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[7], '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[10], '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[21], '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[15], '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[15], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); UPDATE part_config SET premake = 5 WHERE parent_table = 'partman_test.time_taptest_table'; -- Run to create proper future partitions SELECT run_maintenance(); -- Insert after maintenance since native fails with no child INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(101,122), CURRENT_TIMESTAMP + '5 days'::interval); -- Run again to create +5 partition now that data exists SELECT run_maintenance(); -- Data exists for +5 days, with 5 premake so +10 day table should exist SELECT has_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')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'. This test will fail in PG11+.'); SELECT col_is_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||'. This test will fail in PG11+.'); SELECT col_is_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||'. This test will fail in PG11+.'); SELECT col_is_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||'. This test will fail in PG11+.'); SELECT col_is_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||'. This test will fail in PG11+.'); SELECT col_is_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||'. This test will fail in PG11+.'); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY[22], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT results_eq('SELECT count(*)::int FROM pg_catalog.pg_publication_tables WHERE pubname = ''partman_test_publication''', ARRAY[15], 'Check that all child tables were added to publication'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); GRANT DELETE ON partman_test.time_taptest_table TO partman_basic; REVOKE ALL ON partman_test.time_taptest_table FROM partman_revoke; ALTER TABLE partman_test.time_taptest_table OWNER TO partman_owner; UPDATE part_config SET premake = 6, optimize_trigger = 6 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(123,150), CURRENT_TIMESTAMP + '6 days'::interval); -- Run again now that +6 data exists SELECT run_maintenance(); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[148], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), ARRAY[28], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT results_eq('SELECT count(*)::int FROM pg_catalog.pg_publication_tables WHERE pubname = ''partman_test_publication''', ARRAY[17], 'Check that all child tables were added to publication'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'13 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'13 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')); SELECT col_is_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||'. This test will fail in PG11+.'); SELECT col_is_fk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), ARRAY['col2'], 'Check for foreign key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')||'. This test will fail in PG11+.'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')); SELECT reapply_privileges('partman_test.time_taptest_table'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); -- Test that maintenance will catch up DO $$ BEGIN EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'); END $$; SELECT hasnt_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')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_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')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_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')||' does not exist'); 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')||' does not exist'); 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')||' does not exist'); 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 hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT run_maintenance(); 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')||' does exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD')||' does exist'); 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')||' does exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 days'::interval, 'YYYY_MM_DD')||' does exist'); 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')||' does exist'); 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')||' does exist'); 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')||' does not exist'); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '1 day'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '2 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(26,30), CURRENT_TIMESTAMP + '3 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(40,49), CURRENT_TIMESTAMP - '1 day'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(50,70), CURRENT_TIMESTAMP - '2 days'::interval); SELECT run_maintenance(); 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')||' does exist'); SELECT has_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 exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' does exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' does exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' does exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||' does exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||' does not exist'); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(31,37), CURRENT_TIMESTAMP + '4 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(101,122), CURRENT_TIMESTAMP + '5 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(123,150), CURRENT_TIMESTAMP + '6 days'::interval); SELECT run_maintenance(); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||' does exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' does exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')||' does exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'13 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'13 days'::interval, 'YYYY_MM_DD')||' does not exist'); ----- This test won't work unless a default table option is added to native --INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '20 days'::interval); --SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT drop_partition_time('partman_test.time_taptest_table', '3 days', p_keep_table := false); 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')||' does not exist'); UPDATE part_config SET retention = '2 days'::interval WHERE parent_table = 'partman_test.time_taptest_table'; SELECT drop_partition_time('partman_test.time_taptest_table', p_retention_schema := 'partman_retention_test'); 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')||' does not exist'); SELECT has_table('partman_retention_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')||' got moved to new schema'); SELECT undo_partition('partman_test.time_taptest_table', 20, p_target_table := 'partman_test.undo_taptest', p_keep_table := false); SELECT results_eq('SELECT count(*)::int FROM partman_test.undo_taptest', ARRAY[118], 'Check count from target table after undo'); SELECT hasnt_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')||' does not exist'); SELECT hasnt_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')||' does not exist'); SELECT hasnt_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')||' does not exist'); 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')||' does not exist'); 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')||' does not exist'); 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 hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_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')||' does not exist'); SELECT hasnt_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')||' does not exist'); SELECT hasnt_table('partman', 'template_partman_test_time_taptest_table', 'Check that template table was dropped'); DROP PUBLICATION partman_test_publication; SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test_native/test-time-epoch-id-subpart-native.sql000066400000000000000000002712611412761706100262570ustar00rootroot00000000000000-- ########## 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; pg_partman-4.6.0/test/test_native/test-time-epoch-weekly-native.sql000066400000000000000000000527511412761706100255060ustar00rootroot00000000000000-- ########## NATIVE TIME WEEKLY EPOCH TESTS ########## -- Other tests: combination of start_partition & constraint_cols/optimize_constraint. Requires manually running apply_constraint to set other old partitions -- No need to test optimize trigger with native -- 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(54); CREATE SCHEMA partman_test; CREATE SCHEMA partman_retention_test; -- Add back when primary key is supported in native partitioning --CREATE TABLE partman_test.time_taptest_table (col1 int primary key, col2 text, col3 int NOT NULL DEFAULT extract('epoch' from CURRENT_TIMESTAMP)::int); 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', 'weekly', '{"col1"}', p_epoch := 'seconds' , p_premake := 2, p_start_partition := to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'YYYY-MM-DD HH24:MI:SS')); SELECT is_partitioned('partman_test', 'time_taptest_table', 'Check that time_taptest_table is natively partitioned'); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), extract('epoch' from CURRENT_TIMESTAMP - '8 weeks'::interval)::int); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' exists (+1 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||' exists (+2 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW')||' does not exist (+3 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' exists (-1 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' exists (-2 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' exists (-3 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' exists (-4 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||' exists (-5 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||' exists (-6 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')||' exists (-7 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')||' exists (-8 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'9 weeks'::interval, 'IYYY"w"IW')||' does not exist (-9 weeks)'); -- Add back when index support is available --SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), ARRAY['col1'], -- 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')); --SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), ARRAY['col1'], -- 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' (+1 weeks)'); --SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], -- 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||' (+2 weeks)'); --SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), ARRAY['col1'], -- 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' (-1 weeks)'); --SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], -- 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' (-2 weeks)'); --SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], -- 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' (-3 weeks)'); --SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], -- 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' (-4 weeks)'); --SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], -- 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||' (-5 weeks)'); --SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], -- 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||' (-6 weeks)'); --SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], -- 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')||' (-7 weeks)'); --SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], -- 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')||' (-8 weeks)'); -- END pk check 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 - '8 weeks'::interval, 'IYYY"w"IW'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')||' (-8 weeks)'); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), extract('epoch' from CURRENT_TIMESTAMP - '7 weeks'::interval)::int); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), extract('epoch' from CURRENT_TIMESTAMP - '6 weeks'::interval)::int); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(26,30), extract('epoch' from CURRENT_TIMESTAMP - '5 weeks'::interval)::int); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(31,37), extract('epoch' from CURRENT_TIMESTAMP - '4 week'::interval)::int); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(38,49), extract('epoch' from CURRENT_TIMESTAMP - '3 week'::interval)::int); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(50,70), extract('epoch' from CURRENT_TIMESTAMP - '2 weeks'::interval)::int); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(71,85), extract('epoch' from CURRENT_TIMESTAMP - '1 week'::interval)::int); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(86,100), extract('epoch' from CURRENT_TIMESTAMP + '1 week'::interval)::int); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(101,110), extract('epoch' from CURRENT_TIMESTAMP + '2 weeks'::interval)::int); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')||' (-7 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||' (-6 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||' (-5 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), ARRAY[7], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' (-4 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), ARRAY[12], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' (-3 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), ARRAY[21], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' (-2 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' (-1 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' (+1 weeks)'); -- Default optimize_constraint is 30, so set it to a value that will trigger it to work for given conditions of this partition set -- Set optimize_trigger higher than premake to ensure this works as intended UPDATE part_config SET premake = 3, optimize_constraint = 5, optimize_trigger = 8 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); -- Automatic run of apply_constraints due to run_maintenance will put constraint on "now() - 4 weeks" partition with an optimize_constraint value of 5 -- This is due to values "now() + 2 weeks" being inserted above. -- Ex: Current week is 39. +2 weeks is 41. Going back 5 weeks is 36, but constraint is applied to the table OLDER than that value. Hence constraint will be on week 35 SELECT col_has_check('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'col1' , 'Check for additional constraint on col1 on time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' (-4 weeks)'); -- Must run apply_constraints() to manually set the other older constraints SELECT apply_constraints('partman_test.time_taptest_table', 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')); SELECT apply_constraints('partman_test.time_taptest_table', 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')); SELECT apply_constraints('partman_test.time_taptest_table', 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')); SELECT apply_constraints('partman_test.time_taptest_table', 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')); SELECT col_has_check('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW'), 'col1' , 'Check for additional constraint on col1 on time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')); SELECT col_has_check('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW'), 'col1' , 'Check for additional constraint on col1 on time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')); SELECT col_has_check('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), 'col1' , 'Check for additional constraint on col1 on time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')); SELECT col_has_check('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), 'col1' , 'Check for additional constraint on col1 on time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(111,120), extract('epoch' from CURRENT_TIMESTAMP + '3 weeks'::interval)::int); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' exists (+3 weeks'); -- Cannot test for next week not existing. Different lengths of months will sometimes cause an extra partition. -- Add back when index support is available --SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], -- 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' (+3 weeks)'); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' (+3 weeks)'); UPDATE part_config SET premake = 4 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(121,130), extract('epoch' from CURRENT_TIMESTAMP + '4 weeks'::interval)::int); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[130], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' (+4 weeks)'); SELECT col_has_check('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'col1' , 'Check for additional constraint on col1 on time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' exists (+4 weeks)'); -- Cannot test for next week not existing. Different lengths of months will sometimes cause an extra partition. -- Add back when index support is available --SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], -- 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' (+4 weeks)'); -- Add back when default partition is supported --INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), extract('epoch' from CURRENT_TIMESTAMP + '20 weeks'::interval)::int); --SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT drop_partition_time('partman_test.time_taptest_table', '3 weeks', p_keep_table := false); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' does not exist (-4 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||' does not exist (-5 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||' does not exist (-6 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')||' does not exist (-7 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')||' does not exist (-8 weeks)'); UPDATE part_config SET retention = '2 weeks'::interval WHERE parent_table = 'partman_test.time_taptest_table'; SELECT drop_partition_time('partman_test.time_taptest_table', p_retention_schema := 'partman_retention_test'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' does not exist (-3 weeks)'); SELECT has_table('partman_retention_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' got moved to new schema (-3 weeks)'); SELECT undo_partition('partman_test.time_taptest_table', 20, p_target_table := 'partman_test.undo_taptest', p_keep_table := false); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.undo_taptest', ARRAY[81], 'Check count from target table after undo'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||' does not exist (now)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' does not exist (+1 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||' does not exist (+2 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' does not exist (+3 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' does not exist (+4 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' does not exist (-1 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' does not exist (-2 weeks)'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test_native/test-time-gap-fill.sql000066400000000000000000000510071412761706100233120ustar00rootroot00000000000000-- ########## TIME DAILY TESTS ########## -- Other tests: test that gap in child tables backfill works -- Test using default template table. Initial child tables will have no indexes. New tables after template has indexes added should. \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(76); CREATE SCHEMA partman_test; CREATE TABLE partman_test.time_taptest_table (col1 int, col2 text default 'stuff', col3 timestamptz NOT NULL DEFAULT now()) PARTITION BY RANGE (col3); SELECT create_parent('partman_test.time_taptest_table', 'col3', 'native', 'daily'); SELECT is_partitioned('partman_test', 'time_taptest_table', 'Check that time_taptest_table is natively partitioned'); SELECT has_table('partman', 'template_partman_test_time_taptest_table', 'Check that default template table was created'); -- Add inheritable stuff to template table ALTER TABLE partman.template_partman_test_time_taptest_table ADD PRIMARY KEY (col1); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); 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'); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO 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 is empty. Should be impossible for native, but leaving test here just cause.'); 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')); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '1 day'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '2 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(26,30), CURRENT_TIMESTAMP + '3 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(31,37), CURRENT_TIMESTAMP + '4 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(40,49), CURRENT_TIMESTAMP - '1 day'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(50,70), CURRENT_TIMESTAMP - '2 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(71,85), CURRENT_TIMESTAMP - '3 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(86,100), CURRENT_TIMESTAMP - '4 days'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY[10], '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[5], '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[5], '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[7], '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[10], '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[21], '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[15], '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[15], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); UPDATE part_config SET premake = 5 WHERE parent_table = 'partman_test.time_taptest_table'; -- Run to create proper future partitions SELECT run_maintenance(); -- Insert after maintenance since native fails with no child INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(101,122), CURRENT_TIMESTAMP + '5 days'::interval); -- Run again to create +5 partition now that data exists SELECT run_maintenance(); -- Data exists for +5 days, with 5 premake so +10 day table should exist SELECT has_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')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY[22], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); UPDATE part_config SET premake = 6, optimize_trigger = 6 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(123,150), CURRENT_TIMESTAMP + '6 days'::interval); -- Run again now that +6 data exists SELECT run_maintenance(); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[148], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), ARRAY[28], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'13 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'13 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')); -- Test gap fill. DO $$ BEGIN EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'); END $$; SELECT hasnt_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')||' was dropped'); 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+'6 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' was dropped'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' was dropped'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||' was dropped'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' was dropped'); SELECT partman.partition_gap_fill('partman_test.time_taptest_table'); 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 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 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 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 has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test_native/test-time-id-subpart-native.sql000066400000000000000000002701031412761706100251550ustar00rootroot00000000000000-- ########## 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 timestamptz NOT NULL DEFAULT now()) 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'); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); 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), CURRENT_TIMESTAMP); 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), CURRENT_TIMESTAMP); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,35), CURRENT_TIMESTAMP + '1 day'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,35), CURRENT_TIMESTAMP + '2 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,35), CURRENT_TIMESTAMP + '3 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,35), CURRENT_TIMESTAMP + '4 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,35), CURRENT_TIMESTAMP - '1 day'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,35), CURRENT_TIMESTAMP - '2 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,35), CURRENT_TIMESTAMP - '3 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,35), CURRENT_TIMESTAMP - '4 days'::interval); 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), CURRENT_TIMESTAMP); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(36,50), CURRENT_TIMESTAMP + '1 day'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(36,50), CURRENT_TIMESTAMP + '2 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(36,50), CURRENT_TIMESTAMP + '3 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(36,50), CURRENT_TIMESTAMP + '4 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,50), CURRENT_TIMESTAMP + '5 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(36,50), CURRENT_TIMESTAMP - '1 day'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(36,50), CURRENT_TIMESTAMP - '2 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(36,50), CURRENT_TIMESTAMP - '3 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(36,50), CURRENT_TIMESTAMP - '4 days'::interval); 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; pg_partman-4.6.0/test/test_native/test-time-native-mixed-case.sql000066400000000000000000002003271412761706100251230ustar00rootroot00000000000000-- ########## TIME DAILY TESTS ########## -- Other tests: check that maintenance catches up if tables are missing -- Test using default template table. Initial child tables will have no indexes. New tables after template has indexes added should. -- Mixed case \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(221); CREATE SCHEMA "Partman_Test"; CREATE SCHEMA "Partman_Retention_Test"; CREATE ROLE "Partman_Basic"; CREATE ROLE "Partman_Revoke"; CREATE ROLE "Partman_Owner"; CREATE TABLE "Partman_Test"."FK_Test_Reference" ("Col2" text unique not null); INSERT INTO "Partman_Test"."FK_Test_Reference" VALUES ('stuff'); CREATE TABLE "Partman_Test"."Time_Taptest_Table" ("Col1" int, "Col2" text default 'stuff', "Col3" timestamptz NOT NULL DEFAULT now()) PARTITION BY RANGE ("Col3"); CREATE TABLE "Partman_Test"."Undo_Taptest" (LIKE "Partman_Test"."Time_Taptest_Table" INCLUDING ALL); GRANT SELECT,INSERT,UPDATE ON "Partman_Test"."Time_Taptest_Table" TO "Partman_Basic"; GRANT ALL ON "Partman_Test"."Time_Taptest_Table" TO "Partman_Revoke"; ALTER TABLE "Partman_Test"."Time_Taptest_Table" OWNER TO "Partman_Owner"; SELECT create_parent('Partman_Test.Time_Taptest_Table', 'Col3', 'native', 'daily'); UPDATE part_config SET inherit_privileges = TRUE; SELECT reapply_privileges('Partman_Test.Time_Taptest_Table'); SELECT is_partitioned('Partman_Test', 'Time_Taptest_Table', 'Check that Time_Taptest_Table is natively partitioned'); SELECT has_table('partman', 'template_Partman_Test_Time_Taptest_Table', 'Check that default template table was created'); SELECT table_owner_is ('partman', 'template_Partman_Test_Time_Taptest_Table', 'Partman_Owner', 'Check that template table ownership is set properly'); -- Add inheritable stuff to template table ALTER TABLE partman."template_Partman_Test_Time_Taptest_Table" ADD PRIMARY KEY ("Col1"); ALTER TABLE partman."template_Partman_Test_Time_Taptest_Table" ADD FOREIGN KEY ("Col2") REFERENCES "Partman_Test"."FK_Test_Reference"("Col2"); INSERT INTO "Partman_Test"."Time_Taptest_Table" ("Col1", "Col3") VALUES (generate_series(1,10), CURRENT_TIMESTAMP); 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'); SELECT col_isnt_pk('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['Col1'], 'Check for NO primary key in Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_isnt_pk('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['Col1'], 'Check for NO primary key in Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), ARRAY['Col1'], 'Check for NO primary key in Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), ARRAY['Col1'], 'Check for NO primary key in Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), ARRAY['Col1'], 'Check for NO primary key in Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['Col1'], 'Check for NO primary key in Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_isnt_pk('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), ARRAY['Col1'], 'Check for NO primary key in Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), ARRAY['Col1'], 'Check for NO primary key in Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), ARRAY['Col1'], 'Check for NO primary key in Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), ARRAY['Col1'], 'Check for NO primary key in Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); SELECT col_isnt_fk('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['Col2'], 'Check for NO foreign key in Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_isnt_fk('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['Col2'], 'Check for NO foreign key in Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT col_isnt_fk('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'2 day'::interval, 'YYYY_MM_DD'), ARRAY['Col2'], 'Check for NO foreign key in Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'2 day'::interval, 'YYYY_MM_DD')); SELECT col_isnt_fk('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'3 day'::interval, 'YYYY_MM_DD'), ARRAY['Col2'], 'Check for NO foreign key in Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'3 day'::interval, 'YYYY_MM_DD')); SELECT col_isnt_fk('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'4 day'::interval, 'YYYY_MM_DD'), ARRAY['Col2'], 'Check for NO foreign key in Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'4 day'::interval, 'YYYY_MM_DD')); SELECT col_isnt_fk('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), ARRAY['Col2'], 'Check for NO foreign key in Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT col_isnt_fk('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'2 day'::interval, 'YYYY_MM_DD'), ARRAY['Col2'], 'Check for NO foreign key in Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'2 day'::interval, 'YYYY_MM_DD')); SELECT col_isnt_fk('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'3 day'::interval, 'YYYY_MM_DD'), ARRAY['Col2'], 'Check for NO foreign key in Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'3 day'::interval, 'YYYY_MM_DD')); SELECT col_isnt_fk('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'4 day'::interval, 'YYYY_MM_DD'), ARRAY['Col2'], 'Check for NO foreign key in Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'4 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Partman_Revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check Partman_Revoke privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Partman_Revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check Partman_Revoke privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Partman_Revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check Partman_Revoke privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Partman_Revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check Partman_Revoke privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Partman_Revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check Partman_Revoke privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Partman_Revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check Partman_Revoke privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Partman_Revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check Partman_Revoke privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Partman_Revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check Partman_Revoke privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Partman_Revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check Partman_Revoke privileges of 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 is empty. Should be impossible for native, but leaving test here just cause.'); 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')); REVOKE INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON "Partman_Test"."Time_Taptest_Table" FROM "Partman_Revoke"; INSERT INTO "Partman_Test"."Time_Taptest_Table" ("Col1", "Col3") VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '1 day'::interval); INSERT INTO "Partman_Test"."Time_Taptest_Table" ("Col1", "Col3") VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '2 days'::interval); INSERT INTO "Partman_Test"."Time_Taptest_Table" ("Col1", "Col3") VALUES (generate_series(26,30), CURRENT_TIMESTAMP + '3 days'::interval); INSERT INTO "Partman_Test"."Time_Taptest_Table" ("Col1", "Col3") VALUES (generate_series(31,37), CURRENT_TIMESTAMP + '4 days'::interval); INSERT INTO "Partman_Test"."Time_Taptest_Table" ("Col1", "Col3") VALUES (generate_series(40,49), CURRENT_TIMESTAMP - '1 day'::interval); INSERT INTO "Partman_Test"."Time_Taptest_Table" ("Col1", "Col3") VALUES (generate_series(50,70), CURRENT_TIMESTAMP - '2 days'::interval); INSERT INTO "Partman_Test"."Time_Taptest_Table" ("Col1", "Col3") VALUES (generate_series(71,85), CURRENT_TIMESTAMP - '3 days'::interval); INSERT INTO "Partman_Test"."Time_Taptest_Table" ("Col1", "Col3") VALUES (generate_series(86,100), CURRENT_TIMESTAMP - '4 days'::interval); SELECT is_empty('SELECT * FROM ONLY "Partman_Test"."Time_Taptest_Table"', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM "Partman_Test"."Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||'"', ARRAY[10], '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[5], '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[5], '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[7], '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[10], '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[21], '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[15], '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[15], 'Check count from Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); UPDATE part_config SET premake = 5 WHERE parent_table = 'Partman_Test.Time_Taptest_Table'; -- Run to create proper future partitions SELECT run_maintenance(); -- Insert after maintenance since native fails with no child INSERT INTO "Partman_Test"."Time_Taptest_Table" ("Col1", "Col3") VALUES (generate_series(101,122), CURRENT_TIMESTAMP + '5 days'::interval); -- Run again to create +5 partition now that data exists SELECT run_maintenance(); -- Data exists for +5 days, with 5 premake so +10 day table should exist SELECT has_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')||' exists'); SELECT has_table('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'Check Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Check Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY['Col1'], 'Check for primary key in Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), ARRAY['Col1'], 'Check for primary key in Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), ARRAY['Col1'], 'Check for primary key in Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), ARRAY['Col1'], 'Check for primary key in Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), ARRAY['Col1'], 'Check for primary key in Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), ARRAY['Col1'], 'Check for primary key in Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY "Partman_Test"."Time_Taptest_Table"', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM "Partman_Test"."Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'"', ARRAY[22], 'Check count from Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Partman_Revoke', ARRAY['SELECT'], 'Check Partman_Revoke privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); GRANT DELETE ON "Partman_Test"."Time_Taptest_Table" TO "Partman_Basic"; REVOKE ALL ON "Partman_Test"."Time_Taptest_Table" FROM "Partman_Revoke"; ALTER TABLE "Partman_Test"."Time_Taptest_Table" OWNER TO "Partman_Owner"; UPDATE part_config SET premake = 6, optimize_trigger = 6 WHERE parent_table = 'Partman_Test.Time_Taptest_Table'; SELECT run_maintenance(); INSERT INTO "Partman_Test"."Time_Taptest_Table" ("Col1", "Col3") VALUES (generate_series(123,150), CURRENT_TIMESTAMP + '6 days'::interval); -- Run again now that +6 data exists SELECT run_maintenance(); SELECT is_empty('SELECT * FROM ONLY "Partman_Test"."Time_Taptest_Table"', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM "Partman_Test"."Time_Taptest_Table"', ARRAY[148], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM "Partman_Test"."Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||'"', ARRAY[28], 'Check count from Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT has_table('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'Check Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'13 days'::interval, 'YYYY_MM_DD'), 'Check Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'13 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), ARRAY['Col1'], 'Check for primary key in Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), ARRAY['Col1'], 'Check for primary key in Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Partman_Revoke', ARRAY['SELECT'], 'Check Partman_Revoke privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Partman_Revoke', ARRAY['SELECT'], 'Check Partman_Revoke privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Partman_Revoke', ARRAY['SELECT'], 'Check Partman_Revoke privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Partman_Revoke', ARRAY['SELECT'], 'Check Partman_Revoke privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'Partman_Revoke', ARRAY['SELECT'], 'Check Partman_Revoke privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Partman_Revoke', ARRAY['SELECT'], 'Check Partman_Revoke privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Partman_Revoke', '{}'::text[], 'Check Partman_Revoke privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'Partman_Revoke', '{}'::text[], 'Check Partman_Revoke privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Partman_Owner', 'Check that ownership change worked for Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'Partman_Owner', 'Check that ownership change worked for Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')); SELECT reapply_privileges('Partman_Test.Time_Taptest_Table'); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Partman_Basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check Partman_Basic privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Partman_Revoke', '{}'::text[], 'Check Partman_Revoke privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Partman_Revoke', '{}'::text[], 'Check Partman_Revoke privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Partman_Revoke', '{}'::text[], 'Check Partman_Revoke privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Partman_Revoke', '{}'::text[], 'Check Partman_Revoke privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Partman_Revoke', '{}'::text[], 'Check Partman_Revoke privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Partman_Revoke', '{}'::text[], 'Check Partman_Revoke privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Partman_Revoke', '{}'::text[], 'Check Partman_Revoke privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Partman_Revoke', '{}'::text[], 'Check Partman_Revoke privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Partman_Revoke', '{}'::text[], 'Check Partman_Revoke privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'Partman_Revoke', '{}'::text[], 'Check Partman_Revoke privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Partman_Revoke', '{}'::text[], 'Check Partman_Revoke privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Partman_Revoke', '{}'::text[], 'Check Partman_Revoke privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'Partman_Revoke', '{}'::text[], 'Check Partman_Revoke privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Partman_Revoke', '{}'::text[], 'Check Partman_Revoke privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Partman_Revoke', '{}'::text[], 'Check Partman_Revoke privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Partman_Revoke', '{}'::text[], 'Check Partman_Revoke privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Partman_Revoke', '{}'::text[], 'Check Partman_Revoke privileges of Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Partman_Owner', 'Check that ownership change worked for Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_owner_is ('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Partman_Owner', 'Check that ownership change worked for Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Partman_Owner', 'Check that ownership change worked for Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Partman_Owner', 'Check that ownership change worked for Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Partman_Owner', 'Check that ownership change worked for Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Partman_Owner', 'Check that ownership change worked for Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Partman_Owner', 'Check that ownership change worked for Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Partman_Owner', 'Check that ownership change worked for Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Partman_Owner', 'Check that ownership change worked for Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'Partman_Owner', 'Check that ownership change worked for Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Partman_Owner', 'Check that ownership change worked for Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Partman_Owner', 'Check that ownership change worked for Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'Partman_Owner', 'Check that ownership change worked for Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Partman_Owner', 'Check that ownership change worked for Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Partman_Owner', 'Check that ownership change worked for Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Partman_Owner', 'Check that ownership change worked for Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Partman_Owner', 'Check that ownership change worked for Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); -- Test that maintenance will catch up DO $$ BEGIN EXECUTE 'DROP TABLE "Partman_Test"."Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||'"'; EXECUTE 'DROP TABLE "Partman_Test"."Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD')||'"'; EXECUTE 'DROP TABLE "Partman_Test"."Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||'"'; EXECUTE 'DROP TABLE "Partman_Test"."Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'1 days'::interval, 'YYYY_MM_DD')||'"'; EXECUTE 'DROP TABLE "Partman_Test"."Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||'"'; EXECUTE 'DROP TABLE "Partman_Test"."Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||'"'; EXECUTE 'DROP TABLE "Partman_Test"."Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||'"'; EXECUTE 'DROP TABLE "Partman_Test"."Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||'"'; EXECUTE 'DROP TABLE "Partman_Test"."Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||'"'; EXECUTE 'DROP TABLE "Partman_Test"."Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||'"'; EXECUTE 'DROP TABLE "Partman_Test"."Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||'"'; EXECUTE 'DROP TABLE "Partman_Test"."Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||'"'; EXECUTE 'DROP TABLE "Partman_Test"."Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||'"'; EXECUTE 'DROP TABLE "Partman_Test"."Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||'"'; EXECUTE 'DROP TABLE "Partman_Test"."Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')||'"'; END $$; SELECT hasnt_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')||' does not exist'); SELECT hasnt_table('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'), 'Check Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_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')||' does not exist'); SELECT hasnt_table('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'1 days'::interval, 'YYYY_MM_DD'), 'Check Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'1 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_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')||' does not exist'); 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')||' does not exist'); 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')||' does not exist'); 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 hasnt_table('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'Check Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Check Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'Check Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT run_maintenance(); 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')||' does exist'); SELECT has_table('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'), 'Check Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD')||' does exist'); 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')||' does exist'); SELECT has_table('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'1 days'::interval, 'YYYY_MM_DD'), 'Check Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'1 days'::interval, 'YYYY_MM_DD')||' does exist'); 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')||' does exist'); 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')||' does exist'); 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')||' does not exist'); INSERT INTO "Partman_Test"."Time_Taptest_Table" ("Col1", "Col3") VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '1 day'::interval); INSERT INTO "Partman_Test"."Time_Taptest_Table" ("Col1", "Col3") VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '2 days'::interval); INSERT INTO "Partman_Test"."Time_Taptest_Table" ("Col1", "Col3") VALUES (generate_series(26,30), CURRENT_TIMESTAMP + '3 days'::interval); INSERT INTO "Partman_Test"."Time_Taptest_Table" ("Col1", "Col3") VALUES (generate_series(1,10), CURRENT_TIMESTAMP); INSERT INTO "Partman_Test"."Time_Taptest_Table" ("Col1", "Col3") VALUES (generate_series(40,49), CURRENT_TIMESTAMP - '1 day'::interval); INSERT INTO "Partman_Test"."Time_Taptest_Table" ("Col1", "Col3") VALUES (generate_series(50,70), CURRENT_TIMESTAMP - '2 days'::interval); SELECT run_maintenance(); 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')||' does exist'); SELECT has_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 exist'); SELECT has_table('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' does exist'); SELECT has_table('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' does exist'); SELECT has_table('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' does exist'); SELECT has_table('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'Check Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||' does exist'); SELECT hasnt_table('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Check Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||' does not exist'); INSERT INTO "Partman_Test"."Time_Taptest_Table" ("Col1", "Col3") VALUES (generate_series(31,37), CURRENT_TIMESTAMP + '4 days'::interval); INSERT INTO "Partman_Test"."Time_Taptest_Table" ("Col1", "Col3") VALUES (generate_series(101,122), CURRENT_TIMESTAMP + '5 days'::interval); INSERT INTO "Partman_Test"."Time_Taptest_Table" ("Col1", "Col3") VALUES (generate_series(123,150), CURRENT_TIMESTAMP + '6 days'::interval); SELECT run_maintenance(); SELECT has_table('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Check Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||' does exist'); SELECT has_table('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' does exist'); SELECT has_table('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'Check Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')||' does exist'); SELECT hasnt_table('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'13 days'::interval, 'YYYY_MM_DD'), 'Check Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'13 days'::interval, 'YYYY_MM_DD')||' does not exist'); ----- This test won't work unless a default table option is added to native --INSERT INTO "Partman_Test"."Time_Taptest_Table" ("Col1", "Col3") VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '20 days'::interval); --SELECT results_eq('SELECT count(*)::int FROM ONLY "Partman_Test"."Time_Taptest_Table"', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT drop_partition_time('Partman_Test.Time_Taptest_Table', '3 days', p_keep_table := false); 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')||' does not exist'); UPDATE part_config SET retention = '2 days'::interval WHERE parent_table = 'Partman_Test.Time_Taptest_Table'; SELECT drop_partition_time('Partman_Test.Time_Taptest_Table', p_retention_schema := 'Partman_Retention_Test'); 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')||' does not exist'); SELECT has_table('Partman_Retention_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')||' got moved to new schema'); SELECT undo_partition('Partman_Test.Time_Taptest_Table', 20, p_target_table := 'Partman_Test.Undo_Taptest', p_keep_table := false); SELECT results_eq('SELECT count(*)::int FROM "Partman_Test"."Undo_Taptest"', ARRAY[118], 'Check count from target table after undo'); SELECT hasnt_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')||' does not exist'); SELECT hasnt_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')||' does not exist'); SELECT hasnt_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')||' does not exist'); 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')||' does not exist'); 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')||' does not exist'); 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 hasnt_table('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'Check Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Check Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('Partman_Test', 'Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'Check Time_Taptest_Table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_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')||' does not exist'); SELECT hasnt_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')||' does not exist'); SELECT hasnt_table('partman', 'Template_Partman_Test_Time_Taptest_Table', 'Check that template table was dropped'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test_native/test_default_child/000077500000000000000000000000001412761706100230105ustar00rootroot00000000000000pg_partman-4.6.0/test/test_native/test_default_child/test-id-default-child.sql000066400000000000000000000165711412761706100276170ustar00rootroot00000000000000-- ########## ID DYNAMIC TESTS ########## -- These tests only work on PG11+ -- Additional tests: Check that data can be moved from default to proper child tables -- Test using a pre-created template table and passing to create_parent. Should allow indexes to be made for initial children. \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(38); CREATE SCHEMA partman_test; CREATE TABLE partman_test.id_taptest_table ( col1 bigint , col2 text not null default 'stuff' , col3 timestamptz DEFAULT now() , col4 text) PARTITION BY RANGE (col1); CREATE TABLE partman_test.undo_taptest (LIKE partman_test.id_taptest_table INCLUDING ALL); -- Template table CREATE TABLE partman_test.template_id_taptest_table (LIKE partman_test.id_taptest_table); -- Regular unique indexes do not work on native in PG11 if the partition key isn't included CREATE UNIQUE INDEX ON partman_test.template_id_taptest_table (col4); -- Create on template table ALTER TABLE partman_test.template_id_taptest_table ADD PRIMARY KEY (col1); CREATE INDEX ON partman_test.template_id_taptest_table (col3); SELECT create_parent('partman_test.id_taptest_table', 'col1', 'native', '10', p_jobmon := false, p_start_partition := '3000000000', p_template_table := 'partman_test.template_id_taptest_table'); INSERT INTO partman_test.id_taptest_table (col1, col4) VALUES (generate_series(3000000001,3000000009), 'stuff'||generate_series(3000000001,3000000009)); SELECT has_table('partman_test', 'id_taptest_table_default', 'Check id_taptest_table_default exists'); SELECT has_table('partman_test', 'id_taptest_table_p3000000000', 'Check id_taptest_table_p3000000000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p3000000010', 'Check id_taptest_table_p3000000010 exists'); SELECT has_table('partman_test', 'id_taptest_table_p3000000020', 'Check id_taptest_table_p3000000020 exists'); SELECT has_table('partman_test', 'id_taptest_table_p3000000030', 'Check id_taptest_table_p3000000030 exists'); SELECT has_table('partman_test', 'id_taptest_table_p3000000040', 'Check id_taptest_table_p3000000040 exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p3000000050', 'Check id_taptest_table_p3000000050 doesn''t exists yet'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000000', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000000'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000010', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000010'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000020', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000020'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000030', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000030'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000040', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000040'); SELECT col_is_pk('partman_test', 'id_taptest_table_default', ARRAY['col1'], 'Check for primary key in id_taptest_table_default'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[9], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p3000000000', ARRAY[9], 'Check count from id_taptest_table_p3000000000'); SELECT run_maintenance(); INSERT INTO partman_test.id_taptest_table (col1, col4) VALUES (generate_series(3000000010,3000000025), 'stuff'||generate_series(3000000010,3000000025)); -- Run again to make new partition based on latest data SELECT run_maintenance(); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p3000000010', ARRAY[10], 'Check count from id_taptest_table_p3000000010'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p3000000020', ARRAY[6], 'Check count from id_taptest_table_p3000000020'); SELECT has_table('partman_test', 'id_taptest_table_p3000000050', 'Check id_taptest_table_p3000000050 exists'); SELECT has_table('partman_test', 'id_taptest_table_p3000000060', 'Check id_taptest_table_p3000000060 exists yet'); SELECT hasnt_table('partman_test', 'id_taptest_table_p3000000070', 'Check id_taptest_table_p3000000070 doesn''t exists yet'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000050', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000050'); INSERT INTO partman_test.id_taptest_table (col1, col4) VALUES (generate_series(3000000026,3000000038), 'stuff'||generate_series(3000000026,3000000038)); SELECT run_maintenance(); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[38], 'Check count from id_taptest_table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p3000000020', ARRAY[10], 'Check count from id_taptest_table_p3000000020'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p3000000030', ARRAY[9], 'Check count from id_taptest_table_p3000000030'); SELECT has_table('partman_test', 'id_taptest_table_p3000000070', 'Check id_taptest_table_p3000000070 exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p3000000080', 'Check id_taptest_table_p3000000080 doesn''t exists yet'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000060', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000060'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000070', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000070'); -- Requires default table option INSERT INTO partman_test.id_taptest_table (col1, col4) VALUES (generate_series(3000000200,3000000210), 'stuff'||generate_series(3000000200,3000000210)); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_default', ARRAY[11], 'Check that data with no specific child goes to default'); SELECT throws_ok('SELECT partman.partition_data_id(''partman_test.id_taptest_table'', 10, p_batch_interval := 2)', 'Custom intervals are not allowed when moving data out of the DEFAULT partition in a native set. Please leave p_interval/p_batch_interval parameters unset or NULL to allow use of partition set''s default partitioning interval.', 'Check that interval cannot be set when partitioning off the default child'); -- Move data to proper child SELECT partition_data_id('partman_test.id_taptest_table', 10); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_default', 'Check that data was removed from default.'); SELECT has_table('partman_test', 'id_taptest_table_p3000000200', 'Check id_taptest_table_p3000000200 exists'); SELECT has_table('partman_test', 'id_taptest_table_p3000000210', 'Check id_taptest_table_p3000000210 exists'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p3000000200', ARRAY[10], 'Check count from id_taptest_table_p3000000200 after moving from default'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p3000000210', ARRAY[1], 'Check count from id_taptest_table_p3000000210 after moving from default'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test_native/test_default_child/test-time-1day-8hr-subpart-native.sql000066400000000000000000000701251412761706100317460ustar00rootroot00000000000000-- ########## TIME 9 WEEKS TESTS ########## \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(49); CREATE SCHEMA partman_test; CREATE TABLE partman_test.time_taptest_table (col1 int, col2 text default 'stuff', col3 timestamptz NOT NULL DEFAULT now()) 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', '1 day'); SELECT is_partitioned('partman_test', 'time_taptest_table', 'Check that time_taptest_table is natively partitioned'); SELECT has_table('partman', 'template_partman_test_time_taptest_table', 'Check that default template table was created'); 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 (+1 day)'); 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 (+4 days)'); 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 (-4 days)'); -- Create subpartition -- Need to set a start partition to ensure that the hourly blocks start at midnight. Otherwise, they will be created based on "now", which means the child tables could miss coverage of some hours during the day. SELECT partman.create_sub_parent('partman_test.time_taptest_table', 'col3', 'native', '8 hours', p_native_check := 'yes', p_date_trunc_interval := 'day', p_start_partition := date_trunc('day', CURRENT_TIMESTAMP)::text); --Insert "now" data here due to child table destruction INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), date_trunc('day',CURRENT_TIMESTAMP) + '24 hours'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), date_trunc('day',CURRENT_TIMESTAMP) + '32 hours'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), date_trunc('day',CURRENT_TIMESTAMP) + '40 hours'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), date_trunc('day',CURRENT_TIMESTAMP) + '48 hours'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), date_trunc('day',CURRENT_TIMESTAMP) + '56 hours'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), date_trunc('day',CURRENT_TIMESTAMP) + '64 hours'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(26,30), date_trunc('day',CURRENT_TIMESTAMP) + '72 hours'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(26,30), date_trunc('day',CURRENT_TIMESTAMP) + '80 hours'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(26,30), date_trunc('day',CURRENT_TIMESTAMP) + '88 hours'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(31,37), date_trunc('day',CURRENT_TIMESTAMP) + '96 hours'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(31,37), date_trunc('day',CURRENT_TIMESTAMP) + '104 hours'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(31,37), date_trunc('day',CURRENT_TIMESTAMP) + '112 hours'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), date_trunc('day',CURRENT_TIMESTAMP) - '24 hours'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), date_trunc('day',CURRENT_TIMESTAMP) - '32 hours'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), date_trunc('day',CURRENT_TIMESTAMP) - '40 hours'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), date_trunc('day',CURRENT_TIMESTAMP) - '48 hours'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), date_trunc('day',CURRENT_TIMESTAMP) - '56 hours'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), date_trunc('day',CURRENT_TIMESTAMP) - '64 hours'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(26,30), date_trunc('day',CURRENT_TIMESTAMP) - '72 hours'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(26,30), date_trunc('day',CURRENT_TIMESTAMP) - '80 hours'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(26,30), date_trunc('day',CURRENT_TIMESTAMP) - '88 hours'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(31,37), date_trunc('day',CURRENT_TIMESTAMP) - '96 hours'::interval); SELECT partman.partition_data_time('partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 20); SELECT partman.partition_data_time('partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'24 hours'::interval, 'YYYY_MM_DD'), 20); SELECT partman.partition_data_time('partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'48 hours'::interval, 'YYYY_MM_DD'), 20); SELECT partman.partition_data_time('partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'72 hours'::interval, 'YYYY_MM_DD'), 20); SELECT partman.partition_data_time('partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'96 hours'::interval, 'YYYY_MM_DD'), 20); SELECT partman.partition_data_time('partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'24 hours'::interval, 'YYYY_MM_DD'), 20); SELECT partman.partition_data_time('partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'48 hours'::interval, 'YYYY_MM_DD'), 20); SELECT partman.partition_data_time('partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'72 hours'::interval, 'YYYY_MM_DD'), 20); SELECT partman.partition_data_time('partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'96 hours'::interval, 'YYYY_MM_DD'), 20); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_default', 'Check that default table has had no data inserted to it'); -- Start +4 days validation SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'24 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'24 hours'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[10], 'Check count from time_taptest_table_p'|| to_char(CURRENT_TIMESTAMP+'24 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'24 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' (+24hrs)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'32 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'32 hours'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[10], 'Check count from time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'32 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'32 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' (+32hrs)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'40 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'40 hours'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[10], 'Check count from time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'40 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'40 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' (+40hrs)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'48 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'48 hours'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[5], 'Check count from time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'48 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'48 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' (+48hrs)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'56 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'56 hours'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[5], 'Check count from time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'56 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'56 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' (+56hrs)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'64 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'64 hours'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[5], 'Check count from time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'64 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'64 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' (+64hrs)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'72 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'72 hours'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[5], 'Check count from time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'72 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'72 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' (+72hrs)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'80 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'80 hours'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[5], 'Check count from time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'80 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'80 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' (+80hrs)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'88 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'88 hours'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[5], 'Check count from time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'88 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'88 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' (+88hrs)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'96 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'96 hours'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[7], 'Check count from time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'96 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'96 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' (+96hrs)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'104 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'104 hours'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[7], 'Check count from time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'104 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'104 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' (+104hrs)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'112 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'112 hours'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[7], 'Check count from time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'112 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'112 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' (+112hrs)'); -- End + 4 days UPDATE part_config SET premake = 5 WHERE parent_table = 'partman_test.time_taptest_table'; -- Run to create proper future partitions SELECT run_maintenance(); SELECT has_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')||' exists (+5 days)'); SELECT has_table('partman_test', 'time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'120 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'120 hours'::interval, 'YYYY_MM_DD_HH24MISS') , 'Check time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'120 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'120 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' exists (+120hrs)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'128 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'128 hours'::interval, 'YYYY_MM_DD_HH24MISS') , 'Check time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'128 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'128 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist (+128hrs)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'+6 days'::interval, 'YYYY_MM_DD')||' exists (+6 days)'); SELECT has_table('partman_test', 'time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'144 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'144 hours'::interval, 'YYYY_MM_DD_HH24MISS') , 'Check time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'144 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'144 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' exists (+144hrs)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'144 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'152 hours'::interval, 'YYYY_MM_DD_HH24MISS') , 'Check time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'144 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'152 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist (+152hrs)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'+7 days'::interval, 'YYYY_MM_DD')||' exists (+7 days)'); SELECT has_table('partman_test', 'time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'168 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'168 hours'::interval, 'YYYY_MM_DD_HH24MISS') , 'Check time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'168 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'168 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' exists (+168hrs)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'168 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'176 hours'::interval, 'YYYY_MM_DD_HH24MISS') , 'Check time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'168 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'176 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist (+176hrs)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'+8 days'::interval, 'YYYY_MM_DD')||' exists (+8 days)'); SELECT has_table('partman_test', 'time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'192 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'192 hours'::interval, 'YYYY_MM_DD_HH24MISS') , 'Check time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'192 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'192 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' exists (+192hrs)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'192 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'200 hours'::interval, 'YYYY_MM_DD_HH24MISS') , 'Check time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'192 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'200 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist (+200hrs)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'+9 days'::interval, 'YYYY_MM_DD')||' exists (+9 days)'); SELECT has_table('partman_test', 'time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'216 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'216 hours'::interval, 'YYYY_MM_DD_HH24MISS') , 'Check time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'216 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'216 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' exists (+216hrs)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'216 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'224 hours'::interval, 'YYYY_MM_DD_HH24MISS') , 'Check time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'216 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'224 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist (+224hrs)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'+10 days'::interval, 'YYYY_MM_DD')||' does not exist (+10 days)'); -- Insert after maintenance since native would put it into default INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(40,49), date_trunc('day',CURRENT_TIMESTAMP) + '120 hours'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(40,49), date_trunc('day',CURRENT_TIMESTAMP) + '128 hours'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(40,49), date_trunc('day',CURRENT_TIMESTAMP) + '136 hours'::interval); -- Partition the data out since new child tables cannot be created with data in the +120 hour default SELECT partman.partition_data_time('partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'120 hours'::interval, 'YYYY_MM_DD'), 20); -- Run again to create expected +5 premake partitions now that data exists SELECT run_maintenance(); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_default', 'Check that default table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'120 hours'::interval, 'YYYY_MM_DD'), ARRAY[30], 'Check count from time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'120 hours'::interval, 'YYYY_MM_DD')||' (+5 days)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'120 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'120 hours'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[10], 'Check count from time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'120 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'120 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' (+120hrs)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'120 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'128 hours'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[10], 'Check count from time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'120 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'128 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' (+128hrs)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'120 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'136 hours'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[10], 'Check count from time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'120 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'136 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' (+136hrs)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'+10 days'::interval, 'YYYY_MM_DD')||' exists (+10 days)'); SELECT has_table('partman_test', 'time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'240 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'240 hours'::interval, 'YYYY_MM_DD_HH24MISS') , 'Check time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'240 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'240 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' exists (+240hrs)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'240 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'248 hours'::interval, 'YYYY_MM_DD_HH24MISS') , 'Check time_taptest_table_p'|| to_char(date_trunc('day',CURRENT_TIMESTAMP)+'240 hours'::interval, 'YYYY_MM_DD')|| '_p'||to_char(date_trunc('day',CURRENT_TIMESTAMP)+'248 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist (+248hrs)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'+11 days'::interval, 'YYYY_MM_DD')||' does not exist (+11 days)'); SELECT drop_partition_time('partman_test.time_taptest_table', '3 days', p_keep_table := false); 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')||' does not exist (4 days)'); 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 (5 days)'); UPDATE part_config SET retention = '1 day', retention_keep_table = 'false' WHERE parent_table = 'partman_test.time_taptest_table'; SELECT partman.run_maintenance(); SELECT hasnt_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')||' does not exist (2 days)'); 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')||' does not exist (3 days)'); SELECT undo_partition('partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 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, '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 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+'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+'10 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', 20, p_target_table := 'partman_test.undo_taptest', p_keep_table := false); SELECT is_empty('select relname from pg_class c join pg_namespace n on c.relnamespace = n.oid where n.nspname = ''partman_test'' and c.relname ~ ''time_taptest_table_p''', 'Check that no partition child tables for this set exist in system catalogs'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test_native/test_default_child/test-time-7hour-native.sql000066400000000000000000000470761412761706100300100ustar00rootroot00000000000000-- ########## TIME 9 WEEKS TESTS ########## \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(61); CREATE SCHEMA partman_test; CREATE TABLE partman_test.time_taptest_table (col1 int, col2 text default 'stuff', col3 timestamptz NOT NULL DEFAULT now()) 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', '7 hours', p_date_trunc_interval := 'hour'); SELECT is_partitioned('partman_test', 'time_taptest_table', 'Check that time_taptest_table is natively partitioned'); SELECT has_table('partman', 'template_partman_test_time_taptest_table', 'Check that default template table was created'); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MISS')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' exists (+7 hours)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'14 hours'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'14 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' exists (+14 hours)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'21 hours'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'21 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' exists (+21 hours)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'28 hours'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'28 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' exists (+28 hours)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'35 hours'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'35 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist (+35 hours)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)-'7 hours'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)-'7 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' exists (-7 hours)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)-'14 hours'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)-'14 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' exists (-14 hours)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)-'21 hours'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)-'21 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' exists (-21 hours)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)-'28 hours'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)-'28 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' exists (-28 hours)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)-'35 hours'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)-'35 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist (-35 hours)'); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table is empty. Should be impossible for native, but leaving test here just cause.'); 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(date_trunc('hour',CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MISS'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MISS')); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), date_trunc('hour', CURRENT_TIMESTAMP) + '7 hours'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), date_trunc('hour', CURRENT_TIMESTAMP) + '14 hours'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(26,30), date_trunc('hour', CURRENT_TIMESTAMP) + '21 hours'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(31,37), date_trunc('hour', CURRENT_TIMESTAMP) + '28 hours'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(40,49), date_trunc('hour', CURRENT_TIMESTAMP) - '7 hours'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(50,70), date_trunc('hour', CURRENT_TIMESTAMP) - '14 hours'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(71,85), date_trunc('hour', CURRENT_TIMESTAMP) - '21 hours'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(86,100), date_trunc('hour', CURRENT_TIMESTAMP) - '28 hours'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'14 hours'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'14 hours'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'21 hours'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'21 hours'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'28 hours'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[7], 'Check count from time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'28 hours'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)-'7 hours'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)-'7 hours'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)-'14 hours'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[21], 'Check count from time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)-'14 hours'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)-'21 hours'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)-'21 hours'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)-'28 hours'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)-'28 hours'::interval, 'YYYY_MM_DD_HH24MISS')); UPDATE part_config SET premake = 5 WHERE parent_table = 'partman_test.time_taptest_table'; -- Run to create proper future partitions SELECT run_maintenance(); -- Insert after maintenance since native would put it into default INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(101,122), CURRENT_TIMESTAMP + '35 hours'::interval); -- Run again to create +5 partition now that data exists SELECT run_maintenance(); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'35 hours'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[22], 'Check count from time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'35 hours'::interval, 'YYYY_MM_DD_HH24MISS')); -- Data exists for +35 weeks, with 5 premake so +70 week table should exist SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'35 hours'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'35 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' exists (+35 hours)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'42 hours'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'42 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' exists (+42 hours)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'49 hours'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'49 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' exists (+49 hours)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'56 hours'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'56 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' exists (+56 hours)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'63 hours'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'63 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' exists (+63 hours)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'70 hours'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'70 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' exists (+70 hours)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'77 hours'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'77 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist (+77 hours)'); UPDATE part_config SET premake = 6, optimize_trigger = 6 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(123,150), CURRENT_TIMESTAMP + '42 hours'::interval); -- Run again now that +6 data exists SELECT run_maintenance(); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[148], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'42 hours'::interval, 'YYYY_MM_DD_HH24MISS'), ARRAY[28], 'Check count from time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'42 hours'::interval, 'YYYY_MM_DD_HH24MISS')); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'77 hours'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'77 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' exists (+63 hours)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'84 hours'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'84 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' exists (+84 hours)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'91 hours'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'91 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist (+91 hours)'); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '200 hours'::interval); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[159], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table_default', ARRAY[11], 'Check that data outside existing child tables goes to default'); SELECT drop_partition_time('partman_test.time_taptest_table', '21 hours', p_keep_table := false); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)-'28 hours'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)-'28 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist (-28 hours)'); UPDATE part_config SET retention = '14 hours' WHERE parent_table = 'partman_test.time_taptest_table'; SELECT drop_partition_time('partman_test.time_taptest_table', p_keep_table := false); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)-'21 hours'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)-'21 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist (-21 hours)'); SELECT undo_partition('partman_test.time_taptest_table', 20, p_target_table := 'partman_test.undo_taptest', p_keep_table := false); SELECT results_eq('SELECT count(*)::int FROM partman_test.undo_taptest', ARRAY[129], 'Check count from target table after undo'); SELECT hasnt_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')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MISS')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist (+7 hours)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'14 hours'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist (+14 hours)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'21 hours'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist (+21 hours)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'28 hours'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist (+28 hours)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'35 hours'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist (+35 hours)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'42 hours'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist (+42 hours)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'49 hours'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist (+49 hours)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'56 hours'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist (+56 hours)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'63 hours'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist (+63 hours)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'70 hours'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist (+70 hours)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'77 hours'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist (+77 hours)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'84 hours'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist (+84 hours)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'91 hours'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist (+91 hours)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)-'14 hours'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)-'14 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist (-14 hours)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)-'7 hours'::interval, 'YYYY_MM_DD_HH24MISS'), 'Check time_taptest_table_p'||to_char(date_trunc('hour',CURRENT_TIMESTAMP)-'7 hours'::interval, 'YYYY_MM_DD_HH24MISS')||' does not exist (-7 hours)'); SELECT hasnt_table('partman', 'template_partman_test_time_taptest_table', 'Check that template table was dropped'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test_native/test_default_child/test-time-default-child-premade.sql000066400000000000000000000430531412761706100315670ustar00rootroot00000000000000-- ########## TIME DAILY TESTS ########## -- Other tests: check that maintenance catches up if tables are missing -- Test using default template table. Initial child tables will have no indexes. New tables after template has indexes added should. \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(65); CREATE SCHEMA partman_test; CREATE TABLE partman_test.fk_test_reference (col2 text unique not null); INSERT INTO partman_test.fk_test_reference VALUES ('stuff'); CREATE TABLE partman_test.time_taptest_table (col1 int, col2 text default 'stuff', col3 timestamptz NOT NULL DEFAULT now()) PARTITION BY RANGE (col3); CREATE TABLE partman_test.undo_taptest (LIKE partman_test.time_taptest_table INCLUDING ALL); CREATE TABLE partman_test.template_id_taptest_table (LIKE partman_test.time_taptest_table); ALTER TABLE partman_test.template_id_taptest_table ADD PRIMARY KEY (col1); SELECT create_parent('partman_test.time_taptest_table', 'col3', 'native', 'daily', p_template_table := 'partman_test.template_id_taptest_table'); SELECT is_partitioned('partman_test', 'time_taptest_table', 'Check that time_taptest_table is natively partitioned'); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); SELECT has_table('partman_test', 'time_taptest_table_default', 'Check that default child table was created'); 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'); SELECT col_is_pk('partman_test', 'time_taptest_table_default', ARRAY['col1'], 'Check for primary key in time_taptest_table_default'); 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_default', 'Check that default table is empty.'); 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')); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '1 day'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '2 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(26,30), CURRENT_TIMESTAMP + '3 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(31,37), CURRENT_TIMESTAMP + '4 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(40,49), CURRENT_TIMESTAMP - '1 day'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(50,70), CURRENT_TIMESTAMP - '2 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(71,85), CURRENT_TIMESTAMP - '3 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(86,100), CURRENT_TIMESTAMP - '4 days'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY[10], '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[5], '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[5], '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[7], '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[10], '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[21], '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[15], '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[15], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); UPDATE part_config SET premake = 5 WHERE parent_table = 'partman_test.time_taptest_table'; -- Run to create proper future partitions SELECT run_maintenance(); -- Insert after maintenance since native maintenance runs when it tries to create new child table based on future data in the parent INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(101,122), CURRENT_TIMESTAMP + '5 days'::interval); -- Check that default is empty. SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_default', 'Check that default table is empty'); -- Run maintenance again to create +5 partition now that data is in proper child SELECT run_maintenance(); -- Data exists for +5 days, with 5 premake so +10 day table should exist SELECT has_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')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_default', 'Check that default table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY[22], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); UPDATE part_config SET premake = 6, optimize_trigger = 6 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(123,150), CURRENT_TIMESTAMP + '6 days'::interval); -- Run again now that +6 data exists SELECT run_maintenance(); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[148], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), ARRAY[28], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'13 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'13 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '20 days'::interval); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table_default', ARRAY[11], 'Check that data with no specific child goes to default'); SELECT throws_ok('SELECT partman.partition_data_time(''partman_test.time_taptest_table'', 10, p_batch_interval := ''1 hour'')', 'Custom intervals are not allowed when moving data out of the DEFAULT partition in a native set. Please leave p_interval/p_batch_interval parameters unset or NULL to allow use of partition set''s default partitioning interval.', 'Check that interval cannot be set when partitioning off the default child'); -- Move data to proper child SELECT partition_data_time('partman_test.time_taptest_table', 10); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_default', 'Check that data was removed from default.'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'20 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'20 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'20 days'::interval, 'YYYY_MM_DD'), ARRAY[11], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'20 days'::interval, 'YYYY_MM_DD')||' after moving from default'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test_native/test_default_child/test-time-default-child.sql000066400000000000000000000427551412761706100301640ustar00rootroot00000000000000-- ########## TIME DAILY TESTS ########## -- Other tests: check that maintenance catches up if tables are missing -- Test using default template table. Initial child tables will have no indexes. New tables after template has indexes added should. \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(65); CREATE SCHEMA partman_test; CREATE TABLE partman_test.fk_test_reference (col2 text unique not null); INSERT INTO partman_test.fk_test_reference VALUES ('stuff'); CREATE TABLE partman_test.time_taptest_table (col1 int, col2 text default 'stuff', col3 timestamptz NOT NULL DEFAULT now()) 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'); SELECT is_partitioned('partman_test', 'time_taptest_table', 'Check that time_taptest_table is natively partitioned'); SELECT has_table('partman', 'template_partman_test_time_taptest_table', 'Check that default template table was created'); -- Add inheritable stuff to template table ALTER TABLE partman.template_partman_test_time_taptest_table ADD PRIMARY KEY (col1); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); SELECT has_table('partman_test', 'time_taptest_table_default', 'Check that default child table was created'); 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'); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO 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_default', 'Check that default table is empty.'); 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')); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '1 day'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '2 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(26,30), CURRENT_TIMESTAMP + '3 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(31,37), CURRENT_TIMESTAMP + '4 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(40,49), CURRENT_TIMESTAMP - '1 day'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(50,70), CURRENT_TIMESTAMP - '2 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(71,85), CURRENT_TIMESTAMP - '3 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(86,100), CURRENT_TIMESTAMP - '4 days'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY[10], '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[5], '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[5], '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[7], '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[10], '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[21], '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[15], '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[15], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); UPDATE part_config SET premake = 5 WHERE parent_table = 'partman_test.time_taptest_table'; -- Run to create proper future partitions SELECT run_maintenance(); -- Insert after maintenance since native maintenance runs when it tries to create new child table based on future data in the parent INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(101,122), CURRENT_TIMESTAMP + '5 days'::interval); -- Check that default is empty. SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_default', 'Check that default table is empty'); -- Run maintenance again to create +5 partition now that data is in proper child SELECT run_maintenance(); -- Data exists for +5 days, with 5 premake so +10 day table should exist SELECT has_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')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_default', 'Check that default table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY[22], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); UPDATE part_config SET premake = 6, optimize_trigger = 6 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(123,150), CURRENT_TIMESTAMP + '6 days'::interval); -- Run again now that +6 data exists SELECT run_maintenance(); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[148], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), ARRAY[28], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'13 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'13 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '20 days'::interval); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table_default', ARRAY[11], 'Check that data with no specific child goes to default'); SELECT throws_ok('SELECT partman.partition_data_time(''partman_test.time_taptest_table'', 10, p_batch_interval := ''1 hour'')', 'Custom intervals are not allowed when moving data out of the DEFAULT partition in a native set. Please leave p_interval/p_batch_interval parameters unset or NULL to allow use of partition set''s default partitioning interval.', 'Check that interval cannot be set when partitioning off the default child'); -- Move data to proper child SELECT partition_data_time('partman_test.time_taptest_table', 10); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_default', 'Check that data was removed from default.'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'20 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'20 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'20 days'::interval, 'YYYY_MM_DD'), ARRAY[11], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'20 days'::interval, 'YYYY_MM_DD')||' after moving from default'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test_native/test_default_child/test-time-eleven-weeks-native.sql000066400000000000000000000573631412761706100313360ustar00rootroot00000000000000-- ########## TIME 9 WEEKS TESTS ########## \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(79); CREATE SCHEMA partman_test; CREATE TABLE partman_test.time_taptest_table (col1 int, col2 text default 'stuff', col3 timestamptz NOT NULL DEFAULT now()) 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', '11 weeks', p_date_trunc_interval := 'week'); SELECT is_partitioned('partman_test', 'time_taptest_table', 'Check that time_taptest_table is natively partitioned'); SELECT has_table('partman', 'template_partman_test_time_taptest_table', 'Check that default template table was created'); -- Add inheritable stuff to template table ALTER TABLE partman.template_partman_test_time_taptest_table ADD PRIMARY KEY (col1); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP), 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP), 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'11 weeks'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'11 weeks'::interval, 'YYYY_MM_DD')||' exists (+11 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'22 weeks'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'22 weeks'::interval, 'YYYY_MM_DD')||' exists (+22 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'33 weeks'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'33 weeks'::interval, 'YYYY_MM_DD')||' exists (+33 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'44 weeks'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'44 weeks'::interval, 'YYYY_MM_DD')||' exists (+44 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'55 weeks'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'55 weeks'::interval, 'YYYY_MM_DD')||' does not exist (+55 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)-'11 weeks'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)-'11 weeks'::interval, 'YYYY_MM_DD')||' exists (-11 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)-'22 weeks'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)-'22 weeks'::interval, 'YYYY_MM_DD')||' exists (-22 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)-'33 weeks'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)-'33 weeks'::interval, 'YYYY_MM_DD')||' exists (-33 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)-'44 weeks'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)-'44 weeks'::interval, 'YYYY_MM_DD')||' exists (-44 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)-'55 weeks'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)-'55 weeks'::interval, 'YYYY_MM_DD')||' does not exist (-55 weeks)'); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP), 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP), 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'1 day'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'2 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'2 days'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'3 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'3 days'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'4 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'4 days'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP), 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP), 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)-'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)-'1 day'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)-'2 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)-'2 days'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)-'3 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)-'3 days'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)-'4 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)-'4 days'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table is empty. Should be impossible for native, but leaving test here just cause.'); 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(date_trunc('week',CURRENT_TIMESTAMP), 'YYYY_MM_DD'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP), 'YYYY_MM_DD')); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '11 weeks'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '22 weeks'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(26,30), CURRENT_TIMESTAMP + '33 weeks'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(31,37), CURRENT_TIMESTAMP + '44 weeks'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(40,49), CURRENT_TIMESTAMP - '11 weeks'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(50,70), CURRENT_TIMESTAMP - '22 weeks'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(71,85), CURRENT_TIMESTAMP - '33 weeks'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(86,100), CURRENT_TIMESTAMP - '44 weeks'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'11 weeks'::interval, 'YYYY_MM_DD'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'11 weeks'::interval, 'YYYY_MM_DD')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'22 weeks'::interval, 'YYYY_MM_DD'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'22 weeks'::interval, 'YYYY_MM_DD')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'33 weeks'::interval, 'YYYY_MM_DD'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'33 weeks'::interval, 'YYYY_MM_DD')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'44 weeks'::interval, 'YYYY_MM_DD'), ARRAY[7], 'Check count from time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'44 weeks'::interval, 'YYYY_MM_DD')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)-'11 weeks'::interval, 'YYYY_MM_DD'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)-'11 weeks'::interval, 'YYYY_MM_DD')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)-'22 weeks'::interval, 'YYYY_MM_DD'), ARRAY[21], 'Check count from time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)-'22 weeks'::interval, 'YYYY_MM_DD')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)-'33 weeks'::interval, 'YYYY_MM_DD'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)-'33 weeks'::interval, 'YYYY_MM_DD')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)-'44 weeks'::interval, 'YYYY_MM_DD'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)-'44 weeks'::interval, 'YYYY_MM_DD')); UPDATE part_config SET premake = 5 WHERE parent_table = 'partman_test.time_taptest_table'; -- Run to create proper future partitions SELECT run_maintenance(); -- Insert after maintenance since native fails with no child INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(101,122), CURRENT_TIMESTAMP + '55 weeks'::interval); -- Run again to create +5 partition now that data exists SELECT run_maintenance(); -- Data exists for +55 weeks, with 5 premake so +110 week table should exist SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'55 weeks'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'55 weeks'::interval, 'YYYY_MM_DD')||' exists (+55 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'66 weeks'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'66 weeks'::interval, 'YYYY_MM_DD')||' exists (+66 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'77 weeks'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'77 weeks'::interval, 'YYYY_MM_DD')||' exists (+77 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'88 weeks'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'88 weeks'::interval, 'YYYY_MM_DD')||' exists (+88 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'99 weeks'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'99 weeks'::interval, 'YYYY_MM_DD')||' exists (+99 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'110 weeks'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'110 weeks'::interval, 'YYYY_MM_DD')||' exists (+110 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'121 weeks'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'121 weeks'::interval, 'YYYY_MM_DD')||' does not exist (+121 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'55 weeks'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'55 weeks'::interval, 'YYYY_MM_DD')||' (+55 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'66 weeks'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'66 weeks'::interval, 'YYYY_MM_DD')||' (+66 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'77 weeks'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'77 weeks'::interval, 'YYYY_MM_DD')||' (+77 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'88 weeks'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'88 weeks'::interval, 'YYYY_MM_DD')||' (+88 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'99 weeks'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'99 weeks'::interval, 'YYYY_MM_DD')||' (+99 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'110 weeks'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'110 weeks'::interval, 'YYYY_MM_DD')||' (+110 weeks)'); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'55 weeks'::interval, 'YYYY_MM_DD'), ARRAY[22], 'Check count from time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'55 weeks'::interval, 'YYYY_MM_DD')||' (+55 weeks)'); UPDATE part_config SET premake = 6, optimize_trigger = 6 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(123,150), CURRENT_TIMESTAMP + '66 weeks'::interval); -- Run again now that +6 data exists SELECT run_maintenance(); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[148], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'66 weeks'::interval, 'YYYY_MM_DD'), ARRAY[28], 'Check count from time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'66 weeks'::interval, 'YYYY_MM_DD')||' (+66 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'121 weeks'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'121 weeks'::interval, 'YYYY_MM_DD')||' exists (+121 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'132 weeks'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'132 weeks'::interval, 'YYYY_MM_DD')||' exists (+132 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'143 weeks'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'143 weeks'::interval, 'YYYY_MM_DD')||' does not exist (+143 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'121 weeks'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'121 weeks'::interval, 'YYYY_MM_DD')||' (+121 weeks)'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'132 weeks'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'132 weeks'::interval, 'YYYY_MM_DD')||' (+132 weeks)'); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '200 weeks'::interval); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[159], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table_default', ARRAY[11], 'Check that data outside existing child tables goes to default'); SELECT drop_partition_time('partman_test.time_taptest_table', '33 weeks', p_keep_table := false); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)-'44 weeks'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)-'44 weeks'::interval, 'YYYY_MM_DD')||' does not exist (-44 weeks)'); UPDATE part_config SET retention = '22 weeks'::interval WHERE parent_table = 'partman_test.time_taptest_table'; SELECT drop_partition_time('partman_test.time_taptest_table', p_keep_table := false); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)-'33 weeks'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)-'33 weeks'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT undo_partition('partman_test.time_taptest_table', 20, p_target_table := 'partman_test.undo_taptest', p_keep_table := false); SELECT results_eq('SELECT count(*)::int FROM partman_test.undo_taptest', ARRAY[129], 'Check count from target table after undo'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP), 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP), 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'11 weeks'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'11 weeks'::interval, 'YYYY_MM_DD')||' does not exist (+11 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'22 weeks'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'22 weeks'::interval, 'YYYY_MM_DD')||' does not exist (+22 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'33 weeks'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'33 weeks'::interval, 'YYYY_MM_DD')||' does not exist (+33 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'44 weeks'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'44 weeks'::interval, 'YYYY_MM_DD')||' does not exist (+44 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'55 weeks'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'55 weeks'::interval, 'YYYY_MM_DD')||' does not exist (+55 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'66 weeks'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'66 weeks'::interval, 'YYYY_MM_DD')||' does not exist (+66 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'77 weeks'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'77 weeks'::interval, 'YYYY_MM_DD')||' does not exist (+77 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'88 weeks'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'88 weeks'::interval, 'YYYY_MM_DD')||' does not exist (+88 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'99 weeks'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'99 weeks'::interval, 'YYYY_MM_DD')||' does not exist (+99 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'110 weeks'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'110 weeks'::interval, 'YYYY_MM_DD')||' does not exist (+110 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'121 weeks'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'121 weeks'::interval, 'YYYY_MM_DD')||' does not exist (+121 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'132 weeks'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)+'132 weeks'::interval, 'YYYY_MM_DD')||' does not exist (+132 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)-'11 weeks'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)-'11 weeks'::interval, 'YYYY_MM_DD')||' does not exist (-11 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)-'22 weeks'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(date_trunc('week',CURRENT_TIMESTAMP)-'22 weeks'::interval, 'YYYY_MM_DD')||' does not exist (-22 weeks)'); SELECT hasnt_table('partman', 'template_partman_test_time_taptest_table', 'Check that template table was dropped'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test_native/test_default_child/test-time-epoch-milliseconds-weekly.sql000066400000000000000000000425741412761706100325350ustar00rootroot00000000000000-- ########## TIME WEEKLY EPOCH TESTS ########## -- Other tests: combination of start_partition & constraint_cols/optimize_constraint. Requires manually running apply_constraint to set other old partitions -- Test millisecond epoch time partitioning \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(53); CREATE SCHEMA partman_test; CREATE SCHEMA partman_retention_test; CREATE TABLE partman_test.time_taptest_table (col1 bigint , col2 text , col3 bigint NOT NULL DEFAULT (extract('epoch' from CURRENT_TIMESTAMP)::bigint)*1000) 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', 'weekly', '{"col1"}', p_epoch := 'milliseconds' , p_premake := 2, p_start_partition := to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'YYYY-MM-DD HH24:MI:SS')); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), (extract('epoch' from CURRENT_TIMESTAMP - '8 weeks'::interval)::bigint)*1000); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' exists (+1 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||' exists (+2 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW')||' does not exist (+3 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' exists (-1 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' exists (-2 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' exists (-3 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' exists (-4 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||' exists (-5 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||' exists (-6 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')||' exists (-7 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')||' exists (-8 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'9 weeks'::interval, 'IYYY"w"IW')||' does not exist (-9 weeks)'); 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 - '8 weeks'::interval, 'IYYY"w"IW'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')||' (-8 weeks)'); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), (extract('epoch' from CURRENT_TIMESTAMP - '7 weeks'::interval)::bigint)*1000); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), (extract('epoch' from CURRENT_TIMESTAMP - '6 weeks'::interval)::bigint)*1000); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(26,30), (extract('epoch' from CURRENT_TIMESTAMP - '5 weeks'::interval)::bigint)*1000); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(31,37), (extract('epoch' from CURRENT_TIMESTAMP - '4 week'::interval)::bigint)*1000); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(38,49), (extract('epoch' from CURRENT_TIMESTAMP - '3 week'::interval)::bigint)*1000); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(50,70), (extract('epoch' from CURRENT_TIMESTAMP - '2 weeks'::interval)::bigint)*1000); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(71,85), (extract('epoch' from CURRENT_TIMESTAMP - '1 week'::interval)::bigint)*1000); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(86,100), (extract('epoch' from CURRENT_TIMESTAMP + '1 week'::interval)::bigint)*1000); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(101,110), (extract('epoch' from CURRENT_TIMESTAMP + '2 weeks'::interval)::bigint)*1000); SELECT partition_data_time('partman_test.time_taptest_table', 20); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')||' (-7 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||' (-6 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||' (-5 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), ARRAY[7], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' (-4 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), ARRAY[12], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' (-3 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), ARRAY[21], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' (-2 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' (-1 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' (+1 weeks)'); UPDATE part_config SET premake = 3, optimize_constraint = 5 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); -- Automatic run of apply_constraints due to run_maintenance will put constraint on "now() - 4 weeks" partition with an optimize_constraint value of 5 -- This is due to values "now() + 2 weeks" being inserted above. -- Ex: Current week is 39. +2 weeks is 41. Going back 5 weeks is 36, but constraint is applied to the table OLDER than that value. Hence constraint will be on week 35 SELECT col_has_check('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'col1' , 'Check for additional constraint on col1 on time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' (-4 weeks)'); -- Must run apply_constraints() to manually set the other older constraints SELECT apply_constraints('partman_test.time_taptest_table', 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')); SELECT apply_constraints('partman_test.time_taptest_table', 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')); SELECT apply_constraints('partman_test.time_taptest_table', 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')); SELECT apply_constraints('partman_test.time_taptest_table', 'partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')); SELECT col_has_check('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW'), 'col1' , 'Check for additional constraint on col1 on time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')); SELECT col_has_check('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW'), 'col1' , 'Check for additional constraint on col1 on time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')); SELECT col_has_check('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), 'col1' , 'Check for additional constraint on col1 on time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')); SELECT col_has_check('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), 'col1' , 'Check for additional constraint on col1 on time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(111,120), (extract('epoch' from CURRENT_TIMESTAMP + '3 weeks'::interval)::bigint)*1000); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' exists (+3 weeks'); -- Cannot test for next week not existing. Different lengths of months will sometimes cause an extra partition. SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' (+3 weeks)'); UPDATE part_config SET premake = 4 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(121,130), (extract('epoch' from CURRENT_TIMESTAMP + '4 weeks'::interval)::bigint)*1000); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[130], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' (+4 weeks)'); SELECT col_has_check('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'col1' , 'Check for additional constraint on col1 on time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' exists (+4 weeks)'); -- Cannot test for next week not existing. Different lengths of months will sometimes cause an extra partition. INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), (extract('epoch' from CURRENT_TIMESTAMP + '20 weeks'::interval)::bigint)*1000); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_default', ARRAY[11], 'Check that data outside trigger scope goes to default'); SELECT drop_partition_time('partman_test.time_taptest_table', '3 weeks', p_keep_table := false); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' does not exist (-4 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||' does not exist (-5 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||' does not exist (-6 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')||' does not exist (-7 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')||' does not exist (-8 weeks)'); UPDATE part_config SET retention = '2 weeks'::interval WHERE parent_table = 'partman_test.time_taptest_table'; SELECT drop_partition_time('partman_test.time_taptest_table', p_retention_schema := 'partman_retention_test'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' does not exist (-3 weeks)'); SELECT has_table('partman_retention_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' got moved to new schema (-3 weeks)'); SELECT undo_partition('partman_test.time_taptest_table', 20, p_keep_table := false, p_target_table := 'partman_test.undo_taptest'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.undo_taptest', ARRAY[92], 'Check count from parent table after undo'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||' does not exist (now)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' does not exist (+1 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||' does not exist (+2 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' does not exist (+3 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' does not exist (+4 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' does not exist (-1 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' does not exist (-2 weeks)'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test_native/test_generated_col/000077500000000000000000000000001412761706100230145ustar00rootroot00000000000000pg_partman-4.6.0/test/test_native/test_generated_col/test-time-daily-generated.sql000066400000000000000000000545211412761706100305130ustar00rootroot00000000000000-- ########## TIME DAILY TESTS ########## -- Other tests: -- Test generated always columns \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(84); CREATE SCHEMA partman_test; CREATE TABLE partman_test.time_taptest_table ( col1 int , col2 text default 'stuff' , col3 timestamptz NOT NULL DEFAULT now() , col4 bigint GENERATED ALWAYS AS (col1 * 100) STORED ) 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'); UPDATE part_config SET inherit_privileges = TRUE; SELECT reapply_privileges('partman_test.time_taptest_table'); SELECT is_partitioned('partman_test', 'time_taptest_table', 'Check that time_taptest_table is natively partitioned'); SELECT has_table('partman', 'template_partman_test_time_taptest_table', 'Check that default template table was created'); -- Add inheritable stuff to template table ALTER TABLE partman.template_partman_test_time_taptest_table ADD PRIMARY KEY (col1); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); 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'); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')); SELECT col_isnt_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for NO 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 is empty. Should be impossible for native, but leaving test here just cause.'); 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')); SELECT results_eq('SELECT col4::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY[100,200,300,400,500,600,700,800,900,1000], 'Check generated values from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '1 day'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '2 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(26,30), CURRENT_TIMESTAMP + '3 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(31,37), CURRENT_TIMESTAMP + '4 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(40,49), CURRENT_TIMESTAMP - '1 day'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(50,70), CURRENT_TIMESTAMP - '2 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(71,85), CURRENT_TIMESTAMP - '3 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(86,100), CURRENT_TIMESTAMP - '4 days'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY[10], '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[5], '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[5], '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[7], '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[10], '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[21], '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[15], '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[15], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); SELECT results_eq('SELECT col4::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY[1100,1200,1300,1400,1500,1600,1700,1800,1900,2000], 'Check generated values from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT results_eq('SELECT col4::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 day'::interval, 'YYYY_MM_DD'), ARRAY[2100,2200,2300,2400,2500], 'Check generated values from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 day'::interval, 'YYYY_MM_DD')); SELECT results_eq('SELECT col4::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 day'::interval, 'YYYY_MM_DD'), ARRAY[2600,2700,2800,2900,3000], 'Check generated values from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 day'::interval, 'YYYY_MM_DD')); SELECT results_eq('SELECT col4::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 day'::interval, 'YYYY_MM_DD'), ARRAY[3100,3200,3300,3400,3500,3600,3700], 'Check generated values from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 day'::interval, 'YYYY_MM_DD')); SELECT results_eq('SELECT col4::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), ARRAY[4000,4100,4200,4300,4400,4500,4600,4700,4800,4900], 'Check generated values from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); UPDATE part_config SET premake = 5 WHERE parent_table = 'partman_test.time_taptest_table'; -- Run to create proper future partitions SELECT run_maintenance(); -- Insert after maintenance since native fails with no child INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(101,112), CURRENT_TIMESTAMP + '5 days'::interval); -- Run again to create +5 partition now that data exists SELECT run_maintenance(); -- Data exists for +5 days, with 5 premake so +10 day table should exist SELECT has_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')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY[12], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT results_eq('SELECT col4::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY[10100,10200,10300,10400,10500,10600,10700,10800,10900,11000,11100,11200], 'Check generated values from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); -- Check default and migration of data from default when using generated columns INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(120,125), CURRENT_TIMESTAMP + '11 days'::interval); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_default', ARRAY[6], 'Check count from time_taptest_table_default'); SELECT results_eq('SELECT col4::int FROM partman_test.time_taptest_table_default', ARRAY[12000,12100,12200,12300,12400,12500], 'Check generated values from time_taptest_table_default'); SELECT partman.partition_data_time('partman_test.time_taptest_table', 10, p_ignored_columns := ARRAY['col4']); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' was created by partition_data_time'); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table_default', 'Check that default had data removed'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), ARRAY[6], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')); SELECT results_eq('SELECT col4::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), ARRAY[12000,12100,12200,12300,12400,12500], 'Check generated values were properly moved to time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')); -- Begin undo section SELECT drop_partition_time('partman_test.time_taptest_table', '3 days', p_keep_table := false); 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')||' does not exist'); UPDATE part_config SET retention = '2 days'::interval WHERE parent_table = 'partman_test.time_taptest_table'; SELECT drop_partition_time('partman_test.time_taptest_table', p_keep_table := false); 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')||' does not exist'); SELECT undo_partition('partman_test.time_taptest_table', 20, p_target_table := 'partman_test.undo_taptest', p_keep_table := false, p_ignored_columns := ARRAY['col4']); SELECT results_eq('SELECT count(*)::int FROM partman_test.undo_taptest', ARRAY[86], 'Check count from target table after undo'); SELECT results_eq('SELECT col4::int FROM partman_test.undo_taptest ORDER BY col4 ASC LIMIT 10', ARRAY[100,200,300,400,500,600,700,800,900,1000], 'Check generated values in target table after undo (asc)'); SELECT results_eq('SELECT col4::int FROM partman_test.undo_taptest ORDER BY col4 DESC LIMIT 10', ARRAY[12500,12400,12300,12200,12100,12000,11200,11100,11000,10900], 'Check generated values in target table after undo (desc)'); SELECT hasnt_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')||' does not exist'); SELECT hasnt_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')||' does not exist'); SELECT hasnt_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')||' does not exist'); 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')||' does not exist'); 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')||' does not exist'); 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 hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_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')||' does not exist'); SELECT hasnt_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')||' does not exist'); SELECT hasnt_table('partman', 'template_partman_test_time_taptest_table', 'Check that template table was dropped'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test_native/test_nonsuperuser/000077500000000000000000000000001412761706100227715ustar00rootroot00000000000000pg_partman-4.6.0/test/test_native/test_nonsuperuser/test-id-nonsuperuser-part2.sql000066400000000000000000000245721412761706100306710ustar00rootroot00000000000000-- ########## ID DYNAMIC TESTS ########## -- Additional tests: inherit FK, nonsupuseruser -- Test using a pre-created template table and passing to create_parent. Should allow indexes to be made for initial children. -- Tests that foreign keys and normal indexes for PG10 use the template and for PG11 they use the parent. Also since this is id partitioning, we can use the partition key for primary key, so that should work from parent on PG11 as well. -- NOTE: THIS TEST ONLY WORKS FOR PG11+ due to requiring tests for default partition. -- NOTE: THIS FILE MUST BE RUN AS partman_owner AND CONNECT TO THE DATABASE THAT RAN PART 1 TO EFFECTIVLELY TEST AS NONSUPERUSER -- Ex pg_prove -ovf -U partman_owner -d mydb test/test_native/test_nonsuperuser/test-id-nonsuperuser-part2.sql \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); DO $pg11_check$ BEGIN IF current_setting('server_version_num')::int < 110000 THEN RAISE EXCEPTION 'This test can only be run on PG11+'; END IF; END $pg11_check$; SELECT plan(58); CREATE TABLE partman_test.fk_test_reference (col2 text unique not null); INSERT INTO partman_test.fk_test_reference VALUES ('stuff'); CREATE TABLE partman_test.id_taptest_table ( col1 bigint , col2 text not null default 'stuff' , col3 timestamptz DEFAULT now() , col4 text) PARTITION BY RANGE (col1); CREATE TABLE partman_test.undo_taptest (LIKE partman_test.id_taptest_table INCLUDING ALL); -- Template table CREATE TABLE partman_test.template_id_taptest_table (LIKE partman_test.id_taptest_table); -- Regular unique indexes do not work on native in PG11 if the partition key isn't included CREATE UNIQUE INDEX ON partman_test.template_id_taptest_table (col4); GRANT SELECT,INSERT,UPDATE ON partman_test.id_taptest_table TO partman_basic, PUBLIC; GRANT ALL ON partman_test.id_taptest_table TO partman_revoke; -- Create on parent table since partition column is pk ALTER TABLE partman_test.id_taptest_table ADD PRIMARY KEY (col1); ALTER TABLE partman_test.id_taptest_table ADD FOREIGN KEY (col2) REFERENCES partman_test.fk_test_reference(col2); CREATE INDEX ON partman_test.id_taptest_table (col3); SELECT create_parent('partman_test.id_taptest_table', 'col1', 'native', '10', p_jobmon := false, p_template_table := 'partman_test.template_id_taptest_table'); UPDATE part_config SET inherit_privileges = TRUE; SELECT reapply_privileges('partman_test.id_taptest_table'); INSERT INTO partman_test.id_taptest_table (col1, col4) VALUES (generate_series(1,9), 'stuff'||generate_series(1,9)); SELECT has_table('partman_test', 'id_taptest_table_p0', 'Check id_taptest_table_p0 exists'); SELECT has_table('partman_test', 'id_taptest_table_p10', 'Check id_taptest_table_p10 exists'); SELECT has_table('partman_test', 'id_taptest_table_p20', 'Check id_taptest_table_p20 exists'); SELECT has_table('partman_test', 'id_taptest_table_p30', 'Check id_taptest_table_p30 exists'); SELECT has_table('partman_test', 'id_taptest_table_p40', 'Check id_taptest_table_p40 exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50', 'Check id_taptest_table_p50 doesn''t exists yet'); SELECT col_is_pk('partman_test', 'id_taptest_table_p0', ARRAY['col1'], 'Check for primary key in id_taptest_table_p0'); SELECT col_is_pk('partman_test', 'id_taptest_table_p10', ARRAY['col1'], 'Check for primary key in id_taptest_table_p10'); SELECT col_is_pk('partman_test', 'id_taptest_table_p20', ARRAY['col1'], 'Check for primary key in id_taptest_table_p20'); SELECT col_is_pk('partman_test', 'id_taptest_table_p30', ARRAY['col1'], 'Check for primary key in id_taptest_table_p30'); SELECT col_is_pk('partman_test', 'id_taptest_table_p40', ARRAY['col1'], 'Check for primary key in id_taptest_table_p40'); SELECT col_is_fk('partman_test', 'id_taptest_table_p0', 'col2', 'Check that foreign key was inherited to id_taptest_table_p0'); SELECT col_is_fk('partman_test', 'id_taptest_table_p10', 'col2', 'Check that foreign key was inherited to id_taptest_table_p10'); SELECT col_is_fk('partman_test', 'id_taptest_table_p20', 'col2', 'Check that foreign key was inherited to id_taptest_table_p20'); SELECT col_is_fk('partman_test', 'id_taptest_table_p30', 'col2', 'Check that foreign key was inherited to id_taptest_table_p30'); SELECT col_is_fk('partman_test', 'id_taptest_table_p40', 'col2', 'Check that foreign key was inherited to id_taptest_table_p40'); SELECT table_privs_are('partman_test', 'id_taptest_table', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table'); SELECT table_privs_are('partman_test', 'id_taptest_table', 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of id_taptest_table'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[9], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0', ARRAY[9], 'Check count from id_taptest_table_p0'); REVOKE INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON partman_test.id_taptest_table FROM partman_revoke, PUBLIC; SELECT run_maintenance(); INSERT INTO partman_test.id_taptest_table (col1, col4) VALUES (generate_series(10,25), 'stuff'||generate_series(10,25)); -- Run again to make new partition based on latest data SELECT run_maintenance(); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10', ARRAY[10], 'Check count from id_taptest_table_p10'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20', ARRAY[6], 'Check count from id_taptest_table_p20'); SELECT has_table('partman_test', 'id_taptest_table_p50', 'Check id_taptest_table_p50 exists'); SELECT has_table('partman_test', 'id_taptest_table_p60', 'Check id_taptest_table_p60 exists yet'); SELECT hasnt_table('partman_test', 'id_taptest_table_p70', 'Check id_taptest_table_p70 doesn''t exists yet'); SELECT col_is_pk('partman_test', 'id_taptest_table_p50', ARRAY['col1'], 'Check for primary key in id_taptest_table_p50'); SELECT col_is_fk('partman_test', 'id_taptest_table_p50', 'col2', 'Check that foreign key was inherited to id_taptest_table_p50'); SELECT table_privs_are('partman_test', 'id_taptest_table', 'partman_revoke', ARRAY['SELECT'], 'Check new partman_revoke privileges of id_taptest_table'); INSERT INTO partman_test.id_taptest_table (col1, col4) VALUES (generate_series(26,38), 'stuff'||generate_series(26,38)); SELECT run_maintenance(); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[38], 'Check count from id_taptest_table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20', ARRAY[10], 'Check count from id_taptest_table_p20'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p30', ARRAY[9], 'Check count from id_taptest_table_p30'); SELECT has_table('partman_test', 'id_taptest_table_p70', 'Check id_taptest_table_p70 exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p80', 'Check id_taptest_table_p80 doesn''t exists yet'); SELECT col_is_pk('partman_test', 'id_taptest_table_p60', ARRAY['col1'], 'Check for primary key in id_taptest_table_p60'); SELECT col_is_pk('partman_test', 'id_taptest_table_p70', ARRAY['col1'], 'Check for primary key in id_taptest_table_p70'); SELECT col_is_fk('partman_test', 'id_taptest_table_p60', 'col2', 'Check that foreign key was inherited to id_taptest_table_p60'); SELECT col_is_fk('partman_test', 'id_taptest_table_p70', 'col2', 'Check that foreign key was inherited to id_taptest_table_p70'); -- Requires default table option INSERT INTO partman_test.id_taptest_table (col1, col4) VALUES (generate_series(200,210), 'stuff'||generate_series(200,210)); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[49], 'Check that data outside child scope is visible from parent'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table_default', ARRAY[11], 'Check that data outside child scope goes to default'); SELECT partition_data_id('partman_test.id_taptest_table', 10); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_default', 'Check data was removed from default'); SELECT has_table('partman_test', 'id_taptest_table_p200', 'Check id_taptest_table_p200 exists'); SELECT has_table('partman_test', 'id_taptest_table_p210', 'Check id_taptest_table_p210 exists'); SELECT run_maintenance(); -- Test keeping the rest of the tables SELECT undo_partition('partman_test.id_taptest_table', 20, p_target_table := 'partman_test.undo_taptest', p_keep_table := false); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.undo_taptest', ARRAY[49], 'Check count from undo target table after undo'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0', 'Check id_taptest_table_p0 doesn''t exists anymore'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10', 'Check id_taptest_table_p10 doesn''t exists anymore'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20', 'Check id_taptest_table_p20 doesn''t exists anymore'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30', 'Check id_taptest_table_p30 doesn''t exists anymore'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40', 'Check id_taptest_table_p40 doesn''t exists anymore'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50', 'Check id_taptest_table_p50 doesn''t exists anymore'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60', 'Check id_taptest_table_p60 doesn''t exists anymore'); SELECT hasnt_table('partman_test', 'id_taptest_table_p70', 'Check id_taptest_table_p70 doesn''t exists anymore'); SELECT hasnt_table('partman_test', 'id_taptest_table_p200', 'Check id_taptest_table_p200 doesn''t exists anymore'); SELECT hasnt_table('partman_test', 'id_taptest_table_p210', 'Check id_taptest_table_p210 doesn''t exists anymore'); SELECT hasnt_table('partman_test', 'template_id_taptest_table', 'Check that template table was dropped'); SELECT hasnt_table('partman_test', 'id_taptest_table_default', 'Check that default table was dropped'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test_native/test_nonsuperuser/test-nonsuperuser-part1.sql000066400000000000000000000016421412761706100302670ustar00rootroot00000000000000-- ########## TIME HOURLY TESTS ########## -- Additional tests: Testing nonsuperuser -- NOTE: THIS FILE MUST BE RUN AS A SUPERUSER \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true SELECT set_config('search_path','partman, public',false); SELECT plan(1); CREATE SCHEMA partman_test; CREATE ROLE partman_basic WITH LOGIN; CREATE ROLE partman_owner WITH LOGIN; CREATE ROLE partman_revoke; GRANT ALL ON ALL FUNCTIONS IN SCHEMA partman TO partman_basic; GRANT ALL ON ALL TABLES IN SCHEMA partman TO partman_basic; GRANT ALL ON SCHEMA partman TO partman_basic; GRANT ALL ON SCHEMA partman_test TO partman_basic; GRANT ALL ON SCHEMA partman TO partman_owner; GRANT ALL ON SCHEMA partman_test TO partman_owner; GRANT ALL ON ALL FUNCTIONS IN SCHEMA partman TO partman_owner; GRANT ALL ON ALL TABLES IN SCHEMA partman TO partman_owner; SELECT has_schema('partman_test', 'Check that test schema exits'); SELECT * FROM finish(); pg_partman-4.6.0/test/test_native/test_nonsuperuser/test-nonsuperuser-part3.sql000066400000000000000000000014741412761706100302740ustar00rootroot00000000000000-- Additional tests: Testing nonsuperuser -- NOTE: THIS FILE MUST BE RUN AS A SUPERUSER \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true SELECT set_config('search_path','partman, public',false); SELECT plan(1); DROP SCHEMA IF EXISTS partman_test CASCADE; REVOKE ALL ON SCHEMA partman FROM partman_basic; REVOKE ALL ON ALL FUNCTIONS IN SCHEMA partman FROM partman_basic; REVOKE ALL ON ALL TABLES IN SCHEMA partman FROM partman_basic; REVOKE ALL ON SCHEMA partman FROM partman_owner; REVOKE ALL ON ALL FUNCTIONS IN SCHEMA partman FROM partman_owner; REVOKE ALL ON ALL TABLES IN SCHEMA partman FROM partman_owner; DROP ROLE IF EXISTS partman_basic; DROP ROLE IF EXISTS partman_owner; DROP ROLE IF EXISTS partman_revoke; SELECT hasnt_schema('partman_test', 'Check that test schema was dropped'); SELECT * FROM finish(); pg_partman-4.6.0/test/test_native/test_nonsuperuser/test-time-hourly-nonsuperuser-part2.sql000066400000000000000000000406261412761706100325510ustar00rootroot00000000000000-- ########## TIME HOURLY TESTS ########## -- Additional tests: Testing nonsuperuser \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true -- NOTE: THIS FILE MUST BE RUN AS partman_basic AND CONNECT TO THE DATABASE THAT RAN PART 1 TO EFFECTIVLELY TEST AS NONSUPERUSER -- Ex pg_prove -ovf -U partman_basic -d mydb test/test_native/test_nonsuperuser/test-time-hourly-nonsuperuser-part2.sql BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(54); CREATE TABLE partman_test.time_taptest_table (col1 serial, col2 text, col3 timestamp NOT NULL DEFAULT now()) PARTITION BY RANGE (col3); CREATE TABLE partman_test.time_taptest_undo (LIKE partman_test.time_taptest_table); SELECT has_table('partman_test', 'time_taptest_table', 'Check that parent was created'); SELECT create_parent('partman_test.time_taptest_table', 'col3', 'native', 'hourly'); ALTER TABLE partman.template_partman_test_time_taptest_table ADD PRIMARY KEY (col1); UPDATE part_config SET inherit_privileges = TRUE; SELECT reapply_privileges('partman_test.time_taptest_table'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'5 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'5 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); --SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table'')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved'); 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(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI')); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '1 hour'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '2 hours'::interval); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_default', 'Check that default table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[25], 'Check count from time_taptest_table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI')); UPDATE part_config SET premake = 5, optimize_trigger = 5 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); -- New tables should have primary keys SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI')); UPDATE part_config SET premake = 6, optimize_trigger = 6 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'9 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'9 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '20 hours'::interval); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table_default', ARRAY[11], 'Check that data outside trigger scope goes to default'); -- Keep tables after undoing SELECT undo_partition('partman_test.time_taptest_table', 20, p_target_table := 'partman_test.time_taptest_undo'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test_native/test_tablespace/000077500000000000000000000000001412761706100223245ustar00rootroot00000000000000pg_partman-4.6.0/test/test_native/test_tablespace/test-time-daily-native-tablespace-pg12.sql000066400000000000000000000551261412761706100322250ustar00rootroot00000000000000 -- ########## TIME DAILY TESTS ########## -- Other tests: Tablespaces for table and indexes on PG12+ using the real parent -- Generated column inheritance -- Requires folder creation for tablespace and running tablespace creation before test. Cannot create tablespaces inside an explicit transaction block. -- mkdir /opt/partman_tablespace -- CREATE TABLESPACE mytablespace LOCATION '/opt/partman_tablespace'; -- Can remove tablespace and folder after test has completed -- DROP TABLESPACE mytablespace; -- rm -r /opt/partman_tablespace -- Currently doesn't test that the child tables and indexes are in the given tablespace (pgtap doesn't have that test). Just tests the code paths. \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(77); CREATE SCHEMA partman_test; CREATE TABLE partman_test.time_taptest_table ( id int GENERATED ALWAYS AS IDENTITY , peaktemp_f int , peaktemp_c int GENERATED ALWAYS AS ((peaktemp_f - 32) * 5/9) STORED , logdate timestamptz NOT NULL DEFAULT now()) PARTITION BY RANGE (logdate) TABLESPACE mytablespace; CREATE TABLE partman_test.time_taptest_table_template (LIKE partman_test.time_taptest_table) TABLESPACE mytablespace; ALTER TABLE partman_test.time_taptest_table_template ADD PRIMARY KEY (id); -- Tablespaces for unique indexes still have to be set on template table since they don't exist on real parent ALTER INDEX partman_test.time_taptest_table_template_pkey SET TABLESPACE mytablespace ; CREATE UNIQUE INDEX ON partman_test.time_taptest_table_template (peaktemp_f) TABLESPACE mytablespace; -- Yes next index is sort of duped. This is just for testing proper tablespace setting CREATE INDEX ON partman_test.time_taptest_table (id, peaktemp_f) TABLESPACE mytablespace; SELECT create_parent('partman_test.time_taptest_table', 'logdate', 'native', 'daily', p_template_table := 'partman_test.time_taptest_table_template'); SELECT is_partitioned('partman_test', 'time_taptest_table', 'Check that time_taptest_table is natively partitioned'); SELECT has_table('partman_test', 'time_taptest_table_template', 'Check template table was created'); INSERT INTO partman_test.time_taptest_table (peaktemp_f, logdate) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); 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'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['id'], '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['id'], '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 day'::interval, 'YYYY_MM_DD'), ARRAY['id'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 day'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 day'::interval, 'YYYY_MM_DD'), ARRAY['id'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 day'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 day'::interval, 'YYYY_MM_DD'), ARRAY['id'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 day'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), ARRAY['id'], '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 day'::interval, 'YYYY_MM_DD'), ARRAY['id'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 day'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 day'::interval, 'YYYY_MM_DD'), ARRAY['id'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 day'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 day'::interval, 'YYYY_MM_DD'), ARRAY['id'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 day'::interval, 'YYYY_MM_DD')); SELECT is_indexed('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'peaktemp_f', 'Check for peaktemp_f index in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT is_indexed('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'peaktemp_f', 'Check for peaktemp_f index in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT is_indexed('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 day'::interval, 'YYYY_MM_DD'), 'peaktemp_f', 'Check for peaktemp_f index in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 day'::interval, 'YYYY_MM_DD')); SELECT is_indexed('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 day'::interval, 'YYYY_MM_DD'), 'peaktemp_f', 'Check for peaktemp_f index in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 day'::interval, 'YYYY_MM_DD')); SELECT is_indexed('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 day'::interval, 'YYYY_MM_DD'), 'peaktemp_f', 'Check for peaktemp_f index in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 day'::interval, 'YYYY_MM_DD')); SELECT is_indexed('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'peaktemp_f', 'Check for peaktemp_f index in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT is_indexed('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 day'::interval, 'YYYY_MM_DD'), 'peaktemp_f', 'Check for peaktemp_f index in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 day'::interval, 'YYYY_MM_DD')); SELECT is_indexed('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 day'::interval, 'YYYY_MM_DD'), 'peaktemp_f', 'Check for peaktemp_f index in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 day'::interval, 'YYYY_MM_DD')); SELECT is_indexed('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 day'::interval, 'YYYY_MM_DD'), 'peaktemp_f', 'Check for peaktemp_f index in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 day'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table is empty. Should be impossible for native, but leaving test here just cause.'); 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')); INSERT INTO partman_test.time_taptest_table (peaktemp_f, logdate) VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '1 day'::interval); INSERT INTO partman_test.time_taptest_table (peaktemp_f, logdate) VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '2 days'::interval); INSERT INTO partman_test.time_taptest_table (peaktemp_f, logdate) VALUES (generate_series(26,30), CURRENT_TIMESTAMP + '3 days'::interval); INSERT INTO partman_test.time_taptest_table (peaktemp_f, logdate) VALUES (generate_series(31,37), CURRENT_TIMESTAMP + '4 days'::interval); INSERT INTO partman_test.time_taptest_table (peaktemp_f, logdate) VALUES (generate_series(40,49), CURRENT_TIMESTAMP - '1 day'::interval); INSERT INTO partman_test.time_taptest_table (peaktemp_f, logdate) VALUES (generate_series(50,70), CURRENT_TIMESTAMP - '2 days'::interval); INSERT INTO partman_test.time_taptest_table (peaktemp_f, logdate) VALUES (generate_series(71,85), CURRENT_TIMESTAMP - '3 days'::interval); INSERT INTO partman_test.time_taptest_table (peaktemp_f, logdate) VALUES (generate_series(86,100), CURRENT_TIMESTAMP - '4 days'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY[10], '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[5], '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[5], '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[7], '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[10], '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[21], '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[15], '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[15], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); UPDATE part_config SET premake = 5 WHERE parent_table = 'partman_test.time_taptest_table'; -- Run to create proper future partitions SELECT run_maintenance(); -- Insert after maintenance since native fails with no child INSERT INTO partman_test.time_taptest_table (peaktemp_f, logdate) VALUES (generate_series(101,122), CURRENT_TIMESTAMP + '5 days'::interval); -- Run again to create +5 partition now that data exists SELECT run_maintenance(); -- Data exists for +5 days, with 5 premake so +10 day table should exist SELECT has_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')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY['id'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), ARRAY['id'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), ARRAY['id'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), ARRAY['id'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), ARRAY['id'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), ARRAY['id'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')); SELECT is_indexed('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 day'::interval, 'YYYY_MM_DD'), 'peaktemp_f', 'Check for peaktemp_f index in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 day'::interval, 'YYYY_MM_DD')); SELECT is_indexed('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 day'::interval, 'YYYY_MM_DD'), 'peaktemp_f', 'Check for peaktemp_f index in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 day'::interval, 'YYYY_MM_DD')); SELECT is_indexed('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 day'::interval, 'YYYY_MM_DD'), 'peaktemp_f', 'Check for peaktemp_f index in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 day'::interval, 'YYYY_MM_DD')); SELECT is_indexed('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 day'::interval, 'YYYY_MM_DD'), 'peaktemp_f', 'Check for peaktemp_f index in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 day'::interval, 'YYYY_MM_DD')); SELECT is_indexed('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 day'::interval, 'YYYY_MM_DD'), 'peaktemp_f', 'Check for peaktemp_f index in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 day'::interval, 'YYYY_MM_DD')); SELECT is_indexed('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 day'::interval, 'YYYY_MM_DD'), 'peaktemp_f', 'Check for peaktemp_f index in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 day'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY[22], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); UPDATE part_config SET premake = 6, optimize_trigger = 6 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table (peaktemp_f, logdate) VALUES (generate_series(123,150), CURRENT_TIMESTAMP + '6 days'::interval); -- Run again now that +6 data exists SELECT run_maintenance(); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[148], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), ARRAY[28], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'13 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'13 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), ARRAY['id'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), ARRAY['id'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')); SELECT is_indexed('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 day'::interval, 'YYYY_MM_DD'), 'peaktemp_f', 'Check for peaktemp_f index in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 day'::interval, 'YYYY_MM_DD')); SELECT is_indexed('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 day'::interval, 'YYYY_MM_DD'), 'peaktemp_f', 'Check for peaktemp_f index in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 day'::interval, 'YYYY_MM_DD')); INSERT INTO partman_test.time_taptest_table (peaktemp_f, logdate) VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '20 days'::interval); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_default', ARRAY[11], 'Check that data outside trigger scope goes to default'); -- Check that all generated columns had the proper values set -- SQL to generate csv list of id values: --- with i as (select id from partman_test.time_taptest_table order by id asc) select string_agg(id::text, ',') from i; SELECT results_eq('SELECT id FROM partman_test.time_taptest_table ORDER BY id ASC', ARRAY[1,2,3,4,5,6,7,8,9,10,12,13,14,15,16,17,18,19,20,21,23,24,25,26,27,29,30,31,32,33,35,36,37,38,39,40,41,43,44,45,46,47,48,49,50,51,52,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,160,161,162,163,164,165,166,167,168,169,170], 'Check all identity generated column values'); -- SQL to generate csv list of peaktemp_c values: --- with c as (select peaktemp_c from partman_test.time_taptest_table order by id asc) select string_agg(peaktemp_c, ',') from c; SELECT results_eq('SELECT peaktemp_c FROM partman_test.time_taptest_table ORDER BY id ASC', ARRAY[-17,-16,-16,-15,-15,-14,-13,-13,-12,-12,-11,-11,-10,-10,-9,-8,-8,-7,-7,-6,-6,-5,-5,-4,-3,-3,-2,-2,-1,-1,0,0,0,1,1,2,2,4,5,5,6,6,7,7,8,8,9,10,10,11,11,12,12,13,13,14,15,15,16,16,17,17,18,18,19,20,20,21,21,22,22,23,23,24,25,25,26,26,27,27,28,28,29,30,30,31,31,32,32,33,33,34,35,35,36,36,37,37,38,38,39,40,40,41,41,42,42,43,43,44,45,45,46,46,47,47,48,48,49,50,50,51,51,52,52,53,53,54,55,55,56,56,57,57,58,58,59,60,60,61,61,62,62,63,63,64,65,65,93,93,94,95,95,96,96,97,97,98,98], 'Check all user generated column values'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test_native/test_tablespace/test-time-daily-native-tablespace-template.sql000066400000000000000000000505431412761706100332650ustar00rootroot00000000000000 -- ########## TIME DAILY TESTS ########## -- Other tests: Tablespaces for table and indexes for versions PG11 and older using template table -- Requires folder creation for tablespace and running tablespace creation before test. Cannot create tablespaces inside an explicit transaction block. -- mkdir /opt/partman_tablespace -- CREATE TABLESPACE mytablespace LOCATION '/opt/partman_tablespace'; -- Can remove tablespace and folder after test has completed -- DROP TABLESPACE mytablespace; -- rm -r /opt/partman_tablespace -- Currently doesn't test that the child tables and indexes are in the given tablespace (pgtap doesn't have that test). Just tests the code paths. \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(74); CREATE SCHEMA partman_test; CREATE TABLE partman_test.time_taptest_table (col1 int, col2 text default 'stuff', col3 timestamptz NOT NULL DEFAULT now()) PARTITION BY RANGE (col3); CREATE TABLE partman_test.time_taptest_table_template (LIKE partman_test.time_taptest_table) TABLESPACE mytablespace; ALTER TABLE partman_test.time_taptest_table_template ADD PRIMARY KEY (col1); ALTER INDEX partman_test.time_taptest_table_template_pkey SET TABLESPACE mytablespace ; CREATE UNIQUE INDEX ON partman_test.time_taptest_table_template (col2) TABLESPACE mytablespace; SELECT create_parent('partman_test.time_taptest_table', 'col3', 'native', 'daily', p_template_table := 'partman_test.time_taptest_table_template'); SELECT is_partitioned('partman_test', 'time_taptest_table', 'Check that time_taptest_table is natively partitioned'); SELECT has_table('partman_test', 'time_taptest_table_template', 'Check template table was created'); INSERT INTO partman_test.time_taptest_table (col1, col2, col3) VALUES (generate_series(1,10), 'stuff'||generate_series(1,10), CURRENT_TIMESTAMP); 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'); 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 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 day'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 day'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 day'::interval, '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 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 day'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 day'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 day'::interval, 'YYYY_MM_DD')); SELECT is_indexed('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'col2', 'Check for col2 index in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT is_indexed('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'col2', 'Check for col2 index in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT is_indexed('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 day'::interval, 'YYYY_MM_DD'), 'col2', 'Check for col2 index in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 day'::interval, 'YYYY_MM_DD')); SELECT is_indexed('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 day'::interval, 'YYYY_MM_DD'), 'col2', 'Check for col2 index in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 day'::interval, 'YYYY_MM_DD')); SELECT is_indexed('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 day'::interval, 'YYYY_MM_DD'), 'col2', 'Check for col2 index in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 day'::interval, 'YYYY_MM_DD')); SELECT is_indexed('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'col2', 'Check for col2 index in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT is_indexed('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 day'::interval, 'YYYY_MM_DD'), 'col2', 'Check for col2 index in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 day'::interval, 'YYYY_MM_DD')); SELECT is_indexed('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 day'::interval, 'YYYY_MM_DD'), 'col2', 'Check for col2 index in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 day'::interval, 'YYYY_MM_DD')); SELECT is_indexed('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 day'::interval, 'YYYY_MM_DD'), 'col2', 'Check for col2 index in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 day'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table is empty. Should be impossible for native, but leaving test here just cause.'); 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')); INSERT INTO partman_test.time_taptest_table (col1, col2, col3) VALUES (generate_series(11,20), 'stuff'||generate_series(11,20), CURRENT_TIMESTAMP + '1 day'::interval); INSERT INTO partman_test.time_taptest_table (col1, col2, col3) VALUES (generate_series(21,25), 'stuff'||generate_series(21,25), CURRENT_TIMESTAMP + '2 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col2, col3) VALUES (generate_series(26,30), 'stuff'||generate_series(26,30), CURRENT_TIMESTAMP + '3 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col2, col3) VALUES (generate_series(31,37), 'stuff'||generate_series(31,37), CURRENT_TIMESTAMP + '4 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col2, col3) VALUES (generate_series(40,49), 'stuff'||generate_series(40,49), CURRENT_TIMESTAMP - '1 day'::interval); INSERT INTO partman_test.time_taptest_table (col1, col2, col3) VALUES (generate_series(50,70), 'stuff'||generate_series(50,70), CURRENT_TIMESTAMP - '2 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col2, col3) VALUES (generate_series(71,85), 'stuff'||generate_series(71,85), CURRENT_TIMESTAMP - '3 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col2, col3) VALUES (generate_series(86,100), 'stuff'||generate_series(86,100), CURRENT_TIMESTAMP - '4 days'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY[10], '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[5], '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[5], '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[7], '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[10], '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[21], '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[15], '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[15], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); UPDATE part_config SET premake = 5 WHERE parent_table = 'partman_test.time_taptest_table'; -- Run to create proper future partitions SELECT run_maintenance(); -- Insert after maintenance since native fails with no child INSERT INTO partman_test.time_taptest_table (col1, col2, col3) VALUES (generate_series(101,122), 'stuff'||generate_series(101,122), CURRENT_TIMESTAMP + '5 days'::interval); -- Run again to create +5 partition now that data exists SELECT run_maintenance(); -- Data exists for +5 days, with 5 premake so +10 day table should exist SELECT has_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')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')); SELECT is_indexed('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 day'::interval, 'YYYY_MM_DD'), 'col2', 'Check for col2 index in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 day'::interval, 'YYYY_MM_DD')); SELECT is_indexed('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 day'::interval, 'YYYY_MM_DD'), 'col2', 'Check for col2 index in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 day'::interval, 'YYYY_MM_DD')); SELECT is_indexed('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 day'::interval, 'YYYY_MM_DD'), 'col2', 'Check for col2 index in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 day'::interval, 'YYYY_MM_DD')); SELECT is_indexed('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 day'::interval, 'YYYY_MM_DD'), 'col2', 'Check for col2 index in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 day'::interval, 'YYYY_MM_DD')); SELECT is_indexed('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 day'::interval, 'YYYY_MM_DD'), 'col2', 'Check for col2 index in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 day'::interval, 'YYYY_MM_DD')); SELECT is_indexed('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 day'::interval, 'YYYY_MM_DD'), 'col2', 'Check for col2 index in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 day'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY[22], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); UPDATE part_config SET premake = 6, optimize_trigger = 6 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table (col1, col2, col3) VALUES (generate_series(123,150), 'stuff'||generate_series(123,150), CURRENT_TIMESTAMP + '6 days'::interval); -- Run again now that +6 data exists SELECT run_maintenance(); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[148], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), ARRAY[28], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'13 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'13 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')); SELECT is_indexed('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 day'::interval, 'YYYY_MM_DD'), 'col2', 'Check for col2 index in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 day'::interval, 'YYYY_MM_DD')); SELECT is_indexed('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 day'::interval, 'YYYY_MM_DD'), 'col2', 'Check for col2 index in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 day'::interval, 'YYYY_MM_DD')); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test_no_search_path/000077500000000000000000000000001412761706100206515ustar00rootroot00000000000000pg_partman-4.6.0/test/test_no_search_path/test-time-daily_nosearchpath.sql000066400000000000000000001707451412761706100271620ustar00rootroot00000000000000-- ########## TIME STATIC TESTS ########## -- Other tests: run_maintenance(p_analyze := false), check that maintenance catches up if tables are missing -- Do not set the search path before running tests. Requires that pg_partman is installed to partman schema. -- This test it to ensure that all internal functions properly use the @extschema@ macro \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT plan(210); CREATE SCHEMA partman_test; CREATE SCHEMA partman_retention_test; CREATE ROLE partman_basic; CREATE ROLE partman_revoke; CREATE ROLE partman_owner; CREATE TABLE partman_test.time_taptest_table (col1 int primary key, col2 text, col3 timestamptz NOT NULL DEFAULT now()); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); GRANT SELECT,INSERT,UPDATE ON partman_test.time_taptest_table TO partman_basic; GRANT ALL ON partman_test.time_taptest_table TO partman_revoke; SELECT partman.create_parent('partman_test.time_taptest_table', 'col3', 'partman', 'daily'); 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'); 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 table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); SELECT results_eq('SELECT partman.partition_data_time(''partman_test.time_taptest_table'')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved'); 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')); REVOKE INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON partman_test.time_taptest_table FROM partman_revoke; INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '1 day'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '2 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(26,30), CURRENT_TIMESTAMP + '3 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(31,37), CURRENT_TIMESTAMP + '4 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(40,49), CURRENT_TIMESTAMP - '1 day'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(50,70), CURRENT_TIMESTAMP - '2 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(71,85), CURRENT_TIMESTAMP - '3 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(86,100), CURRENT_TIMESTAMP - '4 days'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY[10], '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[5], '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[5], '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[7], '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[10], '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[21], '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[15], '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[15], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); UPDATE partman.part_config SET premake = 5, optimize_trigger = 5 WHERE parent_table = 'partman_test.time_taptest_table'; -- Run to get +5 trigger in plae SELECT partman.run_maintenance(p_analyze := false); -- Insert after maintenance so new +5 day trigger is in place INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(101,122), CURRENT_TIMESTAMP + '5 days'::interval); -- Run again to create proper future partitions SELECT partman.run_maintenance(p_analyze := false); -- Data exists for +5 days, with 5 premake so +10 day table should exist SELECT has_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')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY[22], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); GRANT DELETE ON partman_test.time_taptest_table TO partman_basic; REVOKE ALL ON partman_test.time_taptest_table FROM partman_revoke; ALTER TABLE partman_test.time_taptest_table OWNER TO partman_owner; UPDATE partman.part_config SET premake = 6, optimize_trigger = 6 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT partman.run_maintenance(p_analyze := false); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(123,150), CURRENT_TIMESTAMP + '6 days'::interval); SELECT partman.run_maintenance(p_analyze := false); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[148], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), ARRAY[28], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'13 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'13 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')); SELECT partman.reapply_privileges('partman_test.time_taptest_table'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')); -- Test that maintenance will catch up DO $$ BEGIN EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'); EXECUTE 'DROP TABLE partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'); END $$; SELECT hasnt_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')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_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')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_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+'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); 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+'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); 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+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); 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+'6 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT partman.run_maintenance(); 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')||' does exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD')||' does exist'); 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')||' does exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 days'::interval, 'YYYY_MM_DD')||' does exist'); 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')||' does exist'); 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')||' does exist'); 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')||' does not exist'); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '1 day'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '2 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(26,30), CURRENT_TIMESTAMP + '3 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(40,49), CURRENT_TIMESTAMP - '1 day'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(50,70), CURRENT_TIMESTAMP - '2 days'::interval); SELECT partman.run_maintenance(); 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')||' does exist'); SELECT has_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 exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' does exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' does exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' does exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||' does exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||' does not exist'); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(31,37), CURRENT_TIMESTAMP + '4 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(101,122), CURRENT_TIMESTAMP + '5 days'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(123,150), CURRENT_TIMESTAMP + '6 days'::interval); SELECT partman.run_maintenance(); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||' does exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' does exist'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')||' does exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'13 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'13 days'::interval, 'YYYY_MM_DD')||' does not exist'); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '20 days'::interval); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT partman.drop_partition_time('partman_test.time_taptest_table', '3 days', p_keep_table := false); 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')||' does not exist'); UPDATE partman.part_config SET retention = '2 days'::interval WHERE parent_table = 'partman_test.time_taptest_table'; SELECT partman.drop_partition_time('partman_test.time_taptest_table', p_retention_schema := 'partman_retention_test'); 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')||' does not exist'); SELECT has_table('partman_retention_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')||' got moved to new schema'); SELECT partman.undo_partition('partman_test.time_taptest_table', 20, p_keep_table := false); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[129], 'Check count from parent table after undo'); SELECT hasnt_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')||' does not exist'); SELECT hasnt_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')||' does not exist'); SELECT hasnt_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')||' does not exist'); 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')||' does not exist'); 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')||' does not exist'); 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 hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_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')||' does not exist'); SELECT hasnt_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')||' does not exist'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test_procedure/000077500000000000000000000000001412761706100176645ustar00rootroot00000000000000pg_partman-4.6.0/test/test_procedure/test-id-procedure-source-table-part1.sql000066400000000000000000000111651412761706100273600ustar00rootroot00000000000000-- ########## ID TESTS NATIVE - SOURCE & TARGET TABLE ########## -- Additional tests: turn off pg_jobmon logging, use source table to natively partition existing table, use target table to undo native partitioning \set ON_ERROR_STOP true SELECT set_config('search_path','partman, public',false); SELECT plan(12); CREATE SCHEMA partman_test; CREATE TABLE partman_test.id_taptest_table_source ( col1 bigint primary key , col2 text , col3 timestamptz DEFAULT now()); INSERT INTO partman_test.id_taptest_table_source (col1) VALUES (generate_series(1,1000000)); CREATE TABLE partman_test.id_taptest_table_target ( col1 bigint primary key , col2 text , col3 timestamptz DEFAULT now()); CREATE TABLE partman_test.id_taptest_table ( col1 bigint primary key , col2 text , col3 timestamptz DEFAULT now() ) PARTITION BY RANGE (col1); CREATE INDEX ON partman_test.id_taptest_table (col3); CREATE TABLE partman_test.template_id_taptest_table (LIKE partman_test.id_taptest_table INCLUDING ALL); SELECT create_parent('partman_test.id_taptest_table', 'col1', 'native', '100000', p_jobmon := false, p_template_table := 'partman_test.template_id_taptest_table'); SELECT has_table('partman_test', 'id_taptest_table_default', 'Check id_taptest_table_default exists'); SELECT has_table('partman_test', 'id_taptest_table_p0', 'Check id_taptest_table_p0 exists'); SELECT has_table('partman_test', 'id_taptest_table_p100000', 'Check id_taptest_table_p100000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p200000', 'Check id_taptest_table_p200000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p300000', 'Check id_taptest_table_p300000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p400000', 'Check id_taptest_table_p400000 exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p500000', 'Check id_taptest_table_p500000 doesn''t exists yet'); -- Check for duped indexes since it was created on both the parent and the template SELECT is_empty($$SELECT key FROM (SELECT indexrelid::regclass AS idx , (indrelid::text ||E'\n'|| indclass::text ||E'\n'|| indkey::text ||E'\n'|| coalesce(indexprs::text,'')||E'\n' || coalesce(indpred::text,'')) AS KEY FROM pg_index WHERE indrelid = 'partman_test.id_taptest_table_p0'::regclass) sub GROUP BY key HAVING count(*)>1$$ , 'Check that table id_taptest_table_p0 does not have duped index'); SELECT is_empty($$SELECT key FROM (SELECT indexrelid::regclass AS idx , (indrelid::text ||E'\n'|| indclass::text ||E'\n'|| indkey::text ||E'\n'|| coalesce(indexprs::text,'')||E'\n' || coalesce(indpred::text,'')) AS KEY FROM pg_index WHERE indrelid = 'partman_test.id_taptest_table_p100000'::regclass) sub GROUP BY key HAVING count(*)>1$$ , 'Check that table id_taptest_table_p100000 does not have duped index'); SELECT is_empty($$SELECT key FROM (SELECT indexrelid::regclass AS idx , (indrelid::text ||E'\n'|| indclass::text ||E'\n'|| indkey::text ||E'\n'|| coalesce(indexprs::text,'')||E'\n' || coalesce(indpred::text,'')) AS KEY FROM pg_index WHERE indrelid = 'partman_test.id_taptest_table_p200000'::regclass) sub GROUP BY key HAVING count(*)>1$$ , 'Check that table id_taptest_table_p200000 does not have duped index'); SELECT is_empty($$SELECT key FROM (SELECT indexrelid::regclass AS idx , (indrelid::text ||E'\n'|| indclass::text ||E'\n'|| indkey::text ||E'\n'|| coalesce(indexprs::text,'')||E'\n' || coalesce(indpred::text,'')) AS KEY FROM pg_index WHERE indrelid = 'partman_test.id_taptest_table_p300000'::regclass) sub GROUP BY key HAVING count(*)>1$$ , 'Check that table id_taptest_table_p300000 does not have duped index'); SELECT is_empty($$SELECT key FROM (SELECT indexrelid::regclass AS idx , (indrelid::text ||E'\n'|| indclass::text ||E'\n'|| indkey::text ||E'\n'|| coalesce(indexprs::text,'')||E'\n' || coalesce(indpred::text,'')) AS KEY FROM pg_index WHERE indrelid = 'partman_test.id_taptest_table_p400000'::regclass) sub GROUP BY key HAVING count(*)>1$$ , 'Check that table id_taptest_table_p400000 does not have duped index'); SELECT diag('!!! In separate psql terminal, please run the following (adjusting schema if needed): "CALL partman.partition_data_proc(''partman_test.id_taptest_table'', p_wait := 0, p_source_table := ''partman_test.id_taptest_table_source'');".'); SELECT diag('!!! After that, run part2 of this script to check result !!!'); SELECT * FROM finish(); pg_partman-4.6.0/test/test_procedure/test-id-procedure-source-table-part2.sql000066400000000000000000000037521412761706100273640ustar00rootroot00000000000000\set ON_ERROR_STOP true SELECT set_config('search_path','partman, public',false); SELECT plan(11); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_source LIMIT 1', 'Check that source table is empty'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[1000000], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0', ARRAY[99999], 'Check count from id_taptest_table_p0'); SELECT has_table('partman_test', 'id_taptest_table_p500000', 'Check id_taptest_table_p500000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p600000', 'Check id_taptest_table_p600000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p700000', 'Check id_taptest_table_p700000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p800000', 'Check id_taptest_table_p800000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p900000', 'Check id_taptest_table_p900000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p1000000', 'Check id_taptest_table_p1000000 exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p1100000', 'Check id_taptest_table_p1100000 doesn''t exists yet'); -- Check for duped indexes since it was created on both the parent and the template SELECT is_empty($$SELECT key FROM (SELECT indexrelid::regclass AS idx , (indrelid::text ||E'\n'|| indclass::text ||E'\n'|| indkey::text ||E'\n'|| coalesce(indexprs::text,'')||E'\n' || coalesce(indpred::text,'')) AS KEY FROM pg_index WHERE indrelid = 'partman_test.id_taptest_table_p500000'::regclass) sub GROUP BY key HAVING count(*)>1$$ , 'Check that table id_taptest_table_p500000 does not have duped index'); SELECT diag('!!! In separate psql terminal, please run the following (adjusting schema if needed): "CALL partman.run_maintenance_proc();".'); SELECT diag('!!! After that, run part3 of this script to check result !!!'); SELECT * FROM finish(); pg_partman-4.6.0/test/test_procedure/test-id-procedure-source-table-part3.sql000066400000000000000000000026531412761706100273640ustar00rootroot00000000000000\set ON_ERROR_STOP true SELECT set_config('search_path','partman, public',false); SELECT plan(7); SELECT has_table('partman_test', 'id_taptest_table_p1100000', 'Check id_taptest_table_p1100000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p1200000', 'Check id_taptest_table_p1200000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p1300000', 'Check id_taptest_table_p1300000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p1400000', 'Check id_taptest_table_p1400000 exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p1500000', 'Check id_taptest_table_p1500000 doesn''t exists yet'); -- Test default partition INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(2000000, 2000009)); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table WHERE col1 > 1000000', ARRAY[10], 'Check count from id_taptest_table for out of band data'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_default', ARRAY[10], 'Check count from id_taptest_table_default'); SELECT diag('!!! In separate psql terminal, please run the following (adjusting schema if needed): "CALL partman.undo_partition_proc(''partman_test.id_taptest_table'', p_target_table := ''partman_test.id_taptest_table_target'', p_keep_table := false);".'); SELECT diag('!!! After that, run part4 of this script to check result and cleanup tests !!!'); SELECT * FROM finish(); pg_partman-4.6.0/test/test_procedure/test-id-procedure-source-table-part4.sql000066400000000000000000000045021412761706100273600ustar00rootroot00000000000000\set ON_ERROR_STOP true SELECT set_config('search_path','partman, public',false); SELECT plan(19); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table LIMIT 1', 'Check that main partition parent set is empty'); SELECT is_empty('SELECT * FROM part_config WHERE parent_table = ''partman_test.id_taptest_table'' ', 'Check config has been cleared'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_target', ARRAY[1000010], 'Check count from target table'); SELECT hasnt_table('partman_test', 'id_taptest_table_default', 'Check id_taptest_table_default was removed'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0', 'Check id_taptest_table_p0 was removed'); SELECT hasnt_table('partman_test', 'id_taptest_table_p100000', 'Check id_taptest_table_p100000 was removed'); SELECT hasnt_table('partman_test', 'id_taptest_table_p200000', 'Check id_taptest_table_p200000 was removed'); SELECT hasnt_table('partman_test', 'id_taptest_table_p300000', 'Check id_taptest_table_p300000 was removed'); SELECT hasnt_table('partman_test', 'id_taptest_table_p400000', 'Check id_taptest_table_p400000 was removed'); SELECT hasnt_table('partman_test', 'id_taptest_table_p500000', 'Check id_taptest_table_p500000 was removed'); SELECT hasnt_table('partman_test', 'id_taptest_table_p600000', 'Check id_taptest_table_p600000 was removed'); SELECT hasnt_table('partman_test', 'id_taptest_table_p700000', 'Check id_taptest_table_p700000 was removed'); SELECT hasnt_table('partman_test', 'id_taptest_table_p800000', 'Check id_taptest_table_p800000 was removed'); SELECT hasnt_table('partman_test', 'id_taptest_table_p900000', 'Check id_taptest_table_p900000 was removed'); SELECT hasnt_table('partman_test', 'id_taptest_table_p1000000', 'Check id_taptest_table_p1000000 was removed'); SELECT hasnt_table('partman_test', 'id_taptest_table_p1100000', 'Check id_taptest_table_p1100000 was removed'); SELECT hasnt_table('partman_test', 'id_taptest_table_p1200000', 'Check id_taptest_table_p1200000 was removed'); SELECT hasnt_table('partman_test', 'id_taptest_table_p1300000', 'Check id_taptest_table_p1300000 was removed'); SELECT hasnt_table('partman_test', 'id_taptest_table_p1400000', 'Check id_taptest_table_p1400000 was removed'); DROP SCHEMA IF EXISTS partman_test CASCADE; SELECT diag('!!! Final test complete !!!'); SELECT * FROM finish(); pg_partman-4.6.0/test/test_procedure/test-time-procedure-daily-part1.sql000066400000000000000000000065061412761706100264420ustar00rootroot00000000000000-- ########## TIME DAILY TESTS PROCEDURE ########## -- Other tests: tests old trigger method, partition and undo with procedures instead of functions \set ON_ERROR_STOP true SELECT set_config('search_path','partman, public',false); SELECT plan(11); CREATE SCHEMA partman_test; CREATE TABLE partman_test.time_taptest_table (col1 serial primary key, col2 text, col3 timestamptz NOT NULL DEFAULT now()); INSERT INTO partman_test.time_taptest_table (col3) VALUES (generate_series(CURRENT_TIMESTAMP - '9 days'::interval, CURRENT_TIMESTAMP ,'1 day'::interval)); SELECT create_parent('partman_test.time_taptest_table', 'col3', 'partman', 'daily'); 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'); SELECT diag('!!! In separate psql terminal, please run the following (adjusting schema if needed): "CALL partman.partition_data_proc(''partman_test.time_taptest_table'', p_wait := 0);".'); SELECT diag('!!! After that, run part2 of this script to check result !!!'); SELECT * FROM finish(); pg_partman-4.6.0/test/test_procedure/test-time-procedure-daily-part2.sql000066400000000000000000000142611412761706100264400ustar00rootroot00000000000000-- ########## TIME DAILY TESTS PROCEDURE ########## -- Other tests: partition and undo with procedures instead of functions \set ON_ERROR_STOP true SELECT set_config('search_path','partman, public',false); SELECT plan(22); 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[1], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 day'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 days'::interval, 'YYYY_MM_DD'), ARRAY[1], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 days'::interval, 'YYYY_MM_DD')); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 day'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), ARRAY[1], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'10 day'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'10 day'::interval, 'YYYY_MM_DD')||' does not exist'); INSERT INTO partman_test.time_taptest_table (col3) VALUES (CURRENT_TIMESTAMP + '1 day'::interval); INSERT INTO partman_test.time_taptest_table (col3) VALUES (CURRENT_TIMESTAMP + '2 days'::interval); INSERT INTO partman_test.time_taptest_table (col3) VALUES (CURRENT_TIMESTAMP + '3 days'::interval); INSERT INTO partman_test.time_taptest_table (col3) VALUES (CURRENT_TIMESTAMP + '4 days'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY[1], '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[1], '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[1], '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[1], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); UPDATE part_config SET premake = 5, optimize_trigger = 5 WHERE parent_table = 'partman_test.time_taptest_table'; -- Run to get +5 trigger in place SELECT run_maintenance(p_analyze := false); -- Insert after maintenance so new +5 day trigger is in place INSERT INTO partman_test.time_taptest_table (col3) VALUES (CURRENT_TIMESTAMP + '5 days'::interval); -- Run again to create proper future partitions SELECT run_maintenance(p_analyze := false); -- Data exists for +5 days, with 5 premake so +10 day table should exist SELECT has_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')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY[1], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT diag('!!! In separate psql terminal, please run the following (adjusting schema if needed): "CALL partman.undo_partition_proc(''partman_test.time_taptest_table'', p_keep_table := false, p_wait := 0);".'); SELECT diag('!!! After that, run part3 of this script to check result and cleanup testing !!!'); SELECT * FROM finish(); pg_partman-4.6.0/test/test_procedure/test-time-procedure-daily-part3.sql000066400000000000000000000074261412761706100264460ustar00rootroot00000000000000\set ON_ERROR_STOP true SELECT set_config('search_path','partman, public',false); SELECT plan(16); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[15], 'Check count from parent table after undo'); SELECT hasnt_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')||' does not exist'); SELECT hasnt_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')||' does not exist'); SELECT hasnt_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')||' does not exist'); 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')||' does not exist'); 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')||' does not exist'); 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 hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'11 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'12 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_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')||' does not exist'); SELECT hasnt_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')||' does not exist'); DROP SCHEMA IF EXISTS partman_test CASCADE; SELECT diag('!!! Final test complete !!!'); SELECT * FROM finish(); pg_partman-4.6.0/test/test_procedure/test-time-procedure-epoch-weekly-native-part1.sql000066400000000000000000000224251412761706100312160ustar00rootroot00000000000000-- ########## NATIVE TIME WEEKLY EPOCH TESTS ########## -- Other tests: start_partition. Native epoch partitioning with stored procedure moving data from source \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true SELECT set_config('search_path','partman, public',false); SELECT plan(15); CREATE SCHEMA partman_test; CREATE TABLE partman_test.time_taptest_table_source ( col1 bigint primary key , col2 text , col3 bigint); INSERT INTO partman_test.time_taptest_table_source (col1, col3) VALUES (generate_series(1,10), extract('epoch' from CURRENT_TIMESTAMP - '8 weeks'::interval)::int); INSERT INTO partman_test.time_taptest_table_source (col1, col3) VALUES (generate_series(11,20), extract('epoch' from CURRENT_TIMESTAMP - '7 weeks'::interval)::int); INSERT INTO partman_test.time_taptest_table_source (col1, col3) VALUES (generate_series(21,25), extract('epoch' from CURRENT_TIMESTAMP - '6 weeks'::interval)::int); INSERT INTO partman_test.time_taptest_table_source (col1, col3) VALUES (generate_series(26,30), extract('epoch' from CURRENT_TIMESTAMP - '5 weeks'::interval)::int); INSERT INTO partman_test.time_taptest_table_source (col1, col3) VALUES (generate_series(31,37), extract('epoch' from CURRENT_TIMESTAMP - '4 week'::interval)::int); INSERT INTO partman_test.time_taptest_table_source (col1, col3) VALUES (generate_series(38,49), extract('epoch' from CURRENT_TIMESTAMP - '3 week'::interval)::int); INSERT INTO partman_test.time_taptest_table_source (col1, col3) VALUES (generate_series(50,70), extract('epoch' from CURRENT_TIMESTAMP - '2 weeks'::interval)::int); INSERT INTO partman_test.time_taptest_table_source (col1, col3) VALUES (generate_series(71,85), extract('epoch' from CURRENT_TIMESTAMP - '1 week'::interval)::int); INSERT INTO partman_test.time_taptest_table_source (col1, col3) VALUES (generate_series(86,100), extract('epoch' from CURRENT_TIMESTAMP + '1 week'::interval)::int); INSERT INTO partman_test.time_taptest_table_source (col1, col3) VALUES (generate_series(101,110), extract('epoch' from CURRENT_TIMESTAMP + '2 weeks'::interval)::int); CREATE TABLE partman_test.time_taptest_table_target ( col1 bigint primary key , col2 text , col3 bigint); -- Add back when primary key is supported in native partitioning --CREATE TABLE partman_test.time_taptest_table (col1 int primary key, col2 text, col3 int NOT NULL DEFAULT extract('epoch' from CURRENT_TIMESTAMP)::int); CREATE TABLE partman_test.time_taptest_table ( col1 int, col2 text, col3 bigint 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', 'weekly', p_epoch := 'seconds' , p_premake := 2, p_start_partition := to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'YYYY-MM-DD HH24:MI:SS')); SELECT is_partitioned('partman_test', 'time_taptest_table', 'Check that time_taptest_table is natively partitioned'); SELECT has_table('partman_test', 'time_taptest_table_default', 'Check time_taptest_table_default exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' exists (+1 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||' exists (+2 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' does not exist (+3 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' exists (-1 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' exists (-2 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' exists (-3 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' exists (-4 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||' exists (-5 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||' exists (-6 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')||' exists (-7 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')||' exists (-8 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'9 weeks'::interval, 'IYYY"w"IW')||' does not exist (-9 weeks)'); -- Add back when index support is available --SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), ARRAY['col1'], -- 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')); --SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), ARRAY['col1'], -- 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' (+1 weeks)'); --SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], -- 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||' (+2 weeks)'); --SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), ARRAY['col1'], -- 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' (-1 weeks)'); --SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], -- 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' (-2 weeks)'); --SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], -- 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' (-3 weeks)'); --SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], -- 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' (-4 weeks)'); --SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], -- 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||' (-5 weeks)'); --SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], -- 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||' (-6 weeks)'); --SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], -- 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')||' (-7 weeks)'); --SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], -- 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')||' (-8 weeks)'); -- END pk check SELECT diag('!!! In separate psql terminal, please run the following (adjusting schema if needed): "CALL partman.partition_data_proc(''partman_test.time_taptest_table'', p_wait := 0, p_source_table := ''partman_test.time_taptest_table_source'');".'); SELECT diag('!!! After that, run part2 of this script to check result !!!'); SELECT * FROM finish(); pg_partman-4.6.0/test/test_procedure/test-time-procedure-epoch-weekly-native-part2.sql000066400000000000000000000063521412761706100312200ustar00rootroot00000000000000\set ON_ERROR_STOP true SELECT set_config('search_path','partman, public',false); SELECT plan(12); 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[110], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP - '8 weeks'::interval, 'IYYY"w"IW'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')||' (-8 weeks)'); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')||' (-7 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||' (-6 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||' (-5 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), ARRAY[7], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' (-4 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), ARRAY[12], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' (-3 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), ARRAY[21], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' (-2 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' (-1 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' (+1 weeks)'); UPDATE part_config SET premake = 3 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT diag('!!! In separate psql terminal, please run the following (adjusting schema if needed): "CALL partman.run_maintenance_proc();".'); SELECT diag('!!! After that, run part3 of this script to check result !!!'); SELECT * FROM finish(); pg_partman-4.6.0/test/test_procedure/test-time-procedure-epoch-weekly-native-part3.sql000066400000000000000000000067101412761706100312170ustar00rootroot00000000000000\set ON_ERROR_STOP true SELECT set_config('search_path','partman, public',false); SELECT plan(8); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(111,120), extract('epoch' from CURRENT_TIMESTAMP + '3 weeks'::interval)::int); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' exists (+3 weeks'); -- Cannot test for next week not existing. Different lengths of months will sometimes cause an extra partition. -- Add back when index support is available --SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], -- 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' (+3 weeks)'); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' (+3 weeks)'); UPDATE part_config SET premake = 4 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(121,130), extract('epoch' from CURRENT_TIMESTAMP + '4 weeks'::interval)::int); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[130], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' (+4 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' exists (+4 weeks)'); -- Cannot test for next week not existing. Different lengths of months will sometimes cause an extra partition. -- Add back when index support is available --SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), ARRAY['col1'], -- 'Check for primary key in time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' (+4 weeks)'); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), extract('epoch' from CURRENT_TIMESTAMP + '20 weeks'::interval)::int); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table_default', ARRAY[11], 'Check that data outside partition scope goes to default'); SELECT diag('!!! In separate psql terminal, please run the following (adjusting schema if needed): "CALL partman.undo_partition_proc(''partman_test.time_taptest_table'', p_keep_table := false, p_wait := 0, p_target_table := ''partman_test.time_taptest_table_target'');".'); SELECT diag('!!! After that, run part4 of this script to check result and cleanup test objects !!!'); SELECT * FROM finish(); pg_partman-4.6.0/test/test_procedure/test-time-procedure-epoch-weekly-native-part4.sql000066400000000000000000000070621412761706100312210ustar00rootroot00000000000000\set ON_ERROR_STOP true SELECT set_config('search_path','partman, public',false); SELECT plan(15); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table_target', ARRAY[141], 'Check count from target table after undo'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||' does not exist (now)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' does not exist (+1 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||' does not exist (+2 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' does not exist (+3 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' does not exist (+4 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' does not exist (-1 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' does not exist (-2 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' does not exist (-3 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' does not exist (-4 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||' does not exist (-5 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||' does not exist (-6 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')||' does not exist (-7 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')||' does not exist (-8 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_default', 'Check time_taptest_table_default does not exist'); DROP SCHEMA IF EXISTS partman_test CASCADE; SELECT diag('!!! Final test complete !!!'); SELECT * FROM finish(); pg_partman-4.6.0/test/test_procedure/test-time-procedure-source-table-part1.sql000066400000000000000000000133521412761706100277220ustar00rootroot00000000000000-- ########## TIME DAILY TESTS NATIVE PROCEDURE ########## -- Other tests: native, default out of bounds data, partition and undo with procedures instead of functions \set ON_ERROR_STOP true SELECT set_config('search_path','partman, public',false); SELECT plan(16); CREATE SCHEMA partman_test; CREATE TABLE partman_test.time_taptest_table_source ( col1 bigserial , col2 text , col3 timestamptz DEFAULT now() NOT NULL); INSERT INTO partman_test.time_taptest_table_source (col3) VALUES (generate_series(CURRENT_TIMESTAMP - '9 days'::interval, CURRENT_TIMESTAMP, '1 day'::interval)); CREATE TABLE partman_test.time_taptest_table_target ( col1 bigserial , col2 text , col3 timestamptz DEFAULT now() NOT NULL); CREATE TABLE partman_test.time_taptest_table ( col1 bigserial , col2 text , col3 timestamptz DEFAULT now() NOT NULL ) PARTITION BY RANGE (col3); CREATE INDEX ON partman_test.time_taptest_table(col3); CREATE TABLE partman_test.template_time_taptest_table (LIKE partman_test.time_taptest_table INCLUDING ALL); SELECT has_table('partman_test', 'template_time_taptest_table', 'Check that template table was made'); SELECT create_parent('partman_test.time_taptest_table', 'col3', 'native', 'daily', p_template_table := 'partman_test.template_time_taptest_table'); SELECT has_table('partman_test', 'time_taptest_table_default', 'Check time_taptest_table_default exists'); 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'); -- Check for duped indexes since it was created on both the parent and the template SELECT is_empty($$SELECT key FROM (SELECT indexrelid::regclass AS idx , (indrelid::text ||E'\n'|| indclass::text ||E'\n'|| indkey::text ||E'\n'|| coalesce(indexprs::text,'')||E'\n' || coalesce(indpred::text,'')) AS KEY FROM pg_index WHERE indrelid = format('partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'))::regclass) sub GROUP BY key HAVING count(*)>1$$ , 'Check that table does not have duped index'); SELECT is_empty($$SELECT key FROM (SELECT indexrelid::regclass AS idx , (indrelid::text ||E'\n'|| indclass::text ||E'\n'|| indkey::text ||E'\n'|| coalesce(indexprs::text,'')||E'\n' || coalesce(indpred::text,'')) AS KEY FROM pg_index WHERE indrelid = format('partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 day'::interval, 'YYYY_MM_DD'))::regclass) sub GROUP BY key HAVING count(*)>1$$ , 'Check that table does not have duped index'); SELECT is_empty($$SELECT key FROM (SELECT indexrelid::regclass AS idx , (indrelid::text ||E'\n'|| indclass::text ||E'\n'|| indkey::text ||E'\n'|| coalesce(indexprs::text,'')||E'\n' || coalesce(indpred::text,'')) AS KEY FROM pg_index WHERE indrelid = format('partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 day'::interval, 'YYYY_MM_DD'))::regclass) sub GROUP BY key HAVING count(*)>1$$ , 'Check that table does not have duped index'); SELECT diag('!!! In separate psql terminal, please run the following (adjusting schema if needed): "CALL partman.partition_data_proc(''partman_test.time_taptest_table'', p_wait := 0, p_source_table := ''partman_test.time_taptest_table_source'');".'); SELECT diag('!!! After that, run part2 of this script to check result !!!'); SELECT * FROM finish(); pg_partman-4.6.0/test/test_procedure/test-time-procedure-source-table-part2.sql000066400000000000000000000103701412761706100277200ustar00rootroot00000000000000\set ON_ERROR_STOP true SELECT set_config('search_path','partman, public',false); SELECT plan(14); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_source', 'Check that source table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY[1], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 day'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 days'::interval, 'YYYY_MM_DD'), ARRAY[1], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 days'::interval, 'YYYY_MM_DD')); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 day'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), ARRAY[1], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'10 day'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'10 day'::interval, 'YYYY_MM_DD')||' does not exist'); INSERT INTO partman_test.time_taptest_table (col3) VALUES (CURRENT_TIMESTAMP + '1 day'::interval); INSERT INTO partman_test.time_taptest_table (col3) VALUES (CURRENT_TIMESTAMP + '2 days'::interval); INSERT INTO partman_test.time_taptest_table (col3) VALUES (CURRENT_TIMESTAMP + '3 days'::interval); INSERT INTO partman_test.time_taptest_table (col3) VALUES (CURRENT_TIMESTAMP + '4 days'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY[1], '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[1], '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[1], '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[1], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')); UPDATE part_config SET premake = 5 WHERE parent_table = 'partman_test.time_taptest_table'; -- Test native default partition INSERT INTO partman_test.time_taptest_table (col3) VALUES (CURRENT_TIMESTAMP + '5 days'::interval); SELECT results_eq ('SELECT count(*)::int FROM partman_test.time_taptest_table_default', ARRAY[1], 'Check native default child table for data'); -- Must either delete the data from default or move it to a temporary table in order to create next child table -- Presence of a default partition will not allow a new child table with a range that contains data located in the default DELETE FROM partman_test.time_taptest_table WHERE date_trunc('day', col3) = date_trunc('day', CURRENT_TIMESTAMP + '5 days'::interval); SELECT is_empty ('SELECT col1 FROM partman_test.time_taptest_table_default', 'Check native default child table is empty'); SELECT diag('!!! In separate psql terminal, please run the following (adjusting schema if needed): "CALL partman.run_maintenance_proc();".'); SELECT diag('!!! After that, run part3 of this script to check result !!!'); SELECT * FROM finish(); pg_partman-4.6.0/test/test_procedure/test-time-procedure-source-table-part3.sql000066400000000000000000000060061412761706100277220ustar00rootroot00000000000000\set ON_ERROR_STOP true SELECT set_config('search_path','partman, public',false); SELECT plan(9); -- child table should now be available for data INSERT INTO partman_test.time_taptest_table (col3) VALUES (CURRENT_TIMESTAMP + '5 days'::interval); -- Data exists for +5 days, with 5 premake so +10 day table should exist SELECT has_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')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'10 days'::interval, 'YYYY_MM_DD')||' exists'); -- Check for duped indexes since it was created on both the parent and the template SELECT is_empty($$SELECT key FROM (SELECT indexrelid::regclass AS idx , (indrelid::text ||E'\n'|| indclass::text ||E'\n'|| indkey::text ||E'\n'|| coalesce(indexprs::text,'')||E'\n' || coalesce(indpred::text,'')) AS KEY FROM pg_index WHERE indrelid = format('partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 day'::interval, 'YYYY_MM_DD'))::regclass) sub GROUP BY key HAVING count(*)>1$$ , 'Check that table does not have duped index'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_default', 'Check that default table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY[1], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT diag('!!! In separate psql terminal, please run the following (adjusting schema if needed): "CALL partman.undo_partition_proc(''partman_test.time_taptest_table'', p_keep_table := false, p_wait := 0, p_target_table := ''partman_test.time_taptest_table_target'');".'); SELECT diag('!!! After that, run part4 of this script to check result and cleanup test objects !!!'); SELECT * FROM finish(); pg_partman-4.6.0/test/test_procedure/test-time-procedure-source-table-part4.sql000066400000000000000000000065021412761706100277240ustar00rootroot00000000000000\set ON_ERROR_STOP true SELECT set_config('search_path','partman, public',false); SELECT plan(15); SELECT is_empty('SELECT col3 FROM partman_test.time_taptest_table', 'Check that original partition set is empty after undo'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_target', ARRAY[15], 'Check count from target table after undo'); SELECT hasnt_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')||' does not exist'); SELECT hasnt_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')||' does not exist'); SELECT hasnt_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')||' does not exist'); 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')||' does not exist'); 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')||' does not exist'); 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 hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'8 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD'), 'Check time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'9 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_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')||' does not exist'); SELECT hasnt_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')||' does not exist'); SELECT hasnt_table('partman_test', 'time_taptest_table_default', 'Check time_taptest_table_default does not exist'); DROP SCHEMA IF EXISTS partman_test CASCADE; SELECT diag('!!! Final test complete !!!'); SELECT * FROM finish(); pg_partman-4.6.0/test/test_procedure/test-time-procedure-weekly-part1.sql000066400000000000000000000233041412761706100266330ustar00rootroot00000000000000-- ########## TIME WEEKLY TESTS ########## -- Other tests: native, constraint_cols/optimize_constraint. Use reapply_constraints procedure \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true SELECT set_config('search_path','partman, public',false); SELECT plan(27); CREATE SCHEMA partman_test; CREATE TABLE partman_test.time_taptest_table ( col1 int , col2 text , col3 timestamptz NOT NULL DEFAULT now()) PARTITION BY RANGE (col3); CREATE TABLE partman_test.template_time_taptest_table (LIKE partman_test.time_taptest_table INCLUDING ALL); CREATE TABLE partman_test.target_time_taptest_table (LIKE partman_test.time_taptest_table INCLUDING ALL); -- Test that premake can be less than optimize_constraint without issues SELECT create_parent('partman_test.time_taptest_table', 'col3', 'native', 'weekly' , '{"col1"}', p_premake := 2 , p_start_partition := to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'YYYY-MM-DD HH24:MI:SS') , p_template_table := 'partman_test.template_time_taptest_table'); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP - '8 weeks'::interval); SELECT has_table('partman_test', 'time_taptest_table_default', 'Check time_taptest_table_default exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' exists (+1 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||' exists (+2 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW')||' does not exist (+3 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' exists (-1 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' exists (-2 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' exists (-3 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' exists (-4 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||' exists (-5 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||' exists (-6 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')||' exists (-7 weeks)'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')||' exists (-8 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'9 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'9 weeks'::interval, 'IYYY"w"IW')||' does not exist (-9 weeks)'); 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 - '8 weeks'::interval, 'IYYY"w"IW'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')||' (-8 weeks)'); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), CURRENT_TIMESTAMP - '7 weeks'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), CURRENT_TIMESTAMP - '6 weeks'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(26,30), CURRENT_TIMESTAMP - '5 weeks'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(31,37), CURRENT_TIMESTAMP - '4 week'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(38,49), CURRENT_TIMESTAMP - '3 week'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(50,70), CURRENT_TIMESTAMP - '2 weeks'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(71,85), CURRENT_TIMESTAMP - '1 week'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(86,100), CURRENT_TIMESTAMP + '1 week'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(101,110), CURRENT_TIMESTAMP + '2 weeks'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')||' (-7 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')||' (-6 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')||' (-5 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), ARRAY[7], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' (-4 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW'), ARRAY[12], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'3 weeks'::interval, 'IYYY"w"IW')||' (-3 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), ARRAY[21], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' (-2 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' (-1 weeks)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), ARRAY[15], 'Check count from time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' (+1 weeks)'); -- Default optimize_constraint is 30, so set it to a value that will trigger it to work for given conditions of this partition set UPDATE part_config SET premake = 3, optimize_constraint = 5 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); -- Automatic run of apply_constraints due to run_maintenance will put constraint on "now() - 4 weeks" partition with an optimize_constraint value of 5 -- This is due to values "now() + 2 weeks" being inserted above. -- Ex: Current week is 39. +2 weeks is 41. Going back 5 weeks is 36, but constraint is applied to the table OLDER than that value. Hence constraint will be on week 35 SELECT col_has_check('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'col1' , 'Check for additional constraint on col1 on time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' (-4 weeks)'); -- There is no check for not having a constraint. So just run procedure to drop & recreate them all and then see if they're all there, rechecking above again -- Cannot run procedures in pgtap since they have independent transactions that the tap test transactions cannot handle SELECT diag('!!! In separate psql terminal, please run the followingi (adjusting schema if needed): "CALL partman.reapply_constraints_proc(''partman_test.time_taptest_table'', p_drop_constraints := true, p_apply_constraints := true);".'); SELECT diag('!!! After that, run part2 of this script to check result and cleanup objects. !!!'); SELECT * FROM finish(); pg_partman-4.6.0/test/test_procedure/test-time-procedure-weekly-part2.sql000066400000000000000000000077711412761706100266460ustar00rootroot00000000000000\set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true SELECT set_config('search_path','partman, public',false); SELECT plan(15); SELECT col_has_check('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW'), 'col1' , 'Check for additional constraint on col1 on time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'4 weeks'::interval, 'IYYY"w"IW')||' (-4 weeks)'); SELECT col_has_check('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW'), 'col1' , 'Check for additional constraint on col1 on time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'8 weeks'::interval, 'IYYY"w"IW')); SELECT col_has_check('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW'), 'col1' , 'Check for additional constraint on col1 on time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'7 weeks'::interval, 'IYYY"w"IW')); SELECT col_has_check('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW'), 'col1' , 'Check for additional constraint on col1 on time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'6 weeks'::interval, 'IYYY"w"IW')); SELECT col_has_check('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW'), 'col1' , 'Check for additional constraint on col1 on time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'5 weeks'::interval, 'IYYY"w"IW')); -- Just testing normal undo function instead of procedure SELECT undo_partition('partman_test.time_taptest_table', 20, p_keep_table := false, p_target_table := 'partman_test.target_time_taptest_table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.target_time_taptest_table', ARRAY[110], 'Check count from target table after undo'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP, 'IYYY"w"IW')||' does not exist (now)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'1 week'::interval, 'IYYY"w"IW')||' does not exist (+1 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'2 weeks'::interval, 'IYYY"w"IW')||' does not exist (+2 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'3 weeks'::interval, 'IYYY"w"IW')||' does not exist (+3 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'4 weeks'::interval, 'IYYY"w"IW')||' does not exist (+4 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'5 weeks'::interval, 'IYYY"w"IW')||' does not exist (+5 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP+'6 weeks'::interval, 'IYYY"w"IW')||' does not exist (+6 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'1 week'::interval, 'IYYY"w"IW')||' does not exist (-1 weeks)'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW'), 'Check time_taptest_table_'||to_char(CURRENT_TIMESTAMP-'2 weeks'::interval, 'IYYY"w"IW')||' does not exist (-2 weeks)'); DROP SCHEMA IF EXISTS partman_test CASCADE; SELECT diag('!!! Final test complete !!!'); SELECT * FROM finish(); pg_partman-4.6.0/test/test_reindex/000077500000000000000000000000001412761706100173325ustar00rootroot00000000000000pg_partman-4.6.0/test/test_reindex/01-setup-tables.sql000066400000000000000000000143541412761706100227100ustar00rootroot00000000000000\set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true CREATE SCHEMA partman_reindex_test; SELECT set_config('search_path','partman_reindex_test, partman, public',false); DELETE FROM part_config WHERE parent_table = 'partman_reindex_test.test_reindex'; CREATE TABLE test_reindex (id bigint, stuff text, morestuff timestamptz default now()); ALTER TABLE test_reindex ADD CONSTRAINT test_reindex_id_pkey PRIMARY KEY (id); INSERT INTO test_reindex VALUES (generate_series(1,1000), 'stuff'||generate_series(1, 1000)); CREATE INDEX test_reindex_stuff_idx ON test_reindex (stuff); CREATE INDEX test_reindex_upper_stuff_idx ON test_reindex(upper(stuff)); SELECT create_parent('partman_reindex_test.test_reindex', 'id', 'partman', '100'); SELECT plan(40); SELECT has_table('partman_reindex_test', 'test_reindex', 'Check test_reindex exists'); SELECT col_is_pk('partman_reindex_test', 'test_reindex', ARRAY['id'], 'Check for primary key in test_reindex'); SELECT has_index('partman_reindex_test', 'test_reindex', 'test_reindex_stuff_idx', ARRAY['stuff'], 'Check for stuff index in test_reindex'); SELECT has_index('partman_reindex_test', 'test_reindex', 'test_reindex_upper_stuff_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) index in test_reindex'); SELECT has_table('partman_reindex_test', 'test_reindex_p600', 'Check test_reindex_p600 exists'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p600', ARRAY['id'], 'Check for primary key in test_reindex_p600'); SELECT has_index('partman_reindex_test', 'test_reindex_p600', 'test_reindex_p600_stuff_idx', ARRAY['stuff'], 'Check for stuff index in test_reindex_p600'); SELECT has_index('partman_reindex_test', 'test_reindex_p600', 'test_reindex_p600_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) index in test_reindex_p600'); SELECT has_table('partman_reindex_test', 'test_reindex_p700', 'Check test_reindex_p700 exists'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p700', ARRAY['id'], 'Check for primary key in test_reindex_p700'); SELECT has_index('partman_reindex_test', 'test_reindex_p700', 'test_reindex_p700_stuff_idx', ARRAY['stuff'], 'Check for stuff index in test_reindex_p700'); SELECT has_index('partman_reindex_test', 'test_reindex_p700', 'test_reindex_p700_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) index in test_reindex_p700'); SELECT has_table('partman_reindex_test', 'test_reindex_p800', 'Check test_reindex_p800 exists'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p800', ARRAY['id'], 'Check for primary key in test_reindex_p800'); SELECT has_index('partman_reindex_test', 'test_reindex_p800', 'test_reindex_p800_stuff_idx', ARRAY['stuff'], 'Check for stuff index in test_reindex_p800'); SELECT has_index('partman_reindex_test', 'test_reindex_p800', 'test_reindex_p800_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) index in test_reindex_p800'); SELECT has_table('partman_reindex_test', 'test_reindex_p900', 'Check test_reindex_p900 exists'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p900', ARRAY['id'], 'Check for primary key in test_reindex_p900'); SELECT has_index('partman_reindex_test', 'test_reindex_p900', 'test_reindex_p900_stuff_idx', ARRAY['stuff'], 'Check for stuff index in test_reindex_p900'); SELECT has_index('partman_reindex_test', 'test_reindex_p900', 'test_reindex_p900_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) index in test_reindex_p900'); SELECT has_table('partman_reindex_test', 'test_reindex_p1000', 'Check test_reindex_p1000 exists'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p1000', ARRAY['id'], 'Check for primary key in test_reindex_p1000'); SELECT has_index('partman_reindex_test', 'test_reindex_p1000', 'test_reindex_p1000_stuff_idx', ARRAY['stuff'], 'Check for stuff index in test_reindex_p1000'); SELECT has_index('partman_reindex_test', 'test_reindex_p1000', 'test_reindex_p1000_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) index in test_reindex_p1000'); SELECT has_table('partman_reindex_test', 'test_reindex_p1100', 'Check test_reindex_p1100 exists'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p1100', ARRAY['id'], 'Check for primary key in test_reindex_p1100'); SELECT has_index('partman_reindex_test', 'test_reindex_p1100', 'test_reindex_p1100_stuff_idx', ARRAY['stuff'], 'Check for stuff index in test_reindex_p1100'); SELECT has_index('partman_reindex_test', 'test_reindex_p1100', 'test_reindex_p1100_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) index in test_reindex_p1100'); SELECT has_table('partman_reindex_test', 'test_reindex_p1200', 'Check test_reindex_p1200 exists'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p1200', ARRAY['id'], 'Check for primary key in test_reindex_p1200'); SELECT has_index('partman_reindex_test', 'test_reindex_p1200', 'test_reindex_p1200_stuff_idx', ARRAY['stuff'], 'Check for stuff index in test_reindex_p1200'); SELECT has_index('partman_reindex_test', 'test_reindex_p1200', 'test_reindex_p1200_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) index in test_reindex_p1200'); SELECT has_table('partman_reindex_test', 'test_reindex_p1300', 'Check test_reindex_p1300 exists'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p1300', ARRAY['id'], 'Check for primary key in test_reindex_p1300'); SELECT has_index('partman_reindex_test', 'test_reindex_p1300', 'test_reindex_p1300_stuff_idx', ARRAY['stuff'], 'Check for stuff index in test_reindex_p1300'); SELECT has_index('partman_reindex_test', 'test_reindex_p1300', 'test_reindex_p1300_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) index in test_reindex_p1300'); SELECT has_table('partman_reindex_test', 'test_reindex_p1400', 'Check test_reindex_p1400 exists'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p1400', ARRAY['id'], 'Check for primary key in test_reindex_p1400'); SELECT has_index('partman_reindex_test', 'test_reindex_p1400', 'test_reindex_p1400_stuff_idx', ARRAY['stuff'], 'Check for stuff index in test_reindex_p1400'); SELECT has_index('partman_reindex_test', 'test_reindex_p1400', 'test_reindex_p1400_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) index in test_reindex_p1400'); SELECT partition_data_id('partman_reindex_test.test_reindex', p_batch_count := 20); SELECT diag('!!! Next run 02-change-index.sql !!!'); SELECT * FROM finish(); pg_partman-4.6.0/test/test_reindex/02-change-index.sql000066400000000000000000000026241412761706100226300ustar00rootroot00000000000000\set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true SELECT set_config('search_path','partman_reindex_test, partman, public',false); DROP INDEX test_reindex_stuff_idx; CREATE INDEX test_reindex_morestuff_idx ON test_reindex (morestuff); CREATE INDEX test_reindex_stuff_morestuff_idx ON test_reindex (stuff, morestuff); CREATE INDEX test_reindex_lower_stuff_idx ON test_reindex(lower(stuff)); ALTER TABLE test_reindex ADD new_id bigint; UPDATE test_reindex SET new_id = id; ALTER TABLE test_reindex DROP CONSTRAINT test_reindex_id_pkey; ALTER TABLE test_reindex ADD CONSTRAINT test_reindex_new_id primary key (new_id); SELECT plan(3); SELECT hasnt_index('partman_reindex_test', 'test_reindex', 'test_reindex_stuff_idx', 'Check for stuff index in test_reindex'); SELECT has_index('partman_reindex_test', 'test_reindex', 'test_reindex_morestuff_idx', ARRAY['morestuff'], 'Check for stuff index in test_reindex'); SELECT col_is_pk('partman_reindex_test', 'test_reindex', ARRAY['new_id'], 'Check for new primary key in test_reindex'); SELECT diag('!!! Now run reapply_index.py on "partman_reindex_test.test_reindex" with the --primary option to apply the new indexes to all the children !!!'); SELECT diag('!!! After that completes, run 03-check-indexes.sql !!!'); SELECT diag('!!! You can set any options you''d like on the python script to test them. Re-run from test 01 to test different options !!! '); SELECT * FROM finish(); pg_partman-4.6.0/test/test_reindex/03-check-indexes.sql000066400000000000000000000354551412761706100230210ustar00rootroot00000000000000\set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true SELECT set_config('search_path','partman_reindex_test, partman, public',false); SELECT plan(90); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p0', 'test_reindex_p0_stuff_idx', 'Check stuff index was removed in test_reindex_p0'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p100', 'test_reindex_p100_stuff_idx', 'Check stuff index was removed in test_reindex_p100'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p200', 'test_reindex_p200_stuff_idx', 'Check stuff index was removed in test_reindex_p200'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p300', 'test_reindex_p300_stuff_idx', 'Check stuff index was removed in test_reindex_p300'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p400', 'test_reindex_p400_stuff_idx', 'Check stuff index was removed in test_reindex_p400'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p500', 'test_reindex_p500_stuff_idx', 'Check stuff index was removed in test_reindex_p500'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p600', 'test_reindex_p600_stuff_idx', 'Check stuff index was removed in test_reindex_p600'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p700', 'test_reindex_p700_stuff_idx', 'Check stuff index was removed in test_reindex_p700'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p800', 'test_reindex_p800_stuff_idx', 'Check stuff index was removed in test_reindex_p800'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p900', 'test_reindex_p900_stuff_idx', 'Check stuff index was removed in test_reindex_p900'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p1000', 'test_reindex_p1000_stuff_idx', 'Check stuff index was removed in test_reindex_p1000'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p1100', 'test_reindex_p1100_stuff_idx', 'Check stuff index was removed in test_reindex_p1100'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p1200', 'test_reindex_p1200_stuff_idx', 'Check stuff index was removed in test_reindex_p1200'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p1300', 'test_reindex_p1300_stuff_idx', 'Check stuff index was removed in test_reindex_p1300'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p1400', 'test_reindex_p1400_stuff_idx', 'Check stuff index was removed in test_reindex_p1400'); SELECT has_index('partman_reindex_test', 'test_reindex_p0', 'test_reindex_p0_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p0'); SELECT has_index('partman_reindex_test', 'test_reindex_p100', 'test_reindex_p100_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p100'); SELECT has_index('partman_reindex_test', 'test_reindex_p200', 'test_reindex_p200_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p200'); SELECT has_index('partman_reindex_test', 'test_reindex_p300', 'test_reindex_p300_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p300'); SELECT has_index('partman_reindex_test', 'test_reindex_p400', 'test_reindex_p400_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p400'); SELECT has_index('partman_reindex_test', 'test_reindex_p500', 'test_reindex_p500_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p500'); SELECT has_index('partman_reindex_test', 'test_reindex_p600', 'test_reindex_p600_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p600'); SELECT has_index('partman_reindex_test', 'test_reindex_p700', 'test_reindex_p700_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p700'); SELECT has_index('partman_reindex_test', 'test_reindex_p800', 'test_reindex_p800_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p800'); SELECT has_index('partman_reindex_test', 'test_reindex_p900', 'test_reindex_p900_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p900'); SELECT has_index('partman_reindex_test', 'test_reindex_p1000', 'test_reindex_p1000_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p1000'); SELECT has_index('partman_reindex_test', 'test_reindex_p1100', 'test_reindex_p1100_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p1100'); SELECT has_index('partman_reindex_test', 'test_reindex_p1200', 'test_reindex_p1200_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p1200'); SELECT has_index('partman_reindex_test', 'test_reindex_p1300', 'test_reindex_p1300_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p1300'); SELECT has_index('partman_reindex_test', 'test_reindex_p1400', 'test_reindex_p1400_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p1400'); SELECT has_index('partman_reindex_test', 'test_reindex_p0', 'test_reindex_p0_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for morestuff index in test_reindex_p0'); SELECT has_index('partman_reindex_test', 'test_reindex_p100', 'test_reindex_p100_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p100'); SELECT has_index('partman_reindex_test', 'test_reindex_p200', 'test_reindex_p200_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p200'); SELECT has_index('partman_reindex_test', 'test_reindex_p300', 'test_reindex_p300_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p300'); SELECT has_index('partman_reindex_test', 'test_reindex_p400', 'test_reindex_p400_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p400'); SELECT has_index('partman_reindex_test', 'test_reindex_p500', 'test_reindex_p500_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p500'); SELECT has_index('partman_reindex_test', 'test_reindex_p600', 'test_reindex_p600_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p600'); SELECT has_index('partman_reindex_test', 'test_reindex_p700', 'test_reindex_p700_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p700'); SELECT has_index('partman_reindex_test', 'test_reindex_p800', 'test_reindex_p800_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p800'); SELECT has_index('partman_reindex_test', 'test_reindex_p900', 'test_reindex_p900_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p900'); SELECT has_index('partman_reindex_test', 'test_reindex_p1000', 'test_reindex_p1000_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p1000'); SELECT has_index('partman_reindex_test', 'test_reindex_p1100', 'test_reindex_p1100_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p1100'); SELECT has_index('partman_reindex_test', 'test_reindex_p1200', 'test_reindex_p1200_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p1200'); SELECT has_index('partman_reindex_test', 'test_reindex_p1300', 'test_reindex_p1300_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p1300'); SELECT has_index('partman_reindex_test', 'test_reindex_p1400', 'test_reindex_p1400_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p1400'); SELECT has_index('partman_reindex_test', 'test_reindex_p0', 'test_reindex_p0_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) index in test_reindex_p0'); SELECT has_index('partman_reindex_test', 'test_reindex_p100', 'test_reindex_p100_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p100'); SELECT has_index('partman_reindex_test', 'test_reindex_p200', 'test_reindex_p200_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p200'); SELECT has_index('partman_reindex_test', 'test_reindex_p300', 'test_reindex_p300_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p300'); SELECT has_index('partman_reindex_test', 'test_reindex_p400', 'test_reindex_p400_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p400'); SELECT has_index('partman_reindex_test', 'test_reindex_p500', 'test_reindex_p500_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p500'); SELECT has_index('partman_reindex_test', 'test_reindex_p600', 'test_reindex_p600_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p600'); SELECT has_index('partman_reindex_test', 'test_reindex_p700', 'test_reindex_p700_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p700'); SELECT has_index('partman_reindex_test', 'test_reindex_p800', 'test_reindex_p800_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p800'); SELECT has_index('partman_reindex_test', 'test_reindex_p900', 'test_reindex_p900_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p900'); SELECT has_index('partman_reindex_test', 'test_reindex_p1000', 'test_reindex_p1000_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p1000'); SELECT has_index('partman_reindex_test', 'test_reindex_p1100', 'test_reindex_p1100_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p1100'); SELECT has_index('partman_reindex_test', 'test_reindex_p1200', 'test_reindex_p1200_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p1200'); SELECT has_index('partman_reindex_test', 'test_reindex_p1300', 'test_reindex_p1300_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p1300'); SELECT has_index('partman_reindex_test', 'test_reindex_p1400', 'test_reindex_p1400_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p1400'); SELECT has_index('partman_reindex_test', 'test_reindex_p0', 'test_reindex_p0_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) index in test_reindex_p0'); SELECT has_index('partman_reindex_test', 'test_reindex_p100', 'test_reindex_p100_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p100'); SELECT has_index('partman_reindex_test', 'test_reindex_p200', 'test_reindex_p200_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p200'); SELECT has_index('partman_reindex_test', 'test_reindex_p300', 'test_reindex_p300_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p300'); SELECT has_index('partman_reindex_test', 'test_reindex_p400', 'test_reindex_p400_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p400'); SELECT has_index('partman_reindex_test', 'test_reindex_p500', 'test_reindex_p500_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p500'); SELECT has_index('partman_reindex_test', 'test_reindex_p600', 'test_reindex_p600_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p600'); SELECT has_index('partman_reindex_test', 'test_reindex_p700', 'test_reindex_p700_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p700'); SELECT has_index('partman_reindex_test', 'test_reindex_p800', 'test_reindex_p800_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p800'); SELECT has_index('partman_reindex_test', 'test_reindex_p900', 'test_reindex_p900_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p900'); SELECT has_index('partman_reindex_test', 'test_reindex_p1000', 'test_reindex_p1000_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p1000'); SELECT has_index('partman_reindex_test', 'test_reindex_p1100', 'test_reindex_p1100_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p1100'); SELECT has_index('partman_reindex_test', 'test_reindex_p1200', 'test_reindex_p1200_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p1200'); SELECT has_index('partman_reindex_test', 'test_reindex_p1300', 'test_reindex_p1300_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p1300'); SELECT has_index('partman_reindex_test', 'test_reindex_p1400', 'test_reindex_p1400_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p1400'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p0', ARRAY['new_id'], 'Check for new primary key in test_reindex_p0'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p100', ARRAY['new_id'], 'Check for new primary key in test_reindex_p100'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p200', ARRAY['new_id'], 'Check for new primary key in test_reindex_p200'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p300', ARRAY['new_id'], 'Check for new primary key in test_reindex_p300'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p400', ARRAY['new_id'], 'Check for new primary key in test_reindex_p400'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p500', ARRAY['new_id'], 'Check for new primary key in test_reindex_p500'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p600', ARRAY['new_id'], 'Check for new primary key in test_reindex_p600'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p700', ARRAY['new_id'], 'Check for new primary key in test_reindex_p700'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p800', ARRAY['new_id'], 'Check for new primary key in test_reindex_p800'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p900', ARRAY['new_id'], 'Check for new primary key in test_reindex_p900'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p1000', ARRAY['new_id'], 'Check for new primary key in test_reindex_p1000'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p1100', ARRAY['new_id'], 'Check for new primary key in test_reindex_p1100'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p1200', ARRAY['new_id'], 'Check for new primary key in test_reindex_p1200'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p1300', ARRAY['new_id'], 'Check for new primary key in test_reindex_p1300'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p1400', ARRAY['new_id'], 'Check for new primary key in test_reindex_p1400'); SELECT diag('!!! Now run reapply_index.py again with all the same arguments as last time but add --recreate_all !!!'); SELECT diag('!!! After that completes, run 04-check-indexes.sql !!!'); SELECT * FROM finish(); pg_partman-4.6.0/test/test_reindex/04-recreate_all_indexes.sql000066400000000000000000000352461412761706100244470ustar00rootroot00000000000000\set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true SELECT set_config('search_path','partman_reindex_test, partman, public',false); SELECT plan(90); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p0', 'test_reindex_p0_stuff_idx', 'Check stuff index was removed in test_reindex_p0'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p100', 'test_reindex_p100_stuff_idx', 'Check stuff index was removed in test_reindex_p100'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p200', 'test_reindex_p200_stuff_idx', 'Check stuff index was removed in test_reindex_p200'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p300', 'test_reindex_p300_stuff_idx', 'Check stuff index was removed in test_reindex_p300'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p400', 'test_reindex_p400_stuff_idx', 'Check stuff index was removed in test_reindex_p400'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p500', 'test_reindex_p500_stuff_idx', 'Check stuff index was removed in test_reindex_p500'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p600', 'test_reindex_p600_stuff_idx', 'Check stuff index was removed in test_reindex_p600'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p700', 'test_reindex_p700_stuff_idx', 'Check stuff index was removed in test_reindex_p700'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p800', 'test_reindex_p800_stuff_idx', 'Check stuff index was removed in test_reindex_p800'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p900', 'test_reindex_p900_stuff_idx', 'Check stuff index was removed in test_reindex_p900'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p1000', 'test_reindex_p1000_stuff_idx', 'Check stuff index was removed in test_reindex_p1000'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p1100', 'test_reindex_p1100_stuff_idx', 'Check stuff index was removed in test_reindex_p1100'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p1200', 'test_reindex_p1200_stuff_idx', 'Check stuff index was removed in test_reindex_p1200'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p1300', 'test_reindex_p1300_stuff_idx', 'Check stuff index was removed in test_reindex_p1300'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p1400', 'test_reindex_p1400_stuff_idx', 'Check stuff index was removed in test_reindex_p1400'); SELECT has_index('partman_reindex_test', 'test_reindex_p0', 'test_reindex_p0_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p0'); SELECT has_index('partman_reindex_test', 'test_reindex_p100', 'test_reindex_p100_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p100'); SELECT has_index('partman_reindex_test', 'test_reindex_p200', 'test_reindex_p200_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p200'); SELECT has_index('partman_reindex_test', 'test_reindex_p300', 'test_reindex_p300_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p300'); SELECT has_index('partman_reindex_test', 'test_reindex_p400', 'test_reindex_p400_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p400'); SELECT has_index('partman_reindex_test', 'test_reindex_p500', 'test_reindex_p500_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p500'); SELECT has_index('partman_reindex_test', 'test_reindex_p600', 'test_reindex_p600_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p600'); SELECT has_index('partman_reindex_test', 'test_reindex_p700', 'test_reindex_p700_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p700'); SELECT has_index('partman_reindex_test', 'test_reindex_p800', 'test_reindex_p800_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p800'); SELECT has_index('partman_reindex_test', 'test_reindex_p900', 'test_reindex_p900_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p900'); SELECT has_index('partman_reindex_test', 'test_reindex_p1000', 'test_reindex_p1000_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p1000'); SELECT has_index('partman_reindex_test', 'test_reindex_p1100', 'test_reindex_p1100_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p1100'); SELECT has_index('partman_reindex_test', 'test_reindex_p1200', 'test_reindex_p1200_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p1200'); SELECT has_index('partman_reindex_test', 'test_reindex_p1300', 'test_reindex_p1300_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p1300'); SELECT has_index('partman_reindex_test', 'test_reindex_p1400', 'test_reindex_p1400_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p1400'); SELECT has_index('partman_reindex_test', 'test_reindex_p0', 'test_reindex_p0_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for morestuff index in test_reindex_p0'); SELECT has_index('partman_reindex_test', 'test_reindex_p100', 'test_reindex_p100_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p100'); SELECT has_index('partman_reindex_test', 'test_reindex_p200', 'test_reindex_p200_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p200'); SELECT has_index('partman_reindex_test', 'test_reindex_p300', 'test_reindex_p300_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p300'); SELECT has_index('partman_reindex_test', 'test_reindex_p400', 'test_reindex_p400_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p400'); SELECT has_index('partman_reindex_test', 'test_reindex_p500', 'test_reindex_p500_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p500'); SELECT has_index('partman_reindex_test', 'test_reindex_p600', 'test_reindex_p600_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p600'); SELECT has_index('partman_reindex_test', 'test_reindex_p700', 'test_reindex_p700_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p700'); SELECT has_index('partman_reindex_test', 'test_reindex_p800', 'test_reindex_p800_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p800'); SELECT has_index('partman_reindex_test', 'test_reindex_p900', 'test_reindex_p900_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p900'); SELECT has_index('partman_reindex_test', 'test_reindex_p1000', 'test_reindex_p1000_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p1000'); SELECT has_index('partman_reindex_test', 'test_reindex_p1100', 'test_reindex_p1100_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p1100'); SELECT has_index('partman_reindex_test', 'test_reindex_p1200', 'test_reindex_p1200_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p1200'); SELECT has_index('partman_reindex_test', 'test_reindex_p1300', 'test_reindex_p1300_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p1300'); SELECT has_index('partman_reindex_test', 'test_reindex_p1400', 'test_reindex_p1400_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p1400'); SELECT has_index('partman_reindex_test', 'test_reindex_p0', 'test_reindex_p0_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) index in test_reindex_p0'); SELECT has_index('partman_reindex_test', 'test_reindex_p100', 'test_reindex_p100_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p100'); SELECT has_index('partman_reindex_test', 'test_reindex_p200', 'test_reindex_p200_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p200'); SELECT has_index('partman_reindex_test', 'test_reindex_p300', 'test_reindex_p300_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p300'); SELECT has_index('partman_reindex_test', 'test_reindex_p400', 'test_reindex_p400_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p400'); SELECT has_index('partman_reindex_test', 'test_reindex_p500', 'test_reindex_p500_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p500'); SELECT has_index('partman_reindex_test', 'test_reindex_p600', 'test_reindex_p600_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p600'); SELECT has_index('partman_reindex_test', 'test_reindex_p700', 'test_reindex_p700_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p700'); SELECT has_index('partman_reindex_test', 'test_reindex_p800', 'test_reindex_p800_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p800'); SELECT has_index('partman_reindex_test', 'test_reindex_p900', 'test_reindex_p900_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p900'); SELECT has_index('partman_reindex_test', 'test_reindex_p1000', 'test_reindex_p1000_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p1000'); SELECT has_index('partman_reindex_test', 'test_reindex_p1100', 'test_reindex_p1100_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p1100'); SELECT has_index('partman_reindex_test', 'test_reindex_p1200', 'test_reindex_p1200_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p1200'); SELECT has_index('partman_reindex_test', 'test_reindex_p1300', 'test_reindex_p1300_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p1300'); SELECT has_index('partman_reindex_test', 'test_reindex_p1400', 'test_reindex_p1400_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p1400'); SELECT has_index('partman_reindex_test', 'test_reindex_p0', 'test_reindex_p0_idx1', ARRAY['upper(stuff)'], 'Check for upper(stuff) index in test_reindex_p0'); SELECT has_index('partman_reindex_test', 'test_reindex_p100', 'test_reindex_p100_idx1', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p100'); SELECT has_index('partman_reindex_test', 'test_reindex_p200', 'test_reindex_p200_idx1', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p200'); SELECT has_index('partman_reindex_test', 'test_reindex_p300', 'test_reindex_p300_idx1', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p300'); SELECT has_index('partman_reindex_test', 'test_reindex_p400', 'test_reindex_p400_idx1', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p400'); SELECT has_index('partman_reindex_test', 'test_reindex_p500', 'test_reindex_p500_idx1', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p500'); SELECT has_index('partman_reindex_test', 'test_reindex_p600', 'test_reindex_p600_idx1', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p600'); SELECT has_index('partman_reindex_test', 'test_reindex_p700', 'test_reindex_p700_idx1', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p700'); SELECT has_index('partman_reindex_test', 'test_reindex_p800', 'test_reindex_p800_idx1', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p800'); SELECT has_index('partman_reindex_test', 'test_reindex_p900', 'test_reindex_p900_idx1', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p900'); SELECT has_index('partman_reindex_test', 'test_reindex_p1000', 'test_reindex_p1000_idx1', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p1000'); SELECT has_index('partman_reindex_test', 'test_reindex_p1100', 'test_reindex_p1100_idx1', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p1100'); SELECT has_index('partman_reindex_test', 'test_reindex_p1200', 'test_reindex_p1200_idx1', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p1200'); SELECT has_index('partman_reindex_test', 'test_reindex_p1300', 'test_reindex_p1300_idx1', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p1300'); SELECT has_index('partman_reindex_test', 'test_reindex_p1400', 'test_reindex_p1400_idx1', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p1400'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p0', ARRAY['new_id'], 'Check for new primary key in test_reindex_p0'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p100', ARRAY['new_id'], 'Check for new primary key in test_reindex_p100'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p200', ARRAY['new_id'], 'Check for new primary key in test_reindex_p200'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p300', ARRAY['new_id'], 'Check for new primary key in test_reindex_p300'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p400', ARRAY['new_id'], 'Check for new primary key in test_reindex_p400'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p500', ARRAY['new_id'], 'Check for new primary key in test_reindex_p500'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p600', ARRAY['new_id'], 'Check for new primary key in test_reindex_p600'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p700', ARRAY['new_id'], 'Check for new primary key in test_reindex_p700'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p800', ARRAY['new_id'], 'Check for new primary key in test_reindex_p800'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p900', ARRAY['new_id'], 'Check for new primary key in test_reindex_p900'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p1000', ARRAY['new_id'], 'Check for new primary key in test_reindex_p1000'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p1100', ARRAY['new_id'], 'Check for new primary key in test_reindex_p1100'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p1200', ARRAY['new_id'], 'Check for new primary key in test_reindex_p1200'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p1300', ARRAY['new_id'], 'Check for new primary key in test_reindex_p1300'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p1400', ARRAY['new_id'], 'Check for new primary key in test_reindex_p1400'); SELECT diag('!!! If all tests have passed, you can now run 99-cleanup-reindex-test.sql to remove all reindexing test objects !!!'); SELECT * FROM finish(); pg_partman-4.6.0/test/test_reindex/99-cleanup-reindex-test.sql000066400000000000000000000005261412761706100243550ustar00rootroot00000000000000\set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true SELECT set_config('search_path','partman_reindex_test, partman, public',false); SELECT plan(1); SELECT undo_partition('partman_reindex_test.test_reindex', 20, p_keep_table := false); DROP SCHEMA IF EXISTS partman_reindex_test CASCADE; SELECT pass('Cleanup Done'); SELECT * FROM finish(); pg_partman-4.6.0/test/test_reindex_native/000077500000000000000000000000001412761706100207005ustar00rootroot00000000000000pg_partman-4.6.0/test/test_reindex_native/01-setup-tables-native.sql000066400000000000000000000153341412761706100255410ustar00rootroot00000000000000\set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true CREATE SCHEMA partman_reindex_test; SELECT set_config('search_path','partman_reindex_test, partman, public',false); DELETE FROM part_config WHERE parent_table = 'partman_reindex_test.test_reindex'; CREATE TABLE test_reindex (id bigint, stuff text, morestuff timestamptz default now()) PARTITION BY RANGE (id); CREATE TABLE template_test_reindex (LIKE test_reindex); ALTER TABLE template_test_reindex ADD CONSTRAINT test_reindex_id_pkey PRIMARY KEY (id); CREATE INDEX template_test_reindex_stuff_idx ON template_test_reindex (stuff); CREATE INDEX template_test_reindex_upper_stuff_idx ON template_test_reindex(upper(stuff)); SELECT plan(25); SELECT is_partitioned('partman_reindex_test', 'test_reindex', 'Check test_reindex exists and is partition parent'); SELECT has_table('partman_reindex_test', 'template_test_reindex', 'Check template table exists'); SELECT col_is_pk('partman_reindex_test', 'template_test_reindex', ARRAY['id'], 'Check for primary key in template_test_reindex'); SELECT has_index('partman_reindex_test', 'template_test_reindex', 'template_test_reindex_stuff_idx', ARRAY['stuff'], 'Check for stuff index in template_test_reindex'); SELECT has_index('partman_reindex_test', 'template_test_reindex', 'template_test_reindex_upper_stuff_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) index in template_test_reindex'); SELECT create_parent('partman_reindex_test.test_reindex', 'id', 'native', '100', p_template_table := 'partman_reindex_test.template_test_reindex'); INSERT INTO test_reindex VALUES (generate_series(1,499), 'stuff'||generate_series(1, 499)); -- Create a few more child tables to test now that there's data SELECT run_maintenance('partman_reindex_test.test_reindex'); SELECT has_table('partman_reindex_test', 'test_reindex_p0', 'Check test_reindex_p0 exists'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p0', ARRAY['id'], 'Check for primary key in test_reindex_p0'); SELECT has_index('partman_reindex_test', 'test_reindex_p0', 'test_reindex_p0_stuff_idx', ARRAY['stuff'], 'Check for stuff index in test_reindex_p0'); SELECT has_index('partman_reindex_test', 'test_reindex_p0', 'test_reindex_p0_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) index in test_reindex_p0'); SELECT has_table('partman_reindex_test', 'test_reindex_p100', 'Check test_reindex_p100 exists'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p100', ARRAY['id'], 'Check for primary key in test_reindex_p100'); SELECT has_index('partman_reindex_test', 'test_reindex_p100', 'test_reindex_p100_stuff_idx', ARRAY['stuff'], 'Check for stuff index in test_reindex_p100'); SELECT has_index('partman_reindex_test', 'test_reindex_p100', 'test_reindex_p100_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) index in test_reindex_p100'); SELECT has_table('partman_reindex_test', 'test_reindex_p200', 'Check test_reindex_p200 exists'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p200', ARRAY['id'], 'Check for primary key in test_reindex_p200'); SELECT has_index('partman_reindex_test', 'test_reindex_p200', 'test_reindex_p200_stuff_idx', ARRAY['stuff'], 'Check for stuff index in test_reindex_p200'); SELECT has_index('partman_reindex_test', 'test_reindex_p200', 'test_reindex_p200_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) index in test_reindex_p200'); SELECT has_table('partman_reindex_test', 'test_reindex_p300', 'Check test_reindex_p300 exists'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p300', ARRAY['id'], 'Check for primary key in test_reindex_p300'); SELECT has_index('partman_reindex_test', 'test_reindex_p300', 'test_reindex_p300_stuff_idx', ARRAY['stuff'], 'Check for stuff index in test_reindex_p300'); SELECT has_index('partman_reindex_test', 'test_reindex_p300', 'test_reindex_p300_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) index in test_reindex_p300'); SELECT has_table('partman_reindex_test', 'test_reindex_p400', 'Check test_reindex_p400 exists'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p400', ARRAY['id'], 'Check for primary key in test_reindex_p400'); SELECT has_index('partman_reindex_test', 'test_reindex_p400', 'test_reindex_p400_stuff_idx', ARRAY['stuff'], 'Check for stuff index in test_reindex_p400'); SELECT has_index('partman_reindex_test', 'test_reindex_p400', 'test_reindex_p400_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) index in test_reindex_p400'); /* SELECT has_table('partman_reindex_test', 'test_reindex_p1100', 'Check test_reindex_p1100 exists'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p1100', ARRAY['id'], 'Check for primary key in test_reindex_p1100'); SELECT has_index('partman_reindex_test', 'test_reindex_p1100', 'test_reindex_p1100_stuff_idx', ARRAY['stuff'], 'Check for stuff index in test_reindex_p1100'); SELECT has_index('partman_reindex_test', 'test_reindex_p1100', 'test_reindex_p1100_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) index in test_reindex_p1100'); SELECT has_table('partman_reindex_test', 'test_reindex_p1200', 'Check test_reindex_p1200 exists'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p1200', ARRAY['id'], 'Check for primary key in test_reindex_p1200'); SELECT has_index('partman_reindex_test', 'test_reindex_p1200', 'test_reindex_p1200_stuff_idx', ARRAY['stuff'], 'Check for stuff index in test_reindex_p1200'); SELECT has_index('partman_reindex_test', 'test_reindex_p1200', 'test_reindex_p1200_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) index in test_reindex_p1200'); SELECT has_table('partman_reindex_test', 'test_reindex_p1300', 'Check test_reindex_p1300 exists'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p1300', ARRAY['id'], 'Check for primary key in test_reindex_p1300'); SELECT has_index('partman_reindex_test', 'test_reindex_p1300', 'test_reindex_p1300_stuff_idx', ARRAY['stuff'], 'Check for stuff index in test_reindex_p1300'); SELECT has_index('partman_reindex_test', 'test_reindex_p1300', 'test_reindex_p1300_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) index in test_reindex_p1300'); SELECT has_table('partman_reindex_test', 'test_reindex_p1400', 'Check test_reindex_p1400 exists'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p1400', ARRAY['id'], 'Check for primary key in test_reindex_p1400'); SELECT has_index('partman_reindex_test', 'test_reindex_p1400', 'test_reindex_p1400_stuff_idx', ARRAY['stuff'], 'Check for stuff index in test_reindex_p1400'); SELECT has_index('partman_reindex_test', 'test_reindex_p1400', 'test_reindex_p1400_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) index in test_reindex_p1400'); SELECT partition_data_id('partman_reindex_test.test_reindex', p_batch_count := 20); */ SELECT diag('!!! Next run 02-change-index-native.sql !!!'); SELECT * FROM finish(); pg_partman-4.6.0/test/test_reindex_native/02-change-index-native.sql000066400000000000000000000031421412761706100254560ustar00rootroot00000000000000\set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true SELECT set_config('search_path','partman_reindex_test, partman, public',false); DROP INDEX template_test_reindex_stuff_idx; CREATE INDEX template_test_reindex_morestuff_idx ON template_test_reindex (morestuff); CREATE INDEX template_test_reindex_stuff_morestuff_idx ON template_test_reindex (stuff, morestuff); CREATE INDEX template_test_reindex_lower_stuff_idx ON template_test_reindex(lower(stuff)); ALTER TABLE test_reindex ADD new_id bigint; ALTER TABLE template_test_reindex ADD new_id bigint; UPDATE test_reindex SET new_id = id; ALTER TABLE template_test_reindex DROP CONSTRAINT test_reindex_id_pkey; ALTER TABLE template_test_reindex ADD CONSTRAINT test_reindex_new_id primary key (new_id); SELECT plan(3); SELECT hasnt_index('partman_reindex_test', 'template_test_reindex', 'template_test_reindex_stuff_idx', 'Check for stuff index in template_test_reindex'); SELECT has_index('partman_reindex_test', 'template_test_reindex', 'template_test_reindex_morestuff_idx', ARRAY['morestuff'], 'Check for stuff index in template_test_reindex'); SELECT col_is_pk('partman_reindex_test', 'template_test_reindex', ARRAY['new_id'], 'Check for new primary key in template_test_reindex'); SELECT diag('!!! Now run reapply_index.py on "partman_reindex_test.test_reindex" with the --primary option to apply the new indexes to all the children !!!'); SELECT diag('!!! After that completes, run 03-check-indexes.sql !!!'); SELECT diag('!!! You can set any options you''d like on the python script to test them. Re-run from test 01 to test different options !!! '); SELECT * FROM finish(); pg_partman-4.6.0/test/test_reindex_native/03-check-indexes-native.sql000066400000000000000000000217171412761706100256470ustar00rootroot00000000000000\set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true SELECT set_config('search_path','partman_reindex_test, partman, public',false); SELECT plan(54); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p0', 'test_reindex_p0_stuff_idx', 'Check stuff index was removed in test_reindex_p0'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p100', 'test_reindex_p100_stuff_idx', 'Check stuff index was removed in test_reindex_p100'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p200', 'test_reindex_p200_stuff_idx', 'Check stuff index was removed in test_reindex_p200'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p300', 'test_reindex_p300_stuff_idx', 'Check stuff index was removed in test_reindex_p300'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p400', 'test_reindex_p400_stuff_idx', 'Check stuff index was removed in test_reindex_p400'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p500', 'test_reindex_p500_stuff_idx', 'Check stuff index was removed in test_reindex_p500'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p600', 'test_reindex_p600_stuff_idx', 'Check stuff index was removed in test_reindex_p600'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p700', 'test_reindex_p700_stuff_idx', 'Check stuff index was removed in test_reindex_p700'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p800', 'test_reindex_p800_stuff_idx', 'Check stuff index was removed in test_reindex_p800'); SELECT has_index('partman_reindex_test', 'test_reindex_p0', 'test_reindex_p0_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p0'); SELECT has_index('partman_reindex_test', 'test_reindex_p100', 'test_reindex_p100_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p100'); SELECT has_index('partman_reindex_test', 'test_reindex_p200', 'test_reindex_p200_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p200'); SELECT has_index('partman_reindex_test', 'test_reindex_p300', 'test_reindex_p300_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p300'); SELECT has_index('partman_reindex_test', 'test_reindex_p400', 'test_reindex_p400_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p400'); SELECT has_index('partman_reindex_test', 'test_reindex_p500', 'test_reindex_p500_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p500'); SELECT has_index('partman_reindex_test', 'test_reindex_p600', 'test_reindex_p600_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p600'); SELECT has_index('partman_reindex_test', 'test_reindex_p700', 'test_reindex_p700_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p700'); SELECT has_index('partman_reindex_test', 'test_reindex_p800', 'test_reindex_p800_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p800'); SELECT has_index('partman_reindex_test', 'test_reindex_p0', 'test_reindex_p0_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for morestuff index in test_reindex_p0'); SELECT has_index('partman_reindex_test', 'test_reindex_p100', 'test_reindex_p100_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p100'); SELECT has_index('partman_reindex_test', 'test_reindex_p200', 'test_reindex_p200_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p200'); SELECT has_index('partman_reindex_test', 'test_reindex_p300', 'test_reindex_p300_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p300'); SELECT has_index('partman_reindex_test', 'test_reindex_p400', 'test_reindex_p400_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p400'); SELECT has_index('partman_reindex_test', 'test_reindex_p500', 'test_reindex_p500_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p500'); SELECT has_index('partman_reindex_test', 'test_reindex_p600', 'test_reindex_p600_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p600'); SELECT has_index('partman_reindex_test', 'test_reindex_p700', 'test_reindex_p700_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p700'); SELECT has_index('partman_reindex_test', 'test_reindex_p800', 'test_reindex_p800_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p800'); SELECT has_index('partman_reindex_test', 'test_reindex_p0', 'test_reindex_p0_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) index in test_reindex_p0'); SELECT has_index('partman_reindex_test', 'test_reindex_p100', 'test_reindex_p100_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p100'); SELECT has_index('partman_reindex_test', 'test_reindex_p200', 'test_reindex_p200_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p200'); SELECT has_index('partman_reindex_test', 'test_reindex_p300', 'test_reindex_p300_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p300'); SELECT has_index('partman_reindex_test', 'test_reindex_p400', 'test_reindex_p400_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p400'); SELECT has_index('partman_reindex_test', 'test_reindex_p500', 'test_reindex_p500_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p500'); SELECT has_index('partman_reindex_test', 'test_reindex_p600', 'test_reindex_p600_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p600'); SELECT has_index('partman_reindex_test', 'test_reindex_p700', 'test_reindex_p700_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p700'); SELECT has_index('partman_reindex_test', 'test_reindex_p800', 'test_reindex_p800_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p800'); SELECT has_index('partman_reindex_test', 'test_reindex_p0', 'test_reindex_p0_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) index in test_reindex_p0'); SELECT has_index('partman_reindex_test', 'test_reindex_p100', 'test_reindex_p100_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p100'); SELECT has_index('partman_reindex_test', 'test_reindex_p200', 'test_reindex_p200_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p200'); SELECT has_index('partman_reindex_test', 'test_reindex_p300', 'test_reindex_p300_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p300'); SELECT has_index('partman_reindex_test', 'test_reindex_p400', 'test_reindex_p400_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p400'); SELECT has_index('partman_reindex_test', 'test_reindex_p500', 'test_reindex_p500_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p500'); SELECT has_index('partman_reindex_test', 'test_reindex_p600', 'test_reindex_p600_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p600'); SELECT has_index('partman_reindex_test', 'test_reindex_p700', 'test_reindex_p700_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p700'); SELECT has_index('partman_reindex_test', 'test_reindex_p800', 'test_reindex_p800_upper_idx', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p800'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p0', ARRAY['new_id'], 'Check for new primary key in test_reindex_p0'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p100', ARRAY['new_id'], 'Check for new primary key in test_reindex_p100'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p200', ARRAY['new_id'], 'Check for new primary key in test_reindex_p200'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p300', ARRAY['new_id'], 'Check for new primary key in test_reindex_p300'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p400', ARRAY['new_id'], 'Check for new primary key in test_reindex_p400'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p500', ARRAY['new_id'], 'Check for new primary key in test_reindex_p500'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p600', ARRAY['new_id'], 'Check for new primary key in test_reindex_p600'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p700', ARRAY['new_id'], 'Check for new primary key in test_reindex_p700'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p800', ARRAY['new_id'], 'Check for new primary key in test_reindex_p800'); SELECT diag('!!! Now run reapply_index.py again with all the same arguments as last time but add --recreate_all !!!'); SELECT diag('!!! After that completes, run 04-check-indexes.sql !!!'); SELECT * FROM finish(); pg_partman-4.6.0/test/test_reindex_native/04-recreate_all_indexes-native.sql000066400000000000000000000215501412761706100272720ustar00rootroot00000000000000\set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true SELECT set_config('search_path','partman_reindex_test, partman, public',false); SELECT plan(54); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p0', 'test_reindex_p0_stuff_idx', 'Check stuff index was removed in test_reindex_p0'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p100', 'test_reindex_p100_stuff_idx', 'Check stuff index was removed in test_reindex_p100'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p200', 'test_reindex_p200_stuff_idx', 'Check stuff index was removed in test_reindex_p200'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p300', 'test_reindex_p300_stuff_idx', 'Check stuff index was removed in test_reindex_p300'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p400', 'test_reindex_p400_stuff_idx', 'Check stuff index was removed in test_reindex_p400'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p500', 'test_reindex_p500_stuff_idx', 'Check stuff index was removed in test_reindex_p500'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p600', 'test_reindex_p600_stuff_idx', 'Check stuff index was removed in test_reindex_p600'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p700', 'test_reindex_p700_stuff_idx', 'Check stuff index was removed in test_reindex_p700'); SELECT hasnt_index('partman_reindex_test', 'test_reindex_p800', 'test_reindex_p800_stuff_idx', 'Check stuff index was removed in test_reindex_p800'); SELECT has_index('partman_reindex_test', 'test_reindex_p0', 'test_reindex_p0_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p0'); SELECT has_index('partman_reindex_test', 'test_reindex_p100', 'test_reindex_p100_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p100'); SELECT has_index('partman_reindex_test', 'test_reindex_p200', 'test_reindex_p200_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p200'); SELECT has_index('partman_reindex_test', 'test_reindex_p300', 'test_reindex_p300_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p300'); SELECT has_index('partman_reindex_test', 'test_reindex_p400', 'test_reindex_p400_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p400'); SELECT has_index('partman_reindex_test', 'test_reindex_p500', 'test_reindex_p500_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p500'); SELECT has_index('partman_reindex_test', 'test_reindex_p600', 'test_reindex_p600_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p600'); SELECT has_index('partman_reindex_test', 'test_reindex_p700', 'test_reindex_p700_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p700'); SELECT has_index('partman_reindex_test', 'test_reindex_p800', 'test_reindex_p800_morestuff_idx', ARRAY['morestuff'], 'Check for morestuff index in test_reindex_p800'); SELECT has_index('partman_reindex_test', 'test_reindex_p0', 'test_reindex_p0_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for morestuff index in test_reindex_p0'); SELECT has_index('partman_reindex_test', 'test_reindex_p100', 'test_reindex_p100_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p100'); SELECT has_index('partman_reindex_test', 'test_reindex_p200', 'test_reindex_p200_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p200'); SELECT has_index('partman_reindex_test', 'test_reindex_p300', 'test_reindex_p300_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p300'); SELECT has_index('partman_reindex_test', 'test_reindex_p400', 'test_reindex_p400_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p400'); SELECT has_index('partman_reindex_test', 'test_reindex_p500', 'test_reindex_p500_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p500'); SELECT has_index('partman_reindex_test', 'test_reindex_p600', 'test_reindex_p600_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p600'); SELECT has_index('partman_reindex_test', 'test_reindex_p700', 'test_reindex_p700_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p700'); SELECT has_index('partman_reindex_test', 'test_reindex_p800', 'test_reindex_p800_stuff_morestuff_idx', ARRAY['stuff', 'morestuff'], 'Check for stuff,morestuff index in test_reindex_p800'); SELECT has_index('partman_reindex_test', 'test_reindex_p0', 'test_reindex_p0_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) index in test_reindex_p0'); SELECT has_index('partman_reindex_test', 'test_reindex_p100', 'test_reindex_p100_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p100'); SELECT has_index('partman_reindex_test', 'test_reindex_p200', 'test_reindex_p200_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p200'); SELECT has_index('partman_reindex_test', 'test_reindex_p300', 'test_reindex_p300_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p300'); SELECT has_index('partman_reindex_test', 'test_reindex_p400', 'test_reindex_p400_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p400'); SELECT has_index('partman_reindex_test', 'test_reindex_p500', 'test_reindex_p500_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p500'); SELECT has_index('partman_reindex_test', 'test_reindex_p600', 'test_reindex_p600_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p600'); SELECT has_index('partman_reindex_test', 'test_reindex_p700', 'test_reindex_p700_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p700'); SELECT has_index('partman_reindex_test', 'test_reindex_p800', 'test_reindex_p800_idx', ARRAY['lower(stuff)'], 'Check for lower(stuff) stuff index in test_reindex_p800'); SELECT has_index('partman_reindex_test', 'test_reindex_p0', 'test_reindex_p0_idx1', ARRAY['upper(stuff)'], 'Check for upper(stuff) index in test_reindex_p0'); SELECT has_index('partman_reindex_test', 'test_reindex_p100', 'test_reindex_p100_idx1', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p100'); SELECT has_index('partman_reindex_test', 'test_reindex_p200', 'test_reindex_p200_idx1', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p200'); SELECT has_index('partman_reindex_test', 'test_reindex_p300', 'test_reindex_p300_idx1', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p300'); SELECT has_index('partman_reindex_test', 'test_reindex_p400', 'test_reindex_p400_idx1', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p400'); SELECT has_index('partman_reindex_test', 'test_reindex_p500', 'test_reindex_p500_idx1', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p500'); SELECT has_index('partman_reindex_test', 'test_reindex_p600', 'test_reindex_p600_idx1', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p600'); SELECT has_index('partman_reindex_test', 'test_reindex_p700', 'test_reindex_p700_idx1', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p700'); SELECT has_index('partman_reindex_test', 'test_reindex_p800', 'test_reindex_p800_idx1', ARRAY['upper(stuff)'], 'Check for upper(stuff) stuff index in test_reindex_p800'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p0', ARRAY['new_id'], 'Check for new primary key in test_reindex_p0'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p100', ARRAY['new_id'], 'Check for new primary key in test_reindex_p100'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p200', ARRAY['new_id'], 'Check for new primary key in test_reindex_p200'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p300', ARRAY['new_id'], 'Check for new primary key in test_reindex_p300'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p400', ARRAY['new_id'], 'Check for new primary key in test_reindex_p400'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p500', ARRAY['new_id'], 'Check for new primary key in test_reindex_p500'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p600', ARRAY['new_id'], 'Check for new primary key in test_reindex_p600'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p700', ARRAY['new_id'], 'Check for new primary key in test_reindex_p700'); SELECT col_is_pk('partman_reindex_test', 'test_reindex_p800', ARRAY['new_id'], 'Check for new primary key in test_reindex_p800'); SELECT diag('!!! If all tests have passed, you can now run 99-cleanup-reindex-test.sql to remove all reindexing test objects !!!'); SELECT * FROM finish(); pg_partman-4.6.0/test/test_reindex_native/99-cleanup-reindex-test-native.sql000066400000000000000000000010341412761706100272020ustar00rootroot00000000000000\set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true SELECT set_config('search_path','partman_reindex_test, partman, public',false); SELECT plan(1); -- Have to create this table later due to columns changing during test CREATE TABLE IF NOT EXISTS undo_test_reindex (LIKE test_reindex); SELECT undo_partition('partman_reindex_test.test_reindex', 20, p_target_table := 'partman_reindex_test.undo_test_reindex', p_keep_table := false); DROP SCHEMA IF EXISTS partman_reindex_test CASCADE; SELECT pass('Cleanup Done'); SELECT * FROM finish(); pg_partman-4.6.0/test/test_upsert/000077500000000000000000000000001412761706100172165ustar00rootroot00000000000000pg_partman-4.6.0/test/test_upsert/test-id-time-subpart-upsert-do-nothing.sql000066400000000000000000001612751412761706100273220ustar00rootroot00000000000000-- ########## ID PARENT / TIME SUBPARENT TESTS ########## -- Additional test: UPSERT DO NOTHING \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(261); CREATE SCHEMA partman_test; CREATE TABLE partman_test.id_taptest_table (col1 int primary key, col2 text, col3 timestamptz NOT NULL DEFAULT now()); INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(1,9)); SELECT partman.create_parent('partman_test.id_taptest_table', 'col1', 'partman', '10', '{"col3"}', p_automatic_maintenance := 'on', p_jobmon := false, p_upsert := 'ON CONFLICT (col1) DO NOTHING'); SELECT has_table('partman_test', 'id_taptest_table_p0', 'Check id_taptest_table_p0 exists'); SELECT has_table('partman_test', 'id_taptest_table_p10', 'Check id_taptest_table_p10 exists'); SELECT has_table('partman_test', 'id_taptest_table_p20', 'Check id_taptest_table_p20 exists'); SELECT has_table('partman_test', 'id_taptest_table_p30', 'Check id_taptest_table_p30 exists'); SELECT has_table('partman_test', 'id_taptest_table_p40', 'Check id_taptest_table_p40 exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50', 'Check id_taptest_table_p50 doesn''t exists yet'); SELECT col_is_pk('partman_test', 'id_taptest_table_p0', ARRAY['col1'], 'Check for primary key in id_taptest_table_p0'); SELECT col_is_pk('partman_test', 'id_taptest_table_p10', ARRAY['col1'], 'Check for primary key in id_taptest_table_p10'); SELECT col_is_pk('partman_test', 'id_taptest_table_p20', ARRAY['col1'], 'Check for primary key in id_taptest_table_p20'); SELECT col_is_pk('partman_test', 'id_taptest_table_p30', ARRAY['col1'], 'Check for primary key in id_taptest_table_p30'); SELECT col_is_pk('partman_test', 'id_taptest_table_p40', ARRAY['col1'], 'Check for primary key in id_taptest_table_p40'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table'')::int', ARRAY[9], 'Check that partitioning function returns correct count of rows moved'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[9], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0', ARRAY[9], 'Check count from id_taptest_table_p0'); -- Check that upsert doesn't throw conflict INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(1,9)); SELECT pass('Upsert DO NOTHING passed test'); -- Create subpartition SELECT partman.create_sub_parent('partman_test.id_taptest_table', 'col3', 'partman', 'daily', p_upsert := 'ON CONFLICT (col1) DO NOTHING'); -- p0 SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in d_dynamic_table_p0_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); -- Move data to new subpartitions SELECT results_eq('SELECT partition_data_time(''partman_test.id_taptest_table_p0'', p_batch_count := 5)::int', ARRAY[9], 'Check that partitioning function returns correct count of rows moved'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had data moved to partition'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p0', 'Check that subparent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0', ARRAY[9], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY[9], 'Check count from id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check count from id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' (should be empty)'); -- Check that upsert doesn't throw conflict INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(1,9)); SELECT pass('Upsert DO NOTHING passed test'); -- p10 SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in d_dynamic_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10', 'Check that subparent table has had data moved to partition'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p10', 'Check count from parent table _p10 (should be empty)'); -- p20 SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in d_dynamic_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p20', 'Check that subparent table itself _p20 is empty'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p20', 'Check count from parent table _p20 (should be empty)'); -- p30 SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in d_dynamic_table_p30_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p30', 'Check that subparent table itself _p30 is empty'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p30', 'Check count from parent table _p30 (should be empty)'); -- p40 SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in d_dynamic_table_p40_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p40', 'Check that subparent table itself _p40 is empty'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p40', 'Check count from parent table _p40 (should be empty)'); -- insertion round 2 INSERT INTO partman_test.id_taptest_table (col1, col3) VALUES (generate_series(10,20), CURRENT_TIMESTAMP+'1 day'::interval); SELECT run_maintenance(); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had data moved to partition'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p0', 'Check that subparent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0', ARRAY[9], 'Check count from parent table partman_test.id_taptest_table_p0'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY[9], 'Check count from id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10', 'Check that subparent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10', ARRAY[10], 'Check count from parent table partman_test.id_taptest_table_p10'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY[10], 'Check count from id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p20', 'Check that subparent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20', ARRAY[1], 'Check count from parent table partman_test.id_taptest_table_p20'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY[1], 'Check count from id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); -- Check that upsert doesn't throw conflict INSERT INTO partman_test.id_taptest_table (col1, col3) VALUES (generate_series(10,20), CURRENT_TIMESTAMP+'1 day'::interval); SELECT pass('Upsert DO NOTHING passed test (10-20)'); -- p50 SELECT has_table('partman_test', 'id_taptest_table_p50', 'Check id_taptest_table_p50 exists'); SELECT col_is_pk('partman_test', 'id_taptest_table_p50', ARRAY['col1'], 'Check for primary key in id_taptest_table_p50'); SELECT has_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT has_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in d_dynamic_table_p50_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had data moved to partition'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p50', 'Check that subparent table itself _p50 is empty'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p50', 'Check count from parent table _p50 (should be empty)'); -- p60 SELECT has_table('partman_test', 'id_taptest_table_p60', 'Check id_taptest_table_p60 exists'); SELECT col_is_pk('partman_test', 'id_taptest_table_p60', ARRAY['col1'], 'Check for primary key in id_taptest_table_p60'); SELECT has_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT has_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'1 day'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in d_dynamic_table_p60_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had data moved to partition'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p60', 'Check that subparent table itself _p60 is empty'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p60', 'Check count from parent table _p60 (should be empty)'); SELECT hasnt_table('partman_test', 'id_taptest_table_p70', 'Check id_taptest_table_p70 doesn''t exists yet'); -- make sure new data is where it should be SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table_p10', 'Check count from only parent table _p10 (should be empty)'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10', ARRAY[10], 'Check count from subparent table id_taptest_table_p10'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY[10], 'Check count from id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20', ARRAY[1], 'Check count from subparent table id_taptest_table_p20'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), ARRAY[1], 'Check count from id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')); -- Ensure time partitioning works for all sub partitions UPDATE part_config SET premake = 5, optimize_trigger = 5 WHERE parent_table ~ 'partman_test.id_taptest_table_p' AND partition_type = 'partman'; SELECT run_maintenance(); -- Check for new time sub-partitions -- Time data only exists for now() in _p0, so only one additional table will be created SELECT has_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); -- Time data exists for now+1 day in p10, so 2 additional tables will be created SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); -- Time data exists for now+1 day in p20, so 2 additional tables will be created SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT has_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')||' exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'7 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT col_is_pk('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')); SELECT col_is_pk('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD'), ARRAY['col1'], 'Check for primary key in id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'6 days'::interval, 'YYYY_MM_DD')); -- No time data has been inserted for p30 and higher, so no more partitions should be created for them SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); -- Test dropping without retention set SELECT drop_partition_time ('partman_test.id_taptest_table_p0', '2 days', p_keep_table := false); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); UPDATE part_config SET retention = '10', retention_keep_table = false WHERE parent_table = 'partman_test.id_taptest_table'; UPDATE part_config SET retention = '2 days', retention_keep_table = false WHERE parent_table = 'partman_test.id_taptest_table_p0'; UPDATE part_config SET retention = '2 days', retention_keep_table = false WHERE parent_table = 'partman_test.id_taptest_table_p10'; UPDATE part_config SET retention = '2 days', retention_keep_table = false WHERE parent_table = 'partman_test.id_taptest_table_p20'; UPDATE part_config SET retention = '2 days', retention_keep_table = false WHERE parent_table = 'partman_test.id_taptest_table_p30'; UPDATE part_config SET retention = '2 days', retention_keep_table = false WHERE parent_table = 'partman_test.id_taptest_table_p40'; UPDATE part_config SET retention = '2 days', retention_keep_table = false WHERE parent_table = 'partman_test.id_taptest_table_p50'; UPDATE part_config SET retention = '2 days', retention_keep_table = false WHERE parent_table = 'partman_test.id_taptest_table_p60'; -- Test dropping with it set SELECT run_maintenance(); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p0_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT undo_partition('partman_test.id_taptest_table_p10', 20, p_keep_table := false); SELECT undo_partition('partman_test.id_taptest_table_p20', 20, p_keep_table := false); SELECT undo_partition('partman_test.id_taptest_table_p30', 20, p_keep_table := false); SELECT undo_partition('partman_test.id_taptest_table_p40', 20, p_keep_table := false); SELECT undo_partition('partman_test.id_taptest_table_p50', 20, p_keep_table := false); SELECT undo_partition('partman_test.id_taptest_table_p60', 20, p_keep_table := false); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p10_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p20_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p30_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p40_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p50_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'1 day'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'3 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'4 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP+'5 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'1 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD'), 'Check id_taptest_table_p60_p'||to_char(CURRENT_TIMESTAMP-'2 days'::interval, 'YYYY_MM_DD')||' does not exist'); SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.id_taptest_table_p0''', 'Check that partman_test.id_taptest_table_p0 was removed from part_config'); SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.id_taptest_table_p10''', 'Check that partman_test.id_taptest_table_p10 was removed from part_config'); SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.id_taptest_table_p20''', 'Check that partman_test.id_taptest_table_p20 was removed from part_config'); SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.id_taptest_table_p30''', 'Check that partman_test.id_taptest_table_p30 was removed from part_config'); SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.id_taptest_table_p40''', 'Check that partman_test.id_taptest_table_p40 was removed from part_config'); SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.id_taptest_table_p50''', 'Check that partman_test.id_taptest_table_p50 was removed from part_config'); SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.id_taptest_table_p60''', 'Check that partman_test.id_taptest_table_p60 was removed from part_config'); -- Check top parent is still empty SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that top parent table has not had any data moved to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p10', ARRAY[10], 'Check count from subparent table'); -- TODO Not returning result that pgtap can test. Unsure why. --SELECT results_eq('SELECT undo_partition(''partman_test.id_taptest_table'', 20, p_keep_table := false);', ARRAY[11], 'Check undo partition_id returns correct number of rows'); SELECT undo_partition('partman_test.id_taptest_table', 20, p_keep_table := false); SELECT hasnt_table('partman_test', 'id_taptest_table_p0', 'Check id_taptest_table_p0 does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p10', 'Check id_taptest_table_p10 does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p20', 'Check id_taptest_table_p20 does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p30', 'Check id_taptest_table_p30 does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p40', 'Check id_taptest_table_p40 does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p50', 'Check id_taptest_table_p50 does not exist'); SELECT hasnt_table('partman_test', 'id_taptest_table_p60', 'Check id_taptest_table_p60 does not exist'); SELECT is_empty('SELECT parent_table from part_config where parent_table = ''partman_test.id_taptest_table''', 'Check that partman_test.id_taptest_table was removed from part_config'); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table', ARRAY[11], 'Check count from final unpartitioned table'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test_upsert/test-id-upsert-do-something.sql000066400000000000000000000556611412761706100252400ustar00rootroot00000000000000-- ########## ID TESTS ########## -- Additional tests: turn off pg_jobmon logging, UNLOGGED, larger than necessary p_batch_count to partition_data_id(), retention, PUBLIC role, upsert with ON CONFLICT DO UPDATE \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(113); CREATE SCHEMA partman_test; CREATE SCHEMA partman_retention_test; CREATE ROLE partman_basic; CREATE ROLE partman_revoke; CREATE ROLE partman_owner; CREATE UNLOGGED TABLE partman_test.id_taptest_table ( col1 bigint primary key , col2 text not null default 'stuff' , col3 timestamptz DEFAULT now()); INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(3000000001,3000000009)); GRANT SELECT,INSERT,UPDATE ON partman_test.id_taptest_table TO partman_basic, PUBLIC; GRANT ALL ON partman_test.id_taptest_table TO partman_revoke; SELECT results_eq('SELECT create_parent(''partman_test.id_taptest_table'', ''col1'', ''partman'', ''10'', p_jobmon := false, p_upsert :=''ON CONFLICT (col1) DO UPDATE SET col2=EXCLUDED.col2, col3=EXCLUDED.col3'')::text', ARRAY['true'], 'Check that create_parent() returns true'); SELECT has_table('partman_test', 'id_taptest_table_p3000000000', 'Check id_taptest_table_p3000000000 exists'); SELECT has_table('partman_test', 'id_taptest_table_p3000000010', 'Check id_taptest_table_p3000000010 exists'); SELECT has_table('partman_test', 'id_taptest_table_p3000000020', 'Check id_taptest_table_p3000000020 exists'); SELECT has_table('partman_test', 'id_taptest_table_p3000000030', 'Check id_taptest_table_p3000000030 exists'); SELECT has_table('partman_test', 'id_taptest_table_p3000000040', 'Check id_taptest_table_p3000000040 exists'); SELECT hasnt_table('partman_test', 'id_taptest_table_p3000000050', 'Check id_taptest_table_p3000000050 doesn''t exists yet'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000000', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000000'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000010', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000010'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000020', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000020'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000030', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000030'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000040', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000040'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000000', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000000'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000010', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000010'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000020', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000020'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000030', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000030'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000040', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000040'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000000', 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of id_taptest_table_p3000000000'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000010', 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of id_taptest_table_p3000000010'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000020', 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of id_taptest_table_p3000000020'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000030', 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of id_taptest_table_p3000000030'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000040', 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of id_taptest_table_p3000000040'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table''::regclass', ARRAY['u'], 'Check that parent table is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p3000000000''::regclass', ARRAY['u'], 'Check that id_taptest_table_p3000000000 is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p3000000010''::regclass', ARRAY['u'], 'Check that id_taptest_table_p3000000010 is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p3000000020''::regclass', ARRAY['u'], 'Check that id_taptest_table_p3000000020 is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p3000000030''::regclass', ARRAY['u'], 'Check that id_taptest_table_p3000000030 is unlogged'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p3000000040''::regclass', ARRAY['u'], 'Check that id_taptest_table_p3000000040 is unlogged'); SELECT results_eq('SELECT partition_data_id(''partman_test.id_taptest_table'', p_batch_count := 5)::int', ARRAY[9], 'Check that partitioning function returns correct count of rows moved'); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had data moved to partition'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[9], 'Check count from parent table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p3000000000', ARRAY[9], 'Check count from id_taptest_table_p3000000000'); -- INSERT original id values again and check their col2 values INSERT INTO partman_test.id_taptest_table (col1, col2) VALUES (generate_series(3000000001,3000000009), 'stuff_upserted'); SELECT partman.run_maintenance(); SELECT results_eq('SELECT col1, col2 FROM partman_test.id_taptest_table', $$VALUES (3000000001, 'stuff_upserted'), (3000000002, 'stuff_upserted'), (3000000003, 'stuff_upserted'), (3000000004, 'stuff_upserted'), (3000000005, 'stuff_upserted'), (3000000006, 'stuff_upserted'), (3000000007, 'stuff_upserted'), (3000000008, 'stuff_upserted'), (3000000009, 'stuff_upserted')$$, 'Checking values after upsert trigger'); REVOKE INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON partman_test.id_taptest_table FROM partman_revoke, PUBLIC; INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(3000000010,3000000025)); SELECT partman.run_maintenance(); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p3000000010', ARRAY[10], 'Check count from id_taptest_table_p3000000010'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p3000000020', ARRAY[6], 'Check count from id_taptest_table_p3000000020'); SELECT has_table('partman_test', 'id_taptest_table_p3000000050', 'Check id_taptest_table_p3000000050 exists'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p3000000050''::regclass', ARRAY['u'], 'Check that id_taptest_table_p3000000050 is unlogged'); SELECT has_table('partman_test', 'id_taptest_table_p3000000060', 'Check id_taptest_table_p3000000060 exists'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p3000000060''::regclass', ARRAY['u'], 'Check that id_taptest_table_p3000000060 is unlogged'); SELECT hasnt_table('partman_test', 'id_taptest_table_p3000000070', 'Check id_taptest_table_p3000000070 doesn''t exists yet'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000050', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000050'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000060', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000060'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000000', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000000'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000010', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000010'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000020', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000020'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000030', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000030'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000040', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000040'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000050', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000050'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000060', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000060'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000050', 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of id_taptest_table_p3000000050'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000060', 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of id_taptest_table_p3000000060'); GRANT DELETE ON partman_test.id_taptest_table TO partman_basic; REVOKE ALL ON partman_test.id_taptest_table FROM partman_revoke, PUBLIC; ALTER TABLE partman_test.id_taptest_table OWNER TO partman_owner; INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(3000000026,3000000038)); SELECT partman.run_maintenance(); SELECT is_empty('SELECT * FROM ONLY partman_test.id_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table', ARRAY[38], 'Check count from id_taptest_table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p3000000020', ARRAY[10], 'Check count from id_taptest_table_p3000000020'); SELECT results_eq('SELECT count(*)::int FROM partman_test.id_taptest_table_p3000000030', ARRAY[9], 'Check count from id_taptest_table_p3000000030'); SELECT has_table('partman_test', 'id_taptest_table_p3000000070', 'Check id_taptest_table_p3000000070 exists'); SELECT results_eq('SELECT relpersistence::text FROM pg_catalog.pg_class WHERE oid::regclass = ''partman_test.id_taptest_table_p3000000070''::regclass', ARRAY['u'], 'Check that id_taptest_table_p3000000070 is unlogged'); SELECT hasnt_table('partman_test', 'id_taptest_table_p3000000080', 'Check id_taptest_table_p3000000080 doesn''t exists yet'); SELECT col_is_pk('partman_test', 'id_taptest_table_p3000000070', ARRAY['col1'], 'Check for primary key in id_taptest_table_p3000000070'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000000', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000000'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000010', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000010'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000020', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000020'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000030', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000030'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000040', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000040'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000050', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000050'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000050', 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of id_taptest_table_p3000000050'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000060', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000060'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000060', 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of id_taptest_table_p3000000060'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000070', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p3000000070'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p3000000070', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p3000000070'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000070', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p3000000070'); INSERT INTO partman_test.id_taptest_table (col1, col2) VALUES (generate_series(3000000026,3000000038), 'stuff_upserted'); SELECT partman.run_maintenance(); SELECT results_eq('SELECT col1, col2 FROM partman_test.id_taptest_table', $$VALUES (3000000001, 'stuff_upserted'), (3000000002, 'stuff_upserted'), (3000000003, 'stuff_upserted'), (3000000004, 'stuff_upserted'), (3000000005, 'stuff_upserted'), (3000000006, 'stuff_upserted'), (3000000007, 'stuff_upserted'), (3000000008, 'stuff_upserted'), (3000000009, 'stuff_upserted'), (3000000010, 'stuff'), (3000000011, 'stuff'), (3000000012, 'stuff'), (3000000013, 'stuff'), (3000000014, 'stuff'), (3000000015, 'stuff'), (3000000016, 'stuff'), (3000000017, 'stuff'), (3000000018, 'stuff'), (3000000019, 'stuff'), (3000000020, 'stuff'), (3000000021, 'stuff'), (3000000022, 'stuff'), (3000000023, 'stuff'), (3000000024, 'stuff'), (3000000025, 'stuff'), (3000000026, 'stuff_upserted'), (3000000027, 'stuff_upserted'), (3000000028, 'stuff_upserted'), (3000000029, 'stuff_upserted'), (3000000030, 'stuff_upserted'), (3000000031, 'stuff_upserted'), (3000000032, 'stuff_upserted'), (3000000033, 'stuff_upserted'), (3000000034, 'stuff_upserted'), (3000000035, 'stuff_upserted'), (3000000036, 'stuff_upserted'), (3000000037, 'stuff_upserted'), (3000000038, 'stuff_upserted')$$, 'Checking values after upsert trigger'); INSERT INTO partman_test.id_taptest_table (col1) VALUES (generate_series(3000000200,3000000210)); SELECT partman.run_maintenance(); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT reapply_privileges('partman_test.id_taptest_table'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000000', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p3000000000'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000010', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p3000000010'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000020', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p3000000020'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000030', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p3000000030'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000040', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p3000000040'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000050', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p3000000050'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000060', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p3000000060'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000070', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE','DELETE'], 'Check partman_basic privileges of id_taptest_table_p3000000070'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000000', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p3000000000'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000010', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p3000000010'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000020', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p3000000020'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000030', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p3000000030'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000040', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p3000000040'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000050', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p3000000050'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000060', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p3000000060'); SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000070', 'partman_revoke', '{}'::text[], 'Check partman_revoke has no privileges on id_taptest_table_p3000000070'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p3000000000', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p3000000000'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p3000000010', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p3000000010'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p3000000020', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p3000000020'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p3000000030', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p3000000030'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p3000000040', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p3000000040'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p3000000050', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p3000000050'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p3000000060', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p3000000060'); SELECT table_owner_is ('partman_test', 'id_taptest_table_p3000000070', 'partman_owner', 'Check that ownership change worked for id_taptest_table_p3000000070'); -- Max value is 38 above SELECT drop_partition_id('partman_test.id_taptest_table', '20', p_keep_table := false); SELECT hasnt_table('partman_test', 'id_taptest_table_p3000000000', 'Check id_taptest_table_p3000000000 doesn''t exists anymore'); UPDATE part_config SET retention = '10' WHERE parent_table = 'partman_test.id_taptest_table'; SELECT drop_partition_id('partman_test.id_taptest_table', p_retention_schema := 'partman_retention_test'); SELECT hasnt_table('partman_test', 'id_taptest_table_p3000000010', 'Check id_taptest_table_p3000000010 doesn''t exists anymore'); SELECT has_table('partman_retention_test', 'id_taptest_table_p3000000010', 'Check id_taptest_table_p3000000010 got moved to new schema'); SELECT undo_partition('partman_test.id_taptest_table', p_keep_table := false); SELECT hasnt_table('partman_test', 'id_taptest_table_p3000000020', 'Check id_taptest_table_p3000000020 does not exist'); -- Test keeping the reset of the tables SELECT undo_partition('partman_test.id_taptest_table', 10); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.id_taptest_table', ARRAY[30], 'Check count from parent table after undo'); SELECT has_table('partman_test', 'id_taptest_table_p3000000030', 'Check id_taptest_table_p3000000030 still exists'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p3000000030', 'Check child table had its data removed id_taptest_table_p3000000030'); SELECT has_table('partman_test', 'id_taptest_table_p3000000040', 'Check id_taptest_table_p3000000040 still exists'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p3000000040', 'Check child table had its data removed id_taptest_table_p3000000040'); SELECT has_table('partman_test', 'id_taptest_table_p3000000050', 'Check id_taptest_table_p3000000050 still exists'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p3000000050', 'Check child table had its data removed id_taptest_table_p3000000050'); SELECT has_table('partman_test', 'id_taptest_table_p3000000060', 'Check id_taptest_table_p3000000060 still exists'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p3000000060', 'Check child table had its data removed id_taptest_table_p3000000060'); SELECT has_table('partman_test', 'id_taptest_table_p3000000070', 'Check id_taptest_table_p3000000070 still exists'); SELECT is_empty('SELECT * FROM partman_test.id_taptest_table_p3000000070', 'Check child table had its data removed id_taptest_table_p3000000070'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/test/test_upsert/test-time-hourly-upsert-do-nothing.sql000066400000000000000000001200331412761706100265550ustar00rootroot00000000000000-- ########## TIME HOURLY TESTS ########## -- Additional tests: Undo partitioning but keep tables. Timestamp without timezone. Upsert DO NOTHING. \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true BEGIN; SELECT set_config('search_path','partman, public',false); SELECT plan(123); CREATE SCHEMA partman_test; CREATE ROLE partman_basic; CREATE ROLE partman_revoke; CREATE ROLE partman_owner; CREATE TABLE partman_test.time_taptest_table (col1 int primary key, col2 text, col3 timestamp NOT NULL DEFAULT now()); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(1,10), CURRENT_TIMESTAMP); GRANT SELECT,INSERT,UPDATE ON partman_test.time_taptest_table TO partman_basic; GRANT ALL ON partman_test.time_taptest_table TO partman_revoke; SELECT create_parent('partman_test.time_taptest_table', 'col3', 'partman', 'hourly', p_upsert := 'ON CONFLICT (col1) DO NOTHING'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'5 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'5 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT results_eq('SELECT partition_data_time(''partman_test.time_taptest_table'')::int', ARRAY[10], 'Check that partitioning function returns correct count of rows moved'); 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(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI')); REVOKE INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON partman_test.time_taptest_table FROM partman_revoke; INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '1 hour'::interval); --INSERT again to check that no errors are raised and nothing is done INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(11,20), CURRENT_TIMESTAMP + '1 hour'::interval); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '2 hours'::interval); --INSERT again to check that no errors are raised and nothing is done INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(21,25), CURRENT_TIMESTAMP + '2 hours'::interval); SELECT is_empty('SELECT * FROM ONLY partman_test.time_taptest_table', 'Check that parent table has had no data inserted to it'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table', ARRAY[25], 'Check count from time_taptest_table'); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY[10], 'Check count from time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI')); SELECT results_eq('SELECT count(*)::int FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY[5], 'Check count from time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI')); UPDATE part_config SET premake = 5, optimize_trigger = 5 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', ARRAY['SELECT'], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI')); GRANT DELETE ON partman_test.time_taptest_table TO partman_basic; REVOKE ALL ON partman_test.time_taptest_table FROM partman_revoke; ALTER TABLE partman_test.time_taptest_table OWNER TO partman_owner; UPDATE part_config SET premake = 6, optimize_trigger = 6 WHERE parent_table = 'partman_test.time_taptest_table'; SELECT run_maintenance(); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT hasnt_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'9 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'9 hours'::interval, 'YYYY_MM_DD_HH24MI')||' exists'); SELECT col_is_pk('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), ARRAY['col1'], 'Check for primary key in time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')); INSERT INTO partman_test.time_taptest_table (col1, col3) VALUES (generate_series(200,210), CURRENT_TIMESTAMP + '20 hours'::interval); SELECT results_eq('SELECT count(*)::int FROM ONLY partman_test.time_taptest_table', ARRAY[11], 'Check that data outside trigger scope goes to parent'); SELECT reapply_privileges('partman_test.time_taptest_table'); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_basic', ARRAY['SELECT','INSERT','UPDATE', 'DELETE'], 'Check partman_basic privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_privs_are('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_revoke', '{}'::text[], 'Check partman_revoke privileges of time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP), 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT table_owner_is ('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'partman_owner', 'Check that ownership change worked for time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')); SELECT undo_partition('partman_test.time_taptest_table', 20); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'1 hour'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'2 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'3 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)-'4 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'1 hour'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'2 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'3 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'4 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'5 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'6 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'7 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT has_table('partman_test', 'time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')||' still exists'); SELECT is_empty('SELECT * FROM partman_test.time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI'), 'Check time_taptest_table_p'||to_char(date_trunc('hour', CURRENT_TIMESTAMP)+'8 hours'::interval, 'YYYY_MM_DD_HH24MI')||' is empty'); SELECT * FROM finish(); ROLLBACK; pg_partman-4.6.0/updates/000077500000000000000000000000001412761706100153235ustar00rootroot00000000000000pg_partman-4.6.0/updates/pg_partman--0.1.0--0.1.1.sql000066400000000000000000000054471412761706100214250ustar00rootroot00000000000000-- Only re-create partition functions if a new partition is made. CREATE OR REPLACE FUNCTION run_maintenance() RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_datetime_string text; v_current_partition_timestamp timestamp; v_last_partition_timestamp timestamp; v_premade_count int; v_row record; v_sql text; BEGIN v_sql := 'SELECT parent_table , type , part_interval::interval , control , last_partition FROM @extschema@.part_config where type = ''time-static'' or type = ''time-dynamic'''; FOR v_row IN SELECT parent_table , type , part_interval::interval , control , premake , datetime_string , last_partition FROM @extschema@.part_config WHERE type = 'time-static' OR type = 'time-dynamic' LOOP CASE WHEN v_row.part_interval = '15 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_row.part_interval = '30 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_row.part_interval = '1 hour' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '1 day' THEN v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '1 week' THEN v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '1 month' THEN v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '1 year' THEN v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; v_last_partition_timestamp := to_timestamp(substring(v_row.last_partition from char_length(v_row.parent_table||'_p')+1), v_row.datetime_string); -- Check and see how many premade partitions there are. If it's less than premake in config table, make another v_premade_count = EXTRACT('epoch' FROM (v_last_partition_timestamp - v_current_partition_timestamp)::interval) / EXTRACT('epoch' FROM v_row.part_interval::interval); IF v_premade_count < v_row.premake THEN RAISE NOTICE 'Creating next partition'; EXECUTE 'SELECT @extschema@.create_next_time_partition('||quote_literal(v_row.parent_table)||')'; IF v_row.type = 'time-static' THEN EXECUTE 'SELECT @extschema@.create_time_function('||quote_literal(v_row.parent_table)||')'; END IF; END IF; END LOOP; -- end of main loop END $$; pg_partman-4.6.0/updates/pg_partman--0.1.1--0.1.2.sql000066400000000000000000001066661412761706100214340ustar00rootroot00000000000000-- Added support for quarterly time partitioning (trickier than it first appeared) -- Fixed bug in run_maintenance() that would give an invalid cast to integer error. -- Fixed some calls to pg_jobmon that were outside the checks to see if it's actually installed -- Properly reset search path back to original before partman functions were run if pg_jobmon is being used -- Changed the default premake to 4 instead of 3. This will cause pg_jobmon's default monitoring for 3 consecutive failing jobs to trigger an before the last premade partition is used up. -- Added optional jobmon logging to run_maintenance() so that if it fails, pg_jobmon can notify that maintenance didn't work. ALTER TABLE @extschema@.part_config ALTER premake SET DEFAULT 4; CREATE OR REPLACE FUNCTION create_parent(p_parent_table text, p_control text, p_type @extschema@.partition_type, p_interval text, p_premake int DEFAULT 4, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_current_id bigint; v_datetime_string text; v_id_interval bigint; v_job_id bigint; v_jobmon_schema text; v_last_partition_name text; v_old_search_path text; v_partition_time timestamp[]; v_partition_id bigint[]; v_max bigint; v_starting_partition_id bigint; v_step_id bigint; v_tablename text; v_time_interval interval; BEGIN SELECT tablename INTO v_tablename FROM pg_tables WHERE schemaname || '.' || tablename = p_parent_table; IF v_tablename IS NULL THEN RAISE EXCEPTION 'Please create given parent table first: %', p_parent_table; END IF; EXECUTE 'LOCK TABLE '||p_parent_table||' IN ACCESS EXCLUSIVE MODE'; SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN SETUP PARENT: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating initial partitions on new parent table: '||p_parent_table); END IF; CASE WHEN p_interval = 'yearly' THEN v_time_interval = '1 year'; v_datetime_string := 'YYYY'; WHEN p_interval = 'quarterly' THEN v_time_interval = '3 months'; v_datetime_string = 'YYYY"q"Q'; WHEN p_interval = 'monthly' THEN v_time_interval = '1 month'; v_datetime_string := 'YYYY_MM'; WHEN p_interval = 'weekly' THEN v_time_interval = '1 week'; v_datetime_string := 'IYYY"w"IW'; WHEN p_interval = 'daily' THEN v_time_interval = '1 day'; v_datetime_string := 'YYYY_MM_DD'; WHEN p_interval = 'hourly' THEN v_time_interval = '1 hour'; v_datetime_string := 'YYYY_MM_DD_HH24MI'; WHEN p_interval = 'half-hour' THEN v_time_interval = '30 mins'; v_datetime_string := 'YYYY_MM_DD_HH24MI'; WHEN p_interval = 'quarter-hour' THEN v_time_interval = '15 mins'; v_datetime_string := 'YYYY_MM_DD_HH24MI'; ELSE IF p_type = 'id-static' OR p_type = 'id-dynamic' THEN v_id_interval := p_interval::bigint; ELSE RAISE EXCEPTION 'Invalid interval for time based partitioning: %', p_interval; END IF; END CASE; IF p_type = 'time-static' OR p_type = 'time-dynamic' THEN FOR i IN 0..p_premake LOOP v_partition_time := array_append(v_partition_time, quote_literal(CURRENT_TIMESTAMP + (v_time_interval*i))::timestamp); END LOOP; EXECUTE 'SELECT @extschema@.create_time_partition('||quote_literal(p_parent_table)||','||quote_literal(p_control)||',' ||quote_literal(v_time_interval)||','||quote_literal(v_datetime_string)||','||quote_literal(v_partition_time)||')' INTO v_last_partition_name; INSERT INTO @extschema@.part_config (parent_table, type, part_interval, control, premake, datetime_string, last_partition) VALUES (p_parent_table, p_type, v_time_interval, p_control, p_premake, v_datetime_string, v_last_partition_name); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Time partitions premade: '||p_premake); END IF; END IF; IF p_type = 'id-static' OR p_type = 'id-dynamic' THEN -- If there is already data, start partitioning with the highest current value EXECUTE 'SELECT COALESCE(max('||p_control||')::bigint, 0) FROM '||p_parent_table||' LIMIT 1' INTO v_max; v_starting_partition_id := v_max - (v_max % v_id_interval); FOR i IN 0..p_premake LOOP v_partition_id = array_append(v_partition_id, (v_id_interval*i)+v_starting_partition_id); END LOOP; EXECUTE 'SELECT @extschema@.create_id_partition('||quote_literal(p_parent_table)||','||quote_literal(p_control)||',' ||v_id_interval||','||quote_literal(v_partition_id)||')' INTO v_last_partition_name; INSERT INTO @extschema@.part_config (parent_table, type, part_interval, control, premake, last_partition) VALUES (p_parent_table, p_type, v_id_interval, p_control, p_premake, v_last_partition_name); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'ID partitions premade: '||p_premake); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating partition function'); END IF; IF p_type = 'time-static' OR p_type = 'time-dynamic' THEN EXECUTE 'SELECT @extschema@.create_time_function('||quote_literal(p_parent_table)||')'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Time function created'); END IF; ELSIF p_type = 'id-static' OR p_type = 'id-dynamic' THEN v_current_id := COALESCE(v_max, 0); EXECUTE 'SELECT @extschema@.create_id_function('||quote_literal(p_parent_table)||','||v_current_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; EXECUTE 'SELECT @extschema@.create_trigger('||quote_literal(p_parent_table)||')'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN CREATE PARENT: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition creation for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'BAD', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; CREATE OR REPLACE FUNCTION create_time_partition (p_parent_table text, p_control text, p_interval interval, p_datetime_string text, p_partition_times timestamp[]) RETURNS text LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_job_id bigint; v_jobmon_schema text; v_old_search_path text; v_partition_name text; v_partition_timestamp_end timestamp; v_partition_timestamp_start timestamp; v_quarter text; v_step_id bigint; v_tablename text; v_time timestamp; v_year text; BEGIN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_name := p_parent_table || '_p'; IF p_interval = '1 year' OR p_interval = '1 month' OR p_interval = '1 day' OR p_interval = '1 hour' OR p_interval = '30 mins' OR p_interval = '15 mins' THEN v_partition_name := v_partition_name || to_char(v_time, 'YYYY'); IF p_interval = '1 month' OR p_interval = '1 day' OR p_interval = '1 hour' OR p_interval = '30 mins' OR p_interval = '15 mins' THEN v_partition_name := v_partition_name || '_' || to_char(v_time, 'MM'); IF p_interval = '1 day' OR p_interval = '1 hour' OR p_interval = '30 mins' OR p_interval = '15 mins' THEN v_partition_name := v_partition_name || '_' || to_char(v_time, 'DD'); IF p_interval = '1 hour' OR p_interval = '30 mins' OR p_interval = '15 mins' THEN v_partition_name := v_partition_name || '_' || to_char(v_time, 'HH24'); IF p_interval <> '30 mins' AND p_interval <> '15 mins' THEN v_partition_name := v_partition_name || '00'; ELSIF p_interval = '15 mins' THEN IF date_part('minute', v_time) < 15 THEN v_partition_name := v_partition_name || '00'; ELSIF date_part('minute', v_time) >= 15 AND date_part('minute', v_time) < 30 THEN v_partition_name := v_partition_name || '15'; ELSIF date_part('minute', v_time) >= 30 AND date_part('minute', v_time) < 45 THEN v_partition_name := v_partition_name || '30'; ELSE v_partition_name := v_partition_name || '45'; END IF; ELSIF p_interval = '30 mins' THEN IF date_part('minute', v_time) < 30 THEN v_partition_name := v_partition_name || '00'; ELSE v_partition_name := v_partition_name || '30'; END IF; END IF; END IF; -- end hour IF END IF; -- end day IF END IF; -- end month IF ELSIF p_interval = '1 week' THEN v_partition_name := v_partition_name || to_char(v_time, 'IYYY') || 'w' || to_char(v_time, 'IW'); END IF; -- end year/week IF -- pull out datetime portion of last partition's tablename v_partition_timestamp_start := to_timestamp(substring(v_partition_name from char_length(p_parent_table||'_p')+1), p_datetime_string); v_partition_timestamp_end := to_timestamp(substring(v_partition_name from char_length(p_parent_table||'_p')+1), p_datetime_string) + p_interval; -- "Q" is ignored in to_timestamp, so handle special case IF p_interval = '3 months' THEN v_year := to_char(v_time, 'YYYY'); v_quarter := to_char(v_time, 'Q'); v_partition_name := v_partition_name || v_year || 'q' || v_quarter; CASE WHEN v_quarter = '1' THEN v_partition_timestamp_start := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_partition_timestamp_start := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_partition_timestamp_start := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_partition_timestamp_start := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; v_partition_timestamp_end := v_partition_timestamp_start + p_interval; END IF; SELECT schemaname ||'.'|| tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_tablename IS NOT NULL THEN CONTINUE; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_partition_timestamp_start||' to '||(v_partition_timestamp_end-'1sec'::interval)); END IF; IF position('.' in p_parent_table) > 0 THEN v_tablename := substring(v_partition_name from position('.' in v_partition_name)+1); END IF; EXECUTE 'CREATE TABLE '||v_partition_name||' (LIKE '||p_parent_table||' INCLUDING DEFAULTS INCLUDING INDEXES)'; EXECUTE 'ALTER TABLE '||v_partition_name||' ADD CONSTRAINT '||v_tablename||'_partition_check CHECK ('||p_control||'>='||quote_literal(v_partition_timestamp_start)||' AND '||p_control||'<'||quote_literal(v_partition_timestamp_end)||')'; EXECUTE 'ALTER TABLE '||v_partition_name||' INHERIT '||p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); PERFORM close_job(v_job_id); END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_partition_name; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'BAD', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; CREATE OR REPLACE FUNCTION create_time_function(p_parent_table text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_1st_partition_name text; v_1st_partition_timestamp timestamp; v_2nd_partition_name text; v_2nd_partition_timestamp timestamp; v_control text; v_current_partition_name text; v_current_partition_timestamp timestamp; v_datetime_string text; v_final_partition_timestamp timestamp; v_job_id bigint; v_jobmon_schema text; v_old_search_path text; v_part_interval interval; v_prev_partition_name text; v_prev_partition_timestamp timestamp; v_step_id bigint; v_trig_func text; v_type text; BEGIN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE FUNCTION: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating partition function for table '||p_parent_table); END IF; SELECT type , part_interval::interval , control , datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic') INTO v_type, v_part_interval, v_control, v_datetime_string; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_type = 'time-static' THEN CASE WHEN v_part_interval = '15 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_part_interval = '30 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_part_interval = '1 hour' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_part_interval = '1 day' THEN v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_part_interval = '1 week' THEN v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_part_interval = '1 month' THEN v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_part_interval = '3 months' THEN v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_part_interval = '1 year' THEN v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; v_prev_partition_timestamp := v_current_partition_timestamp - v_part_interval::interval; v_1st_partition_timestamp := v_current_partition_timestamp + v_part_interval::interval; v_2nd_partition_timestamp := v_1st_partition_timestamp + v_part_interval::interval; v_final_partition_timestamp := v_2nd_partition_timestamp + v_part_interval::interval; v_prev_partition_name := p_parent_table || '_p' || to_char(v_prev_partition_timestamp, v_datetime_string); v_current_partition_name := p_parent_table || '_p' || to_char(v_current_partition_timestamp, v_datetime_string); v_1st_partition_name := p_parent_table || '_p' || to_char(v_1st_partition_timestamp, v_datetime_string); v_2nd_partition_name := p_parent_table || '_p' || to_char(v_2nd_partition_timestamp, v_datetime_string); v_trig_func := 'CREATE OR REPLACE FUNCTION '||p_parent_table||'_part_trig_func() RETURNS trigger LANGUAGE plpgsql AS $t$ BEGIN IF TG_OP = ''INSERT'' THEN IF NEW.'||v_control||' >= '||quote_literal(v_current_partition_timestamp)||' AND NEW.'||v_control||' < '||quote_literal(v_1st_partition_timestamp)|| ' THEN INSERT INTO '||v_current_partition_name||' VALUES (NEW.*); ELSIF NEW.'||v_control||' >= '||quote_literal(v_1st_partition_timestamp)||' AND NEW.'||v_control||' < '||quote_literal(v_2nd_partition_timestamp)|| ' THEN INSERT INTO '||v_1st_partition_name||' VALUES (NEW.*); ELSIF NEW.'||v_control||' >= '||quote_literal(v_2nd_partition_timestamp)||' AND NEW.'||v_control||' < '||quote_literal(v_final_partition_timestamp)|| ' THEN INSERT INTO '||v_2nd_partition_name||' VALUES (NEW.*); ELSIF NEW.'||v_control||' >= '||quote_literal(v_prev_partition_timestamp)||' AND NEW.'||v_control||' < '||quote_literal(v_current_partition_timestamp)|| ' THEN INSERT INTO '||v_prev_partition_name||' VALUES (NEW.*); ELSE RETURN NEW; END IF; END IF; RETURN NULL; END $t$;'; -- RAISE NOTICE 'v_trig_func: %',v_trig_func; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for current time interval: '||v_current_partition_timestamp||' to '||(v_1st_partition_timestamp-'1sec'::interval)); END IF; ELSIF v_type = 'time-dynamic' THEN v_trig_func := 'CREATE OR REPLACE FUNCTION '||p_parent_table||'_part_trig_func() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_partition_name text; v_partition_timestamp timestamp; BEGIN IF TG_OP = ''INSERT'' THEN '; CASE WHEN v_part_interval = '15 mins' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''hour'', NEW.'||v_control||') + ''15min''::interval * floor(date_part(''minute'', NEW.'||v_control||') / 15.0);'; WHEN v_part_interval = '30 mins' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''hour'', NEW.'||v_control||') + ''30min''::interval * floor(date_part(''minute'', NEW.'||v_control||') / 30.0);'; WHEN v_part_interval = '1 hour' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''hour'', NEW.'||v_control||');'; WHEN v_part_interval = '1 day' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''day'', NEW.'||v_control||');'; WHEN v_part_interval = '1 week' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''week'', NEW.'||v_control||');'; WHEN v_part_interval = '1 month' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''month'', NEW.'||v_control||');'; WHEN v_part_interval = '3 months' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''quarter'', NEW.'||v_control||');'; WHEN v_part_interval = '1 year' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''year'', NEW.'||v_control||');'; END CASE; v_trig_func := v_trig_func||' v_partition_name := '''||p_parent_table||'_p''|| to_char(v_partition_timestamp, '||quote_literal(v_datetime_string)||'); EXECUTE ''INSERT INTO ''||v_partition_name||'' VALUES($1.*)'' USING NEW; END IF; RETURN NULL; END $t$;'; --RAISE NOTICE 'v_trig_func: %',v_trig_func; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for dynamic time table: '||p_parent_table); END IF; ELSE RAISE EXCEPTION 'ERROR: Invalid time partitioning type given: %', v_type; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN CREATE FUNCTION: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition function maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'BAD', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; CREATE OR REPLACE FUNCTION create_next_time_partition (p_parent_table text) RETURNS void LANGUAGE plpgsql AS $$ DECLARE v_control text; v_datetime_string text; v_last_partition text; v_next_partition_timestamp timestamp; v_next_year text; v_part_interval interval; v_quarter text; v_tablename text; v_type @extschema@.partition_type; v_year text; BEGIN SELECT type , part_interval::interval , control , datetime_string , last_partition FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic') INTO v_type, v_part_interval, v_control, v_datetime_string, v_last_partition; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; -- Double check that last created partition exists IF v_last_partition IS NOT NULL THEN SELECT tablename INTO v_tablename FROM pg_tables WHERE schemaname || '.' || tablename = v_last_partition; IF v_tablename IS NULL THEN RAISE EXCEPTION 'ERROR: previous partition table missing. Unable to determine next proper partition in sequence.'; END IF; ELSE RAISE EXCEPTION 'ERROR: last known partition missing from config table for parent table %.', p_parent_table; END IF; -- pull out datetime portion of last partition's tablename to make the next one IF v_part_interval != '3 months' THEN v_next_partition_timestamp := to_timestamp(substring(v_last_partition from char_length(p_parent_table||'_p')+1), v_datetime_string) + v_part_interval; ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(v_last_partition from char_length(p_parent_table||'_p')+1), 'q', 1); v_next_year := extract('year' from to_date(v_year, 'YYYY')+'1year'::interval); v_quarter := split_part(substring(v_last_partition from char_length(p_parent_table||'_p')+1), 'q', 2); CASE WHEN v_quarter = '1' THEN v_next_partition_timestamp := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_next_partition_timestamp := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_next_partition_timestamp := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_next_partition_timestamp := to_timestamp(v_next_year || '-01-01', 'YYYY-MM-DD'); END CASE; END IF; EXECUTE 'SELECT @extschema@.create_time_partition('||quote_literal(p_parent_table)||','||quote_literal(v_control)||','||quote_literal(v_part_interval)||',' ||quote_literal(v_datetime_string)||','||quote_literal(ARRAY[v_next_partition_timestamp])||')' INTO v_last_partition; IF v_last_partition IS NOT NULL THEN UPDATE @extschema@.part_config SET last_partition = v_last_partition WHERE parent_table = p_parent_table; END IF; END $$; CREATE OR REPLACE FUNCTION create_prev_time_partition(p_parent_table text, p_batch int DEFAULT 1) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_datetime_string text; v_last_partition_name text; v_max_partition_timestamp timestamp; v_min_control timestamp; v_min_partition_timestamp timestamp; v_part_interval interval; v_partition_timestamp timestamp[]; v_rowcount bigint; v_sql text; v_total_rows bigint := 0; v_type text; BEGIN SELECT type , part_interval::interval , control , datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic') INTO v_type, v_part_interval, v_control, v_datetime_string; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; EXECUTE 'SELECT min('||v_control||') FROM ONLY '||p_parent_table INTO v_min_control; IF v_min_control IS NULL THEN RETURN 0; END IF; CASE WHEN v_part_interval = '15 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_min_control) + '15min'::interval * floor(date_part('minute', v_min_control) / 15.0); WHEN v_part_interval = '30 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_min_control) + '30min'::interval * floor(date_part('minute', v_min_control) / 30.0); WHEN v_part_interval = '1 hour' THEN v_min_partition_timestamp := date_trunc('hour', v_min_control); WHEN v_part_interval = '1 day' THEN v_min_partition_timestamp := date_trunc('day', v_min_control); WHEN v_part_interval = '1 week' THEN v_min_partition_timestamp := date_trunc('week', v_min_control); WHEN v_part_interval = '1 month' THEN v_min_partition_timestamp := date_trunc('month', v_min_control); WHEN v_part_interval = '3 months' THEN v_min_partition_timestamp := date_trunc('quarter', v_min_control); WHEN v_part_interval = '1 year' THEN v_min_partition_timestamp := date_trunc('year', v_min_control); END CASE; -- Subtract 1 so that batch count number actually makes sense FOR i IN 0..p_batch-1 LOOP v_partition_timestamp := ARRAY[(v_min_partition_timestamp + (v_part_interval*i))::timestamp]; RAISE NOTICE 'v_partition_timestamp: %',v_partition_timestamp; v_max_partition_timestamp := v_min_partition_timestamp + (v_part_interval*(i+1)); RAISE NOTICE 'v_max_partition_timestamp: %',v_max_partition_timestamp; v_sql := 'SELECT @extschema@.create_time_partition('||quote_literal(p_parent_table)||','||quote_literal(v_control)||',' ||quote_literal(v_part_interval)||','||quote_literal(v_datetime_string)||','||quote_literal(v_partition_timestamp)||')'; RAISE NOTICE 'v_sql: %', v_sql; EXECUTE v_sql INTO v_last_partition_name; -- create_time_partition() already checks to see if the partition exists and skips creation if it does. -- So this function will still work with already existing partitions to get all data moved out of parent table up to and including when -- pg_partman was used to set up partitioning. v_sql := 'WITH partition_data AS ( DELETE FROM ONLY '||p_parent_table||' WHERE '||v_control||' >= '||quote_literal(v_min_partition_timestamp)|| ' AND '||v_control||' < '||quote_literal(v_max_partition_timestamp)||' RETURNING *) INSERT INTO '||v_last_partition_name||' SELECT * FROM partition_data'; RAISE NOTICE 'v_sql: %', v_sql; EXECUTE v_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; RETURN v_total_rows; END $$; CREATE OR REPLACE FUNCTION run_maintenance() RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_count int := 0; v_current_partition_timestamp timestamp; v_datetime_string text; v_job_id bigint; v_jobmon_schema text; v_last_partition_timestamp timestamp; v_old_search_path text; v_premade_count real; v_quarter text; v_step_id bigint; v_row record; v_year text; BEGIN v_adv_lock := pg_try_advisory_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman maintenance already running.'; RETURN; END IF; SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN RUN MAINTENANCE'); v_step_id := add_step(v_job_id, 'Running maintenance loop'); END IF; FOR v_row IN SELECT parent_table , type , part_interval::interval , control , premake , datetime_string , last_partition FROM @extschema@.part_config WHERE type = 'time-static' OR type = 'time-dynamic' LOOP CASE WHEN v_row.part_interval = '15 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_row.part_interval = '30 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_row.part_interval = '1 hour' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '1 day' THEN v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '1 week' THEN v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '1 month' THEN v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '3 months' THEN v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '1 year' THEN v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; IF v_row.part_interval != '3 months' THEN v_last_partition_timestamp := to_timestamp(substring(v_row.last_partition from char_length(v_row.parent_table||'_p')+1), v_row.datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(v_row.last_partition from char_length(v_row.parent_table||'_p')+1), 'q', 1); v_quarter := split_part(substring(v_row.last_partition from char_length(v_row.parent_table||'_p')+1), 'q', 2); CASE WHEN v_quarter = '1' THEN v_last_partition_timestamp := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_last_partition_timestamp := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_last_partition_timestamp := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_last_partition_timestamp := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; -- Check and see how many premade partitions there are. If it's less than premake in config table, make another v_premade_count = EXTRACT('epoch' FROM (v_last_partition_timestamp - v_current_partition_timestamp)::interval) / EXTRACT('epoch' FROM v_row.part_interval::interval); IF v_premade_count < v_row.premake THEN RAISE NOTICE 'Creating next partition'; EXECUTE 'SELECT @extschema@.create_next_time_partition('||quote_literal(v_row.parent_table)||')'; IF v_row.type = 'time-static' THEN EXECUTE 'SELECT @extschema@.create_time_function('||quote_literal(v_row.parent_table)||')'; END IF; END IF; v_count := v_count + 1; END LOOP; -- end of main loop IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Partition maintenance finished. '||v_count||' partitions made'); PERFORM close_job(v_job_id); END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman run_maintenance')); EXCEPTION WHEN QUERY_CANCELED THEN PERFORM pg_advisory_unlock(hashtext('pg_partman run_maintenance')); RAISE EXCEPTION '%', SQLERRM; WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN RUN MAINTENANCE'); v_step_id := add_step(v_job_id, 'EXCEPTION before job logging started'); END IF; IF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'CRITICAL', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman run_maintenance')); RAISE EXCEPTION '%', SQLERRM; END $$; pg_partman-4.6.0/updates/pg_partman--0.1.2--0.2.0.sql000066400000000000000000000505771412761706100214330ustar00rootroot00000000000000-- New functions to manage dropping old partitions. Does not actually need to be called directly unless necessary. Use run_maintenance() function. -- Added ability to run_maintenance() function to manage dropping old tables in addition to managing time-based partitioning. -- Removed raise notice in run_maintenance and make sure old search path is reset after function finishes running. -- Lot of documentation updates UPDATE @extschema@.part_config SET retention = NULL; ALTER TABLE @extschema@.part_config ALTER retention TYPE text; ALTER TABLE @extschema@.part_config ADD retention_keep_table boolean NOT NULL DEFAULT true; ALTER TABLE @extschema@.part_config ADD retention_keep_index boolean NOT NULL DEFAULT true; /* * Function to drop child tables from a time-based partition set. Options to drop indexes or actually drop the table from the database. */ CREATE FUNCTION drop_id_partition(p_parent_table text, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL) RETURNS int LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_child_table text; v_control text; v_drop_count int := 0; v_index record; v_job_id bigint; v_jobmon_schema text; v_max bigint; v_old_search_path text; v_part_interval bigint; v_partition_id bigint; v_retention bigint; v_retention_keep_index boolean; v_retention_keep_table boolean; v_step_id bigint; BEGIN v_adv_lock := pg_try_advisory_lock(hashtext('pg_partman drop_id_partition')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_id_partition already running.'; RETURN 0; END IF; SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; SELECT part_interval::bigint , control , retention::bigint , retention_keep_table , retention_keep_index INTO v_part_interval , v_control , v_retention , v_retention_keep_table , v_retention_keep_index FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic') AND retention IS NOT NULL; IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; -- Allow override of keeping tables or indexes from input parameters IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN DROP ID PARTITION: '|| p_parent_table); END IF; EXECUTE 'SELECT max('||v_control||') FROM '||p_parent_table INTO v_max; -- Loop through child tables of the given parent FOR v_child_table IN SELECT inhrelid::regclass FROM pg_catalog.pg_inherits WHERE inhparent::regclass = p_parent_table::regclass ORDER BY inhrelid::regclass ASC LOOP v_partition_id := substring(v_child_table from char_length(p_parent_table||'_p')+1)::bigint; -- Add one interval since partition names contain the start of the constraint period IF v_retention <= (v_max - (v_partition_id + v_part_interval)) THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Uninherit table '||v_child_table||' from '||p_parent_table); END IF; EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Drop table '||v_child_table); END IF; EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN FOR v_index IN SELECT i.indexrelid::regclass AS name , c.conname FROM pg_catalog.pg_index i LEFT JOIN pg_catalog.pg_constraint c ON i.indexrelid = c.conindid WHERE i.indrelid = v_child_table::regclass LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Drop index '||v_index.name||' from '||v_child_table); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_child_table||' DROP CONSTRAINT '||v_index.conname; ELSE EXECUTE 'DROP INDEX '||v_index.name; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', v_drop_count||' partitions dropped.'); PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman drop_id_partition')); RETURN v_drop_count; EXCEPTION WHEN QUERY_CANCELED THEN PERFORM pg_advisory_unlock(hashtext('pg_partman drop_id_partition')); RAISE EXCEPTION '%', SQLERRM; WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN DROP ID PARTITION'); v_step_id := add_step(v_job_id, 'EXCEPTION before job logging started'); END IF; IF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'CRITICAL', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman drop_id_partition')); RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to drop child tables from a time-based partition set. Options to drop indexes or actually drop the table from the database. */ CREATE FUNCTION drop_time_partition(p_parent_table text, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL) RETURNS int LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_child_table text; v_datetime_string text; v_drop_count int := 0; v_index record; v_job_id bigint; v_jobmon_schema text; v_old_search_path text; v_part_interval interval; v_partition_timestamp timestamp; v_quarter text; v_retention interval; v_retention_keep_index boolean; v_retention_keep_table boolean; v_step_id bigint; v_year text; BEGIN v_adv_lock := pg_try_advisory_lock(hashtext('pg_partman drop_time_partition')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_time_partition already running.'; RETURN 0; END IF; SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; SELECT part_interval::interval , retention::interval , retention_keep_table , retention_keep_index , datetime_string INTO v_part_interval , v_retention , v_retention_keep_table , v_retention_keep_index , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic') AND retention IS NOT NULL; IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; -- Allow override of keeping tables or indexes from input parameters IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN DROP TIME PARTITION: '|| p_parent_table); END IF; -- Loop through child tables of the given parent FOR v_child_table IN SELECT inhrelid::regclass FROM pg_catalog.pg_inherits WHERE inhparent::regclass = p_parent_table::regclass ORDER BY inhrelid::regclass ASC LOOP -- pull out datetime portion of last partition's tablename to make the next one IF v_part_interval != '3 months' THEN v_partition_timestamp := to_timestamp(substring(v_child_table from char_length(p_parent_table||'_p')+1), v_datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(v_child_table from char_length(p_parent_table||'_p')+1), 'q', 1); v_quarter := split_part(substring(v_child_table from char_length(p_parent_table||'_p')+1), 'q', 2); CASE WHEN v_quarter = '1' THEN v_partition_timestamp := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_partition_timestamp := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_partition_timestamp := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_partition_timestamp := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; -- Add one interval since partition names contain the start of the constraint period IF v_retention < (CURRENT_TIMESTAMP - (v_partition_timestamp + v_part_interval)) THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Uninherit table '||v_child_table||' from '||p_parent_table); END IF; EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Drop table '||v_child_table); END IF; EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN FOR v_index IN SELECT i.indexrelid::regclass AS name , c.conname FROM pg_catalog.pg_index i LEFT JOIN pg_catalog.pg_constraint c ON i.indexrelid = c.conindid WHERE i.indrelid = v_child_table::regclass LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Drop index '||v_index.name||' from '||v_child_table); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_child_table||' DROP CONSTRAINT '||v_index.conname; ELSE EXECUTE 'DROP INDEX '||v_index.name; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', v_drop_count||' partitions dropped.'); PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman drop_time_partition')); RETURN v_drop_count; EXCEPTION WHEN QUERY_CANCELED THEN PERFORM pg_advisory_unlock(hashtext('pg_partman drop_time_partition')); RAISE EXCEPTION '%', SQLERRM; WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN DROP TIME PARTITION'); v_step_id := add_step(v_job_id, 'EXCEPTION before job logging started'); END IF; IF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'CRITICAL', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman drop_time_partition')); RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to manage pre-creation of the next partitions in a time-based partition set * Also manages dropping old partitions if the retention option is set */ CREATE OR REPLACE FUNCTION run_maintenance() RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_create_count int := 0; v_current_partition_timestamp timestamp; v_datetime_string text; v_drop_count int := 0; v_job_id bigint; v_jobmon_schema text; v_last_partition_timestamp timestamp; v_old_search_path text; v_premade_count real; v_quarter text; v_step_id bigint; v_row record; v_year text; BEGIN v_adv_lock := pg_try_advisory_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman maintenance already running.'; RETURN; END IF; SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN RUN MAINTENANCE'); v_step_id := add_step(v_job_id, 'Running maintenance loop'); END IF; FOR v_row IN SELECT parent_table , type , part_interval::interval , control , premake , datetime_string , last_partition FROM @extschema@.part_config WHERE type = 'time-static' OR type = 'time-dynamic' LOOP CASE WHEN v_row.part_interval = '15 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_row.part_interval = '30 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_row.part_interval = '1 hour' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '1 day' THEN v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '1 week' THEN v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '1 month' THEN v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '3 months' THEN v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '1 year' THEN v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; IF v_row.part_interval != '3 months' THEN v_last_partition_timestamp := to_timestamp(substring(v_row.last_partition from char_length(v_row.parent_table||'_p')+1), v_row.datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(v_row.last_partition from char_length(v_row.parent_table||'_p')+1), 'q', 1); v_quarter := split_part(substring(v_row.last_partition from char_length(v_row.parent_table||'_p')+1), 'q', 2); CASE WHEN v_quarter = '1' THEN v_last_partition_timestamp := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_last_partition_timestamp := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_last_partition_timestamp := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_last_partition_timestamp := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; -- Check and see how many premade partitions there are. If it's less than premake in config table, make another v_premade_count = EXTRACT('epoch' FROM (v_last_partition_timestamp - v_current_partition_timestamp)::interval) / EXTRACT('epoch' FROM v_row.part_interval::interval); IF v_premade_count < v_row.premake THEN EXECUTE 'SELECT @extschema@.create_next_time_partition('||quote_literal(v_row.parent_table)||')'; v_create_count := v_create_count + 1; IF v_row.type = 'time-static' THEN EXECUTE 'SELECT @extschema@.create_time_function('||quote_literal(v_row.parent_table)||')'; END IF; END IF; END LOOP; -- end of creation loop -- Manage dropping old partitions if retention option is set FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND (type = 'time-static' OR type = 'time-dynamic') LOOP v_drop_count := v_drop_count + @extschema@.drop_time_partition(v_row.parent_table); END LOOP; FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND (type = 'id-static' OR type = 'id-dynamic') LOOP v_drop_count := v_drop_count + @extschema@.drop_id_partition(v_row.parent_table); END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Partition maintenance finished. '||v_create_count||' partitions made. '||v_drop_count||' partitions dropped.'); PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman run_maintenance')); EXCEPTION WHEN QUERY_CANCELED THEN PERFORM pg_advisory_unlock(hashtext('pg_partman run_maintenance')); RAISE EXCEPTION '%', SQLERRM; WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN RUN MAINTENANCE'); v_step_id := add_step(v_job_id, 'EXCEPTION before job logging started'); END IF; IF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'CRITICAL', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman run_maintenance')); RAISE EXCEPTION '%', SQLERRM; END $$; pg_partman-4.6.0/updates/pg_partman--0.2.0--0.3.0.sql000066400000000000000000000265041412761706100214240ustar00rootroot00000000000000-- Added grants configuration table to propagate permissions to newly created child partitions. Will also apply those permissions to the parent table and all existing child tables whenever a new partition is created. Permissions are only granted, never revoked. See docs for more info. CREATE TABLE part_grants ( parent_table text PRIMARY KEY REFERENCES @extschema@.part_config (parent_table) ON DELETE CASCADE ON UPDATE CASCADE, grants text, roles text ); /* * Function to apply grants on parent & child tables */ CREATE FUNCTION apply_grants(p_parent_table text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_child_table text; v_grants text; v_roles text; BEGIN SELECT grants, roles INTO v_grants, v_roles FROM @extschema@.part_grants WHERE parent_table = p_parent_table; IF v_grants IS NOT NULL AND v_roles IS NOT NULL THEN EXECUTE 'GRANT '||v_grants||' ON '||p_parent_table||' TO '||v_roles; FOR v_child_table IN SELECT inhrelid::regclass FROM pg_catalog.pg_inherits WHERE inhparent::regclass = p_parent_table::regclass ORDER BY inhrelid::regclass ASC LOOP EXECUTE 'GRANT '||v_grants||' ON TABLE '||v_child_table||' TO '||v_roles; END LOOP; END IF; END $$; /* * Function to create id partitions */ CREATE OR REPLACE FUNCTION create_id_partition (p_parent_table text, p_control text, p_interval bigint, p_partition_ids bigint[]) RETURNS text LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_job_id bigint; v_jobmon_schema text; v_old_search_path text; v_partition_name text; v_step_id bigint; v_tablename text; v_id bigint; BEGIN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; FOREACH v_id IN ARRAY p_partition_ids LOOP v_partition_name := p_parent_table||'_p'||v_id; SELECT schemaname ||'.'|| tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_tablename IS NOT NULL THEN CONTINUE; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_id||' to '||(v_id + p_interval)-1); END IF; IF position('.' in p_parent_table) > 0 THEN v_tablename := substring(v_partition_name from position('.' in v_partition_name)+1); END IF; EXECUTE 'CREATE TABLE '||v_partition_name||' (LIKE '||p_parent_table||' INCLUDING DEFAULTS INCLUDING INDEXES)'; EXECUTE 'ALTER TABLE '||v_partition_name||' ADD CONSTRAINT '||v_tablename||'_partition_check CHECK ('||p_control||'>='||quote_literal(v_id)||' AND '||p_control||'<'||quote_literal(v_id + p_interval)||')'; EXECUTE 'ALTER TABLE '||v_partition_name||' INHERIT '||p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); PERFORM close_job(v_job_id); END IF; END LOOP; -- Apply grants if configured PERFORM @extschema@.apply_grants(p_parent_table); IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_partition_name; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'BAD', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to create a child table in a time-based partition set */ CREATE OR REPLACE FUNCTION create_time_partition (p_parent_table text, p_control text, p_interval interval, p_datetime_string text, p_partition_times timestamp[]) RETURNS text LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_job_id bigint; v_jobmon_schema text; v_old_search_path text; v_partition_name text; v_partition_timestamp_end timestamp; v_partition_timestamp_start timestamp; v_quarter text; v_step_id bigint; v_tablename text; v_time timestamp; v_year text; BEGIN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_name := p_parent_table || '_p'; IF p_interval = '1 year' OR p_interval = '1 month' OR p_interval = '1 day' OR p_interval = '1 hour' OR p_interval = '30 mins' OR p_interval = '15 mins' THEN v_partition_name := v_partition_name || to_char(v_time, 'YYYY'); IF p_interval = '1 month' OR p_interval = '1 day' OR p_interval = '1 hour' OR p_interval = '30 mins' OR p_interval = '15 mins' THEN v_partition_name := v_partition_name || '_' || to_char(v_time, 'MM'); IF p_interval = '1 day' OR p_interval = '1 hour' OR p_interval = '30 mins' OR p_interval = '15 mins' THEN v_partition_name := v_partition_name || '_' || to_char(v_time, 'DD'); IF p_interval = '1 hour' OR p_interval = '30 mins' OR p_interval = '15 mins' THEN v_partition_name := v_partition_name || '_' || to_char(v_time, 'HH24'); IF p_interval <> '30 mins' AND p_interval <> '15 mins' THEN v_partition_name := v_partition_name || '00'; ELSIF p_interval = '15 mins' THEN IF date_part('minute', v_time) < 15 THEN v_partition_name := v_partition_name || '00'; ELSIF date_part('minute', v_time) >= 15 AND date_part('minute', v_time) < 30 THEN v_partition_name := v_partition_name || '15'; ELSIF date_part('minute', v_time) >= 30 AND date_part('minute', v_time) < 45 THEN v_partition_name := v_partition_name || '30'; ELSE v_partition_name := v_partition_name || '45'; END IF; ELSIF p_interval = '30 mins' THEN IF date_part('minute', v_time) < 30 THEN v_partition_name := v_partition_name || '00'; ELSE v_partition_name := v_partition_name || '30'; END IF; END IF; END IF; -- end hour IF END IF; -- end day IF END IF; -- end month IF ELSIF p_interval = '1 week' THEN v_partition_name := v_partition_name || to_char(v_time, 'IYYY') || 'w' || to_char(v_time, 'IW'); END IF; -- end year/week IF -- pull out datetime portion of last partition's tablename v_partition_timestamp_start := to_timestamp(substring(v_partition_name from char_length(p_parent_table||'_p')+1), p_datetime_string); v_partition_timestamp_end := to_timestamp(substring(v_partition_name from char_length(p_parent_table||'_p')+1), p_datetime_string) + p_interval; -- "Q" is ignored in to_timestamp, so handle special case IF p_interval = '3 months' THEN v_year := to_char(v_time, 'YYYY'); v_quarter := to_char(v_time, 'Q'); v_partition_name := v_partition_name || v_year || 'q' || v_quarter; CASE WHEN v_quarter = '1' THEN v_partition_timestamp_start := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_partition_timestamp_start := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_partition_timestamp_start := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_partition_timestamp_start := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; v_partition_timestamp_end := v_partition_timestamp_start + p_interval; END IF; SELECT schemaname ||'.'|| tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_tablename IS NOT NULL THEN CONTINUE; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_partition_timestamp_start||' to '||(v_partition_timestamp_end-'1sec'::interval)); END IF; IF position('.' in p_parent_table) > 0 THEN v_tablename := substring(v_partition_name from position('.' in v_partition_name)+1); END IF; EXECUTE 'CREATE TABLE '||v_partition_name||' (LIKE '||p_parent_table||' INCLUDING DEFAULTS INCLUDING INDEXES)'; EXECUTE 'ALTER TABLE '||v_partition_name||' ADD CONSTRAINT '||v_tablename||'_partition_check CHECK ('||p_control||'>='||quote_literal(v_partition_timestamp_start)||' AND '||p_control||'<'||quote_literal(v_partition_timestamp_end)||')'; EXECUTE 'ALTER TABLE '||v_partition_name||' INHERIT '||p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); PERFORM close_job(v_job_id); END IF; END LOOP; -- Apply grants if configured PERFORM @extschema@.apply_grants(p_parent_table); IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_partition_name; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'BAD', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; pg_partman-4.6.0/updates/pg_partman--0.3.0--0.3.1.sql000066400000000000000000000461601412761706100214260ustar00rootroot00000000000000-- Added check to dynamic id & time trigger functions to see if target table exists. If it doesn't, insert to parent instead of throwing error. Better than losing data! check_parent() function can monitor for this happening and create_prev_* functions can easily fix it. Thought of having it auto-create the needed partition, but if something is going wrong, that could end up creating a lot of unwanted partitions and be harder to clean up. /* * Create the trigger function for the parent table of a time-based partition set */ CREATE OR REPLACE FUNCTION create_time_function(p_parent_table text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_1st_partition_name text; v_1st_partition_timestamp timestamp; v_2nd_partition_name text; v_2nd_partition_timestamp timestamp; v_control text; v_current_partition_name text; v_current_partition_timestamp timestamp; v_datetime_string text; v_final_partition_timestamp timestamp; v_job_id bigint; v_jobmon_schema text; v_old_search_path text; v_part_interval interval; v_prev_partition_name text; v_prev_partition_timestamp timestamp; v_step_id bigint; v_trig_func text; v_type text; BEGIN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE FUNCTION: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating partition function for table '||p_parent_table); END IF; SELECT type , part_interval::interval , control , datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic') INTO v_type, v_part_interval, v_control, v_datetime_string; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_type = 'time-static' THEN CASE WHEN v_part_interval = '15 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_part_interval = '30 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_part_interval = '1 hour' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_part_interval = '1 day' THEN v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_part_interval = '1 week' THEN v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_part_interval = '1 month' THEN v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_part_interval = '3 months' THEN v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_part_interval = '1 year' THEN v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; v_prev_partition_timestamp := v_current_partition_timestamp - v_part_interval::interval; v_1st_partition_timestamp := v_current_partition_timestamp + v_part_interval::interval; v_2nd_partition_timestamp := v_1st_partition_timestamp + v_part_interval::interval; v_final_partition_timestamp := v_2nd_partition_timestamp + v_part_interval::interval; v_prev_partition_name := p_parent_table || '_p' || to_char(v_prev_partition_timestamp, v_datetime_string); v_current_partition_name := p_parent_table || '_p' || to_char(v_current_partition_timestamp, v_datetime_string); v_1st_partition_name := p_parent_table || '_p' || to_char(v_1st_partition_timestamp, v_datetime_string); v_2nd_partition_name := p_parent_table || '_p' || to_char(v_2nd_partition_timestamp, v_datetime_string); v_trig_func := 'CREATE OR REPLACE FUNCTION '||p_parent_table||'_part_trig_func() RETURNS trigger LANGUAGE plpgsql AS $t$ BEGIN IF TG_OP = ''INSERT'' THEN IF NEW.'||v_control||' >= '||quote_literal(v_current_partition_timestamp)||' AND NEW.'||v_control||' < '||quote_literal(v_1st_partition_timestamp)|| ' THEN INSERT INTO '||v_current_partition_name||' VALUES (NEW.*); ELSIF NEW.'||v_control||' >= '||quote_literal(v_1st_partition_timestamp)||' AND NEW.'||v_control||' < '||quote_literal(v_2nd_partition_timestamp)|| ' THEN INSERT INTO '||v_1st_partition_name||' VALUES (NEW.*); ELSIF NEW.'||v_control||' >= '||quote_literal(v_2nd_partition_timestamp)||' AND NEW.'||v_control||' < '||quote_literal(v_final_partition_timestamp)|| ' THEN INSERT INTO '||v_2nd_partition_name||' VALUES (NEW.*); ELSIF NEW.'||v_control||' >= '||quote_literal(v_prev_partition_timestamp)||' AND NEW.'||v_control||' < '||quote_literal(v_current_partition_timestamp)|| ' THEN INSERT INTO '||v_prev_partition_name||' VALUES (NEW.*); ELSE RETURN NEW; END IF; END IF; RETURN NULL; END $t$;'; -- RAISE NOTICE 'v_trig_func: %',v_trig_func; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for current time interval: '||v_current_partition_timestamp||' to '||(v_1st_partition_timestamp-'1sec'::interval)); END IF; ELSIF v_type = 'time-dynamic' THEN v_trig_func := 'CREATE OR REPLACE FUNCTION '||p_parent_table||'_part_trig_func() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_partition_name text; v_partition_timestamp timestamp; v_schemaname text; v_tablename text; BEGIN IF TG_OP = ''INSERT'' THEN '; CASE WHEN v_part_interval = '15 mins' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''hour'', NEW.'||v_control||') + ''15min''::interval * floor(date_part(''minute'', NEW.'||v_control||') / 15.0);'; WHEN v_part_interval = '30 mins' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''hour'', NEW.'||v_control||') + ''30min''::interval * floor(date_part(''minute'', NEW.'||v_control||') / 30.0);'; WHEN v_part_interval = '1 hour' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''hour'', NEW.'||v_control||');'; WHEN v_part_interval = '1 day' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''day'', NEW.'||v_control||');'; WHEN v_part_interval = '1 week' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''week'', NEW.'||v_control||');'; WHEN v_part_interval = '1 month' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''month'', NEW.'||v_control||');'; WHEN v_part_interval = '3 months' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''quarter'', NEW.'||v_control||');'; WHEN v_part_interval = '1 year' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''year'', NEW.'||v_control||');'; END CASE; v_trig_func := v_trig_func||' v_partition_name := '''||p_parent_table||'_p''|| to_char(v_partition_timestamp, '||quote_literal(v_datetime_string)||'); v_schemaname := split_part(v_partition_name, ''.'', 1); v_tablename := split_part(v_partition_name, ''.'', 2); SELECT count(*) INTO v_count FROM pg_tables WHERE schemaname = v_schemaname AND tablename = v_tablename; IF v_count > 0 THEN EXECUTE ''INSERT INTO ''||v_partition_name||'' VALUES($1.*)'' USING NEW; ELSE RETURN NEW; END IF; END IF; RETURN NULL; END $t$;'; --RAISE NOTICE 'v_trig_func: %',v_trig_func; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for dynamic time table: '||p_parent_table); END IF; ELSE RAISE EXCEPTION 'ERROR: Invalid time partitioning type given: %', v_type; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN CREATE FUNCTION: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition function maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'BAD', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Create the trigger function for the parent table of an id-based partition set */ CREATE OR REPLACE FUNCTION create_id_function(p_parent_table text, p_current_id bigint) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_1st_partition_name text; v_1st_partition_id bigint; v_2nd_partition_name text; v_2nd_partition_id bigint; v_control text; v_current_partition_name text; v_current_partition_id bigint; v_datetime_string text; v_final_partition_id bigint; v_job_id bigint; v_jobmon_schema text; v_last_partition text; v_old_search_path text; v_part_interval bigint; v_premake int; v_prev_partition_name text; v_prev_partition_id bigint; v_step_id bigint; v_trig_func text; v_type text; BEGIN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE FUNCTION: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating partition function for table '||p_parent_table); END IF; SELECT type , part_interval::bigint , control , premake , last_partition FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic') INTO v_type, v_part_interval, v_control, v_premake, v_last_partition; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_type = 'id-static' THEN v_current_partition_id := p_current_id - (p_current_id % v_part_interval); v_prev_partition_id := v_current_partition_id - v_part_interval; v_1st_partition_id := v_current_partition_id + v_part_interval; v_2nd_partition_id := v_1st_partition_id + v_part_interval; v_final_partition_id := v_2nd_partition_id + v_part_interval; v_prev_partition_name := p_parent_table || '_p' || v_prev_partition_id::text; v_current_partition_name := p_parent_table || '_p' || v_current_partition_id::text; v_1st_partition_name := p_parent_table || '_p' || v_1st_partition_id::text; v_2nd_partition_name := p_parent_table || '_p' || v_2nd_partition_id::text; v_trig_func := 'CREATE OR REPLACE FUNCTION '||p_parent_table||'_part_trig_func() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_current_partition_id bigint; v_last_partition text := '||quote_literal(v_last_partition)||'; v_next_partition_id bigint; v_next_partition_name text; BEGIN IF TG_OP = ''INSERT'' THEN IF NEW.'||v_control||' >= '||v_current_partition_id||' AND NEW.'||v_control||' < '||v_1st_partition_id|| ' THEN INSERT INTO '||v_current_partition_name||' VALUES (NEW.*); ELSIF NEW.'||v_control||' >= '||v_1st_partition_id||' AND NEW.'||v_control||' < '||v_2nd_partition_id|| ' THEN INSERT INTO '||v_1st_partition_name||' VALUES (NEW.*); ELSIF NEW.'||v_control||' >= '||v_2nd_partition_id||' AND NEW.'||v_control||' < '||quote_literal(v_final_partition_id)|| ' THEN INSERT INTO '||v_2nd_partition_name||' VALUES (NEW.*); '; -- If the first partition's function, don't have rule for previous partition IF v_prev_partition_id >= 0 THEN v_trig_func := v_trig_func ||'ELSIF NEW.'||v_control||' >= '||v_prev_partition_id||' AND NEW.'||v_control||' < '||v_current_partition_id|| ' THEN INSERT INTO '||v_prev_partition_name||' VALUES (NEW.*); '; END IF; v_trig_func := v_trig_func ||'ELSE RETURN NEW; END IF; v_current_partition_id := NEW.'||v_control||' - (NEW.'||v_control||' % '||v_part_interval||'); IF (NEW.'||v_control||' % '||v_part_interval||') > ('||v_part_interval||' / 2) THEN v_next_partition_id := (substring(v_last_partition from char_length('||quote_literal(p_parent_table||'_p')||')+1)::bigint) + '||v_part_interval||'; IF ((v_next_partition_id - v_current_partition_id) / '||v_part_interval||') <= '||v_premake||' THEN v_next_partition_name := @extschema@.create_id_partition('||quote_literal(p_parent_table)||', '||quote_literal(v_control)||',' ||v_part_interval||', ARRAY[v_next_partition_id]); UPDATE @extschema@.part_config SET last_partition = v_next_partition_name WHERE parent_table = '||quote_literal(p_parent_table)||'; PERFORM @extschema@.create_id_function('||quote_literal(p_parent_table)||', NEW.'||v_control||'); END IF; END IF; END IF; RETURN NULL; END $t$;'; --RAISE NOTICE 'v_trig_func: %',v_trig_func; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for current id interval: '||v_current_partition_id||' to '||v_1st_partition_id-1); END IF; ELSIF v_type = 'id-dynamic' THEN v_trig_func := 'CREATE OR REPLACE FUNCTION '||p_parent_table||'_part_trig_func() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_current_partition_id bigint; v_current_partition_name text; v_last_partition text := '||quote_literal(v_last_partition)||'; v_last_partition_id bigint; v_next_partition_id bigint; v_next_partition_name text; v_schemaname text; v_tablename text; BEGIN IF TG_OP = ''INSERT'' THEN v_current_partition_id := NEW.'||v_control||' - (NEW.'||v_control||' % '||v_part_interval||'); v_current_partition_name := '''||p_parent_table||'_p''||v_current_partition_id; IF (NEW.'||v_control||' % '||v_part_interval||') > ('||v_part_interval||' / 2) THEN v_last_partition_id = substring(v_last_partition from char_length('||quote_literal(p_parent_table||'_p')||')+1)::bigint; v_next_partition_id := v_last_partition_id + '||v_part_interval||'; IF NEW.'||v_control||' >= v_next_partition_id THEN RETURN NEW; END IF; IF ((v_next_partition_id - v_current_partition_id) / '||quote_literal(v_part_interval)||') <= '||quote_literal(v_premake)||' THEN v_next_partition_name := @extschema@.create_id_partition('||quote_literal(p_parent_table)||', '||quote_literal(v_control)||',' ||quote_literal(v_part_interval)||', ARRAY[v_next_partition_id]); IF v_next_partition_name IS NOT NULL THEN UPDATE @extschema@.part_config SET last_partition = v_next_partition_name WHERE parent_table = '||quote_literal(p_parent_table)||'; PERFORM @extschema@.create_id_function('||quote_literal(p_parent_table)||', NEW.'||v_control||'); END IF; END IF; END IF; v_schemaname := split_part(v_current_partition_name, ''.'', 1); v_tablename := split_part(v_current_partition_name, ''.'', 2); SELECT count(*) INTO v_count FROM pg_tables WHERE schemaname = v_schemaname AND tablename = v_tablename; IF v_count > 0 THEN EXECUTE ''INSERT INTO ''||v_current_partition_name||'' VALUES($1.*)'' USING NEW; ELSE RETURN NEW; END IF; END IF; RETURN NULL; END $t$;'; -- RAISE NOTICE 'v_trig_func: %',v_trig_func; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for dynamic id table: '||p_parent_table); END IF; ELSE RAISE EXCEPTION 'ERROR: Invalid id partitioning type given: %', v_type; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN CREATE FUNCTION: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition function maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'BAD', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; pg_partman-4.6.0/updates/pg_partman--0.3.1--0.3.2.sql000066400000000000000000000222701412761706100214240ustar00rootroot00000000000000-- Allow multiple grant commands for the same partition set in case different roles need different grants. Removed primary key constraint from part_grants table and updated apply_grants function -- create_parent() function now ensures that the control column has a not null constraint. -- Make select-only functions STABLE ALTER TABLE @extschema@.part_grants DROP CONSTRAINT part_grants_pkey; ALTER TABLE @extschema@.part_grants DROP CONSTRAINT part_grants_parent_table_fkey; ALTER TABLE @extschema@.part_grants ADD CONSTRAINT part_grants_parent_table_fkey FOREIGN KEY (parent_table) REFERENCES @extschema@.part_config (parent_table) ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE @extschema@.part_grants ADD CONSTRAINT part_grants_unique_grant UNIQUE (parent_table, grants, roles); /* * Function to apply grants on parent & child tables */ CREATE OR REPLACE FUNCTION apply_grants(p_parent_table text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_child_table text; v_grants text; v_roles text; v_row record; BEGIN FOR v_row IN SELECT grants, roles FROM @extschema@.part_grants WHERE parent_table = p_parent_table LOOP EXECUTE 'GRANT '||v_row.grants||' ON '||p_parent_table||' TO '||v_row.roles; FOR v_child_table IN SELECT inhrelid::regclass FROM pg_catalog.pg_inherits WHERE inhparent::regclass = p_parent_table::regclass ORDER BY inhrelid::regclass ASC LOOP EXECUTE 'GRANT '||v_row.grants||' ON '||v_child_table||' TO '||v_row.roles; END LOOP; END LOOP; END $$; /* * Function to turn a table into the parent of a partition set */ CREATE OR REPLACE FUNCTION create_parent(p_parent_table text, p_control text, p_type @extschema@.partition_type, p_interval text, p_premake int DEFAULT 4, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_current_id bigint; v_datetime_string text; v_id_interval bigint; v_job_id bigint; v_jobmon_schema text; v_last_partition_name text; v_old_search_path text; v_partition_time timestamp[]; v_partition_id bigint[]; v_max bigint; v_notnull boolean; v_starting_partition_id bigint; v_step_id bigint; v_tablename text; v_time_interval interval; BEGIN SELECT tablename INTO v_tablename FROM pg_tables WHERE schemaname || '.' || tablename = p_parent_table; IF v_tablename IS NULL THEN RAISE EXCEPTION 'Please create given parent table first: %', p_parent_table; END IF; SELECT attnotnull INTO v_notnull FROM pg_attribute WHERE attrelid = p_parent_table::regclass AND attname = p_control; IF v_notnull = false THEN RAISE EXCEPTION 'Control column (%) for parent table (%) must be NOT NULL', p_control, p_parent_table; END IF; EXECUTE 'LOCK TABLE '||p_parent_table||' IN ACCESS EXCLUSIVE MODE'; SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN SETUP PARENT: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating initial partitions on new parent table: '||p_parent_table); END IF; CASE WHEN p_interval = 'yearly' THEN v_time_interval = '1 year'; v_datetime_string := 'YYYY'; WHEN p_interval = 'quarterly' THEN v_time_interval = '3 months'; v_datetime_string = 'YYYY"q"Q'; WHEN p_interval = 'monthly' THEN v_time_interval = '1 month'; v_datetime_string := 'YYYY_MM'; WHEN p_interval = 'weekly' THEN v_time_interval = '1 week'; v_datetime_string := 'IYYY"w"IW'; WHEN p_interval = 'daily' THEN v_time_interval = '1 day'; v_datetime_string := 'YYYY_MM_DD'; WHEN p_interval = 'hourly' THEN v_time_interval = '1 hour'; v_datetime_string := 'YYYY_MM_DD_HH24MI'; WHEN p_interval = 'half-hour' THEN v_time_interval = '30 mins'; v_datetime_string := 'YYYY_MM_DD_HH24MI'; WHEN p_interval = 'quarter-hour' THEN v_time_interval = '15 mins'; v_datetime_string := 'YYYY_MM_DD_HH24MI'; ELSE IF p_type = 'id-static' OR p_type = 'id-dynamic' THEN v_id_interval := p_interval::bigint; ELSE RAISE EXCEPTION 'Invalid interval for time based partitioning: %', p_interval; END IF; END CASE; IF p_type = 'time-static' OR p_type = 'time-dynamic' THEN FOR i IN 0..p_premake LOOP v_partition_time := array_append(v_partition_time, quote_literal(CURRENT_TIMESTAMP + (v_time_interval*i))::timestamp); END LOOP; EXECUTE 'SELECT @extschema@.create_time_partition('||quote_literal(p_parent_table)||','||quote_literal(p_control)||',' ||quote_literal(v_time_interval)||','||quote_literal(v_datetime_string)||','||quote_literal(v_partition_time)||')' INTO v_last_partition_name; INSERT INTO @extschema@.part_config (parent_table, type, part_interval, control, premake, datetime_string, last_partition) VALUES (p_parent_table, p_type, v_time_interval, p_control, p_premake, v_datetime_string, v_last_partition_name); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Time partitions premade: '||p_premake); END IF; END IF; IF p_type = 'id-static' OR p_type = 'id-dynamic' THEN -- If there is already data, start partitioning with the highest current value EXECUTE 'SELECT COALESCE(max('||p_control||')::bigint, 0) FROM '||p_parent_table||' LIMIT 1' INTO v_max; v_starting_partition_id := v_max - (v_max % v_id_interval); FOR i IN 0..p_premake LOOP v_partition_id = array_append(v_partition_id, (v_id_interval*i)+v_starting_partition_id); END LOOP; EXECUTE 'SELECT @extschema@.create_id_partition('||quote_literal(p_parent_table)||','||quote_literal(p_control)||',' ||v_id_interval||','||quote_literal(v_partition_id)||')' INTO v_last_partition_name; INSERT INTO @extschema@.part_config (parent_table, type, part_interval, control, premake, last_partition) VALUES (p_parent_table, p_type, v_id_interval, p_control, p_premake, v_last_partition_name); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'ID partitions premade: '||p_premake); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating partition function'); END IF; IF p_type = 'time-static' OR p_type = 'time-dynamic' THEN EXECUTE 'SELECT @extschema@.create_time_function('||quote_literal(p_parent_table)||')'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Time function created'); END IF; ELSIF p_type = 'id-static' OR p_type = 'id-dynamic' THEN v_current_id := COALESCE(v_max, 0); EXECUTE 'SELECT @extschema@.create_id_function('||quote_literal(p_parent_table)||','||v_current_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; EXECUTE 'SELECT @extschema@.create_trigger('||quote_literal(p_parent_table)||')'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN CREATE PARENT: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition creation for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'BAD', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to monitor for data getting inserted into parent tables managed by extension */ CREATE OR REPLACE FUNCTION check_parent() RETURNS SETOF @extschema@.check_parent_table LANGUAGE plpgsql STABLE SECURITY DEFINER AS $$ DECLARE v_count bigint = 0; v_sql text; v_tables record; v_trouble @extschema@.check_parent_table%rowtype; BEGIN FOR v_tables IN SELECT DISTINCT parent_table FROM @extschema@.part_config LOOP v_sql := 'SELECT count(1) AS n FROM ONLY '||v_tables.parent_table; EXECUTE v_sql INTO v_count; IF v_count > 0 THEN v_trouble.parent_table := v_tables.parent_table; v_trouble.count := v_count; RETURN NEXT v_trouble; END IF; v_count := 0; END LOOP; RETURN; END $$; pg_partman-4.6.0/updates/pg_partman--0.3.2--0.4.0.sql000066400000000000000000000617441412761706100214350ustar00rootroot00000000000000-- No separate configuration required for setting privileges on child tables anymore. Grants config table has been dropped. Please apply the grants you need to the parent table and they will be set for all children using that. Note that unlike before, privilges that don't exist on the parent will now be revoked from all child tables. -- create_parent() now enforces that a given parent table be schema qualified. Ensures that a custom search_path doesn't affect the wrong table by accident. -- Removed enum custom type and replace with check function. -- Applying of grants is now logged in pg_jobmon so if there's any issues with that step, it's clear where it failed. /* * Check function for config table partition types */ CREATE FUNCTION check_partition_type (p_type text) RETURNS boolean LANGUAGE plpgsql IMMUTABLE SECURITY DEFINER AS $$ DECLARE v_result boolean; BEGIN SELECT p_type IN ('time-static', 'time-dynamic', 'id-static', 'id-dynamic') INTO v_result; RETURN v_result; END $$; DROP FUNCTION @extschema@.create_parent(text, text, @extschema@.partition_type, text, int, boolean); DROP TABLE @extschema@.part_grants; ALTER TABLE @extschema@.part_config ADD COLUMN new_type text; UPDATE @extschema@.part_config SET new_type = type; ALTER TABLE @extschema@.part_config ALTER new_type SET NOT NULL; ALTER TABLE @extschema@.part_config DROP COLUMN type; DROP TYPE @extschema@.partition_type; ALTER TABLE @extschema@.part_config RENAME new_type TO type; ALTER TABLE @extschema@.part_config ADD CONSTRAINT part_config_type_check CHECK (@extschema@.check_partition_type(type)); /* * Function to apply ownership & privileges on child tables using parent table as reference */ CREATE OR REPLACE FUNCTION apply_grants(p_parent_table text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_child_table text; v_count int := 0; v_grant text; v_grantees text[]; v_owner text; v_owner_sql text; v_revoke text[]; v_revoke_sql text; v_row record; v_sql text; BEGIN SELECT count(parent_table) INTO v_count FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_count = 0 THEN RAISE EXCEPTION 'Given table is not managed by this extention: %', p_parent_table; END IF; SELECT tableowner INTO v_owner FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; FOR v_child_table IN SELECT inhrelid::regclass FROM pg_catalog.pg_inherits WHERE inhparent::regclass = p_parent_table::regclass ORDER BY inhrelid::regclass ASC LOOP v_grantees := NULL; FOR v_row IN SELECT array_agg(privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP EXECUTE 'GRANT '||array_to_string(v_row.types, ',')||' ON '||v_child_table||' TO '||v_row.grantee; SELECT array_agg(r) INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_row.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE '||array_to_string(v_revoke, ',')||' ON '||v_child_table||' FROM '||v_row.grantee||' CASCADE'; END IF; v_grantees := array_append(v_grantees, v_row.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN SELECT array_agg(r) INTO v_revoke FROM ( SELECT DISTINCT grantee::text AS r FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_child_table EXCEPT SELECT unnest(v_grantees)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE ALL ON '||v_child_table||' FROM '||array_to_string(v_revoke, ','); END IF; END IF; EXECUTE 'ALTER TABLE '||v_child_table||' OWNER TO '||v_owner; END LOOP; END $$; /* * Function to turn a table into the parent of a partition set */ CREATE FUNCTION create_parent(p_parent_table text, p_control text, p_type text, p_interval text, p_premake int DEFAULT 4, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_current_id bigint; v_datetime_string text; v_id_interval bigint; v_job_id bigint; v_jobmon_schema text; v_last_partition_name text; v_old_search_path text; v_partition_time timestamp[]; v_partition_id bigint[]; v_max bigint; v_notnull boolean; v_starting_partition_id bigint; v_step_id bigint; v_tablename text; v_time_interval interval; BEGIN IF position('.' in p_parent_table) = 0 THEN RAISE EXCEPTION 'Parent table must be schema qualified'; END IF; SELECT tablename INTO v_tablename FROM pg_tables WHERE schemaname || '.' || tablename = p_parent_table; IF v_tablename IS NULL THEN RAISE EXCEPTION 'Please create given parent table first: %', p_parent_table; END IF; SELECT attnotnull INTO v_notnull FROM pg_attribute WHERE attrelid = p_parent_table::regclass AND attname = p_control; IF v_notnull = false THEN RAISE EXCEPTION 'Control column (%) for parent table (%) must be NOT NULL', p_control, p_parent_table; END IF; EXECUTE 'LOCK TABLE '||p_parent_table||' IN ACCESS EXCLUSIVE MODE'; SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN SETUP PARENT: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating initial partitions on new parent table: '||p_parent_table); END IF; CASE WHEN p_interval = 'yearly' THEN v_time_interval = '1 year'; v_datetime_string := 'YYYY'; WHEN p_interval = 'quarterly' THEN v_time_interval = '3 months'; v_datetime_string = 'YYYY"q"Q'; WHEN p_interval = 'monthly' THEN v_time_interval = '1 month'; v_datetime_string := 'YYYY_MM'; WHEN p_interval = 'weekly' THEN v_time_interval = '1 week'; v_datetime_string := 'IYYY"w"IW'; WHEN p_interval = 'daily' THEN v_time_interval = '1 day'; v_datetime_string := 'YYYY_MM_DD'; WHEN p_interval = 'hourly' THEN v_time_interval = '1 hour'; v_datetime_string := 'YYYY_MM_DD_HH24MI'; WHEN p_interval = 'half-hour' THEN v_time_interval = '30 mins'; v_datetime_string := 'YYYY_MM_DD_HH24MI'; WHEN p_interval = 'quarter-hour' THEN v_time_interval = '15 mins'; v_datetime_string := 'YYYY_MM_DD_HH24MI'; ELSE IF p_type = 'id-static' OR p_type = 'id-dynamic' THEN v_id_interval := p_interval::bigint; ELSE RAISE EXCEPTION 'Invalid interval for time based partitioning: %', p_interval; END IF; END CASE; IF p_type = 'time-static' OR p_type = 'time-dynamic' THEN FOR i IN 0..p_premake LOOP v_partition_time := array_append(v_partition_time, quote_literal(CURRENT_TIMESTAMP + (v_time_interval*i))::timestamp); END LOOP; INSERT INTO @extschema@.part_config (parent_table, type, part_interval, control, premake, datetime_string) VALUES (p_parent_table, p_type, v_time_interval, p_control, p_premake, v_datetime_string); EXECUTE 'SELECT @extschema@.create_time_partition('||quote_literal(p_parent_table)||','||quote_literal(p_control)||',' ||quote_literal(v_time_interval)||','||quote_literal(v_datetime_string)||','||quote_literal(v_partition_time)||')' INTO v_last_partition_name; -- Doing separate update because create function needs parent table in config table for apply_grants() UPDATE @extschema@.part_config SET last_partition = v_last_partition_name WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Time partitions premade: '||p_premake); END IF; END IF; IF p_type = 'id-static' OR p_type = 'id-dynamic' THEN -- If there is already data, start partitioning with the highest current value EXECUTE 'SELECT COALESCE(max('||p_control||')::bigint, 0) FROM '||p_parent_table||' LIMIT 1' INTO v_max; v_starting_partition_id := v_max - (v_max % v_id_interval); FOR i IN 0..p_premake LOOP v_partition_id = array_append(v_partition_id, (v_id_interval*i)+v_starting_partition_id); END LOOP; INSERT INTO @extschema@.part_config (parent_table, type, part_interval, control, premake) VALUES (p_parent_table, p_type, v_id_interval, p_control, p_premake); EXECUTE 'SELECT @extschema@.create_id_partition('||quote_literal(p_parent_table)||','||quote_literal(p_control)||',' ||v_id_interval||','||quote_literal(v_partition_id)||')' INTO v_last_partition_name; -- Doing separate update because create function needs parent table in config table for apply_grants() UPDATE @extschema@.part_config SET last_partition = v_last_partition_name WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'ID partitions premade: '||p_premake); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating partition function'); END IF; IF p_type = 'time-static' OR p_type = 'time-dynamic' THEN EXECUTE 'SELECT @extschema@.create_time_function('||quote_literal(p_parent_table)||')'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Time function created'); END IF; ELSIF p_type = 'id-static' OR p_type = 'id-dynamic' THEN v_current_id := COALESCE(v_max, 0); EXECUTE 'SELECT @extschema@.create_id_function('||quote_literal(p_parent_table)||','||v_current_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; EXECUTE 'SELECT @extschema@.create_trigger('||quote_literal(p_parent_table)||')'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN CREATE PARENT: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition creation for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'BAD', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Create the next partition in sequence for a time-based partition set */ CREATE OR REPLACE FUNCTION create_next_time_partition (p_parent_table text) RETURNS void LANGUAGE plpgsql AS $$ DECLARE v_control text; v_datetime_string text; v_last_partition text; v_next_partition_timestamp timestamp; v_next_year text; v_part_interval interval; v_quarter text; v_tablename text; v_type text; v_year text; BEGIN SELECT type , part_interval::interval , control , datetime_string , last_partition FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic') INTO v_type, v_part_interval, v_control, v_datetime_string, v_last_partition; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; -- Double check that last created partition exists IF v_last_partition IS NOT NULL THEN SELECT tablename INTO v_tablename FROM pg_tables WHERE schemaname || '.' || tablename = v_last_partition; IF v_tablename IS NULL THEN RAISE EXCEPTION 'ERROR: previous partition table missing. Unable to determine next proper partition in sequence.'; END IF; ELSE RAISE EXCEPTION 'ERROR: last known partition missing from config table for parent table %.', p_parent_table; END IF; -- pull out datetime portion of last partition's tablename to make the next one IF v_part_interval != '3 months' THEN v_next_partition_timestamp := to_timestamp(substring(v_last_partition from char_length(p_parent_table||'_p')+1), v_datetime_string) + v_part_interval; ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(v_last_partition from char_length(p_parent_table||'_p')+1), 'q', 1); v_next_year := extract('year' from to_date(v_year, 'YYYY')+'1year'::interval); v_quarter := split_part(substring(v_last_partition from char_length(p_parent_table||'_p')+1), 'q', 2); CASE WHEN v_quarter = '1' THEN v_next_partition_timestamp := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_next_partition_timestamp := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_next_partition_timestamp := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_next_partition_timestamp := to_timestamp(v_next_year || '-01-01', 'YYYY-MM-DD'); END CASE; END IF; EXECUTE 'SELECT @extschema@.create_time_partition('||quote_literal(p_parent_table)||','||quote_literal(v_control)||','||quote_literal(v_part_interval)||',' ||quote_literal(v_datetime_string)||','||quote_literal(ARRAY[v_next_partition_timestamp])||')' INTO v_last_partition; IF v_last_partition IS NOT NULL THEN UPDATE @extschema@.part_config SET last_partition = v_last_partition WHERE parent_table = p_parent_table; END IF; END $$; /* * Function to create a child table in a time-based partition set */ CREATE OR REPLACE FUNCTION create_time_partition (p_parent_table text, p_control text, p_interval interval, p_datetime_string text, p_partition_times timestamp[]) RETURNS text LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_job_id bigint; v_jobmon_schema text; v_old_search_path text; v_partition_name text; v_partition_timestamp_end timestamp; v_partition_timestamp_start timestamp; v_quarter text; v_step_id bigint; v_tablename text; v_time timestamp; v_year text; BEGIN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_name := p_parent_table || '_p'; IF p_interval = '1 year' OR p_interval = '1 month' OR p_interval = '1 day' OR p_interval = '1 hour' OR p_interval = '30 mins' OR p_interval = '15 mins' THEN v_partition_name := v_partition_name || to_char(v_time, 'YYYY'); IF p_interval = '1 month' OR p_interval = '1 day' OR p_interval = '1 hour' OR p_interval = '30 mins' OR p_interval = '15 mins' THEN v_partition_name := v_partition_name || '_' || to_char(v_time, 'MM'); IF p_interval = '1 day' OR p_interval = '1 hour' OR p_interval = '30 mins' OR p_interval = '15 mins' THEN v_partition_name := v_partition_name || '_' || to_char(v_time, 'DD'); IF p_interval = '1 hour' OR p_interval = '30 mins' OR p_interval = '15 mins' THEN v_partition_name := v_partition_name || '_' || to_char(v_time, 'HH24'); IF p_interval <> '30 mins' AND p_interval <> '15 mins' THEN v_partition_name := v_partition_name || '00'; ELSIF p_interval = '15 mins' THEN IF date_part('minute', v_time) < 15 THEN v_partition_name := v_partition_name || '00'; ELSIF date_part('minute', v_time) >= 15 AND date_part('minute', v_time) < 30 THEN v_partition_name := v_partition_name || '15'; ELSIF date_part('minute', v_time) >= 30 AND date_part('minute', v_time) < 45 THEN v_partition_name := v_partition_name || '30'; ELSE v_partition_name := v_partition_name || '45'; END IF; ELSIF p_interval = '30 mins' THEN IF date_part('minute', v_time) < 30 THEN v_partition_name := v_partition_name || '00'; ELSE v_partition_name := v_partition_name || '30'; END IF; END IF; END IF; -- end hour IF END IF; -- end day IF END IF; -- end month IF ELSIF p_interval = '1 week' THEN v_partition_name := v_partition_name || to_char(v_time, 'IYYY') || 'w' || to_char(v_time, 'IW'); END IF; -- end year/week IF -- pull out datetime portion of last partition's tablename v_partition_timestamp_start := to_timestamp(substring(v_partition_name from char_length(p_parent_table||'_p')+1), p_datetime_string); v_partition_timestamp_end := to_timestamp(substring(v_partition_name from char_length(p_parent_table||'_p')+1), p_datetime_string) + p_interval; -- "Q" is ignored in to_timestamp, so handle special case IF p_interval = '3 months' THEN v_year := to_char(v_time, 'YYYY'); v_quarter := to_char(v_time, 'Q'); v_partition_name := v_partition_name || v_year || 'q' || v_quarter; CASE WHEN v_quarter = '1' THEN v_partition_timestamp_start := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_partition_timestamp_start := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_partition_timestamp_start := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_partition_timestamp_start := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; v_partition_timestamp_end := v_partition_timestamp_start + p_interval; END IF; SELECT schemaname ||'.'|| tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_tablename IS NOT NULL THEN CONTINUE; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_partition_timestamp_start||' to '||(v_partition_timestamp_end-'1sec'::interval)); END IF; IF position('.' in p_parent_table) > 0 THEN v_tablename := substring(v_partition_name from position('.' in v_partition_name)+1); END IF; EXECUTE 'CREATE TABLE '||v_partition_name||' (LIKE '||p_parent_table||' INCLUDING DEFAULTS INCLUDING INDEXES)'; EXECUTE 'ALTER TABLE '||v_partition_name||' ADD CONSTRAINT '||v_tablename||'_partition_check CHECK ('||p_control||'>='||quote_literal(v_partition_timestamp_start)||' AND '||p_control||'<'||quote_literal(v_partition_timestamp_end)||')'; EXECUTE 'ALTER TABLE '||v_partition_name||' INHERIT '||p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); PERFORM close_job(v_job_id); END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN APPLYING GRANTS: '||p_parent_table); v_step_id := add_step(v_job_id, 'Looping through all child tables applying privileges of the parent'); END IF; PERFORM @extschema@.apply_grants(p_parent_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); PERFORM close_job(v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_partition_name; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'BAD', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to create id partitions */ CREATE OR REPLACE FUNCTION create_id_partition (p_parent_table text, p_control text, p_interval bigint, p_partition_ids bigint[]) RETURNS text LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_job_id bigint; v_jobmon_schema text; v_old_search_path text; v_partition_name text; v_step_id bigint; v_tablename text; v_id bigint; BEGIN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; FOREACH v_id IN ARRAY p_partition_ids LOOP v_partition_name := p_parent_table||'_p'||v_id; SELECT schemaname ||'.'|| tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_tablename IS NOT NULL THEN CONTINUE; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_id||' to '||(v_id + p_interval)-1); END IF; IF position('.' in p_parent_table) > 0 THEN v_tablename := substring(v_partition_name from position('.' in v_partition_name)+1); END IF; EXECUTE 'CREATE TABLE '||v_partition_name||' (LIKE '||p_parent_table||' INCLUDING DEFAULTS INCLUDING INDEXES)'; EXECUTE 'ALTER TABLE '||v_partition_name||' ADD CONSTRAINT '||v_tablename||'_partition_check CHECK ('||p_control||'>='||quote_literal(v_id)||' AND '||p_control||'<'||quote_literal(v_id + p_interval)||')'; EXECUTE 'ALTER TABLE '||v_partition_name||' INHERIT '||p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); PERFORM close_job(v_job_id); END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN APPLYING GRANTS: '||p_parent_table); v_step_id := add_step(v_job_id, 'Looping through all child tables applying privileges of the parent'); END IF; PERFORM @extschema@.apply_grants(p_parent_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); PERFORM close_job(v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_partition_name; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'BAD', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; pg_partman-4.6.0/updates/pg_partman--0.4.0--0.4.1.sql000066400000000000000000000467201412761706100214320ustar00rootroot00000000000000-- Changed the privilege management system to apply the current parent's privileges only to new child tables at the time they're created. No longer re-applies privileges to existing child tables. When partition sets grew large, this was causing serious performance problems and was too expensive an operation to run every time a child was created. -- Dropped apply_grants() function. New child table privileges are now managed by the partition creation functions themselves. -- Created reapply_privileges() function to reset the privileges of all child tables in a given partition set. Uses the given parent's privileges at the time the function is run. All new grants will be set and all that don't exist will be revoked. Ownership will be updated if it has changed. -- First round of pgTAP tests. DROP FUNCTION apply_grants(text); DROP TYPE IF EXISTS @extschema@.partition_type; /* * Function to re-apply ownership & privileges on all child tables in a partition set using parent table as reference */ CREATE FUNCTION reapply_privileges(p_parent_table text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_child_owner text; v_child_table text; v_child_grant record; v_count int; v_grant text; v_grantees text[]; v_job_id bigint; v_jobmon_schema text; v_match boolean; v_old_search_path text; v_parent_owner text; v_owner_sql text; v_revoke text[]; v_parent_grant record; v_sql text; v_step_id bigint; BEGIN SELECT count(*) INTO v_count FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_count = 0 THEN RAISE EXCEPTION 'Given table is not managed by this extention: %', p_parent_table; END IF; SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN RE-APPLYING PRIVILEGES TO ALL CHILD TABLES OF: '||p_parent_table); v_step_id := add_step(v_job_id, 'Setting new child table privileges'); END IF; SELECT tableowner INTO v_parent_owner FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; FOR v_child_table IN SELECT inhrelid::regclass FROM pg_catalog.pg_inherits WHERE inhparent::regclass = p_parent_table::regclass ORDER BY inhrelid::regclass ASC LOOP PERFORM update_step(v_step_id, 'PENDING', 'Currently on child partition in ascending order: '||v_child_table); v_grantees := NULL; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP -- Compare parent & child grants. Don't re-apply if it already exists v_match := false; FOR v_child_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_child_table GROUP BY grantee LOOP IF v_parent_grant.types = v_child_grant.types AND v_parent_grant.grantee = v_child_grant.grantee THEN v_match := true; END IF; END LOOP; IF v_match = false THEN EXECUTE 'GRANT '||array_to_string(v_parent_grant.types, ',')||' ON '||v_child_table||' TO '||v_parent_grant.grantee; SELECT array_agg(r) INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE '||array_to_string(v_revoke, ',')||' ON '||v_child_table||' FROM '||v_parent_grant.grantee||' CASCADE'; END IF; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN SELECT array_agg(r) INTO v_revoke FROM ( SELECT DISTINCT grantee::text AS r FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_child_table EXCEPT SELECT unnest(v_grantees)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE ALL ON '||v_child_table||' FROM '||array_to_string(v_revoke, ','); END IF; END IF; SELECT tableowner INTO v_child_owner FROM pg_tables WHERE schemaname ||'.'|| tablename = v_child_table; IF v_parent_owner <> v_child_owner THEN EXECUTE 'ALTER TABLE '||v_child_table||' OWNER TO '||v_parent_owner; END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); PERFORM close_job(v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'BAD', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to create id partitions */ CREATE OR REPLACE FUNCTION create_id_partition (p_parent_table text, p_control text, p_interval bigint, p_partition_ids bigint[]) RETURNS text LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_grantees text[]; v_job_id bigint; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_partition_name text; v_revoke text[]; v_step_id bigint; v_tablename text; v_id bigint; BEGIN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; SELECT tableowner INTO v_parent_owner FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; FOREACH v_id IN ARRAY p_partition_ids LOOP v_partition_name := p_parent_table||'_p'||v_id; SELECT schemaname ||'.'|| tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_tablename IS NOT NULL THEN CONTINUE; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_id||' to '||(v_id + p_interval)-1); END IF; IF position('.' in p_parent_table) > 0 THEN v_tablename := substring(v_partition_name from position('.' in v_partition_name)+1); END IF; EXECUTE 'CREATE TABLE '||v_partition_name||' (LIKE '||p_parent_table||' INCLUDING DEFAULTS INCLUDING INDEXES)'; EXECUTE 'ALTER TABLE '||v_partition_name||' ADD CONSTRAINT '||v_tablename||'_partition_check CHECK ('||p_control||'>='||quote_literal(v_id)||' AND '||p_control||'<'||quote_literal(v_id + p_interval)||')'; EXECUTE 'ALTER TABLE '||v_partition_name||' INHERIT '||p_parent_table; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP EXECUTE 'GRANT '||array_to_string(v_parent_grant.types, ',')||' ON '||v_partition_name||' TO '||v_parent_grant.grantee; SELECT array_agg(r) INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE '||array_to_string(v_revoke, ',')||' ON '||v_partition_name||' FROM '||v_parent_grant.grantee||' CASCADE'; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN SELECT array_agg(r) INTO v_revoke FROM ( SELECT DISTINCT grantee::text AS r FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_partition_name EXCEPT SELECT unnest(v_grantees)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE ALL ON '||v_partition_name||' FROM '||array_to_string(v_revoke, ','); END IF; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' OWNER TO '||v_parent_owner; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); PERFORM close_job(v_job_id); END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_partition_name; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'BAD', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to create a child table in a time-based partition set */ CREATE OR REPLACE FUNCTION create_time_partition (p_parent_table text, p_control text, p_interval interval, p_datetime_string text, p_partition_times timestamp[]) RETURNS text LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_grantees text[]; v_job_id bigint; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_partition_name text; v_partition_timestamp_end timestamp; v_partition_timestamp_start timestamp; v_quarter text; v_revoke text[]; v_step_id bigint; v_tablename text; v_time timestamp; v_year text; BEGIN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; SELECT tableowner INTO v_parent_owner FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_name := p_parent_table || '_p'; IF p_interval = '1 year' OR p_interval = '1 month' OR p_interval = '1 day' OR p_interval = '1 hour' OR p_interval = '30 mins' OR p_interval = '15 mins' THEN v_partition_name := v_partition_name || to_char(v_time, 'YYYY'); IF p_interval = '1 month' OR p_interval = '1 day' OR p_interval = '1 hour' OR p_interval = '30 mins' OR p_interval = '15 mins' THEN v_partition_name := v_partition_name || '_' || to_char(v_time, 'MM'); IF p_interval = '1 day' OR p_interval = '1 hour' OR p_interval = '30 mins' OR p_interval = '15 mins' THEN v_partition_name := v_partition_name || '_' || to_char(v_time, 'DD'); IF p_interval = '1 hour' OR p_interval = '30 mins' OR p_interval = '15 mins' THEN v_partition_name := v_partition_name || '_' || to_char(v_time, 'HH24'); IF p_interval <> '30 mins' AND p_interval <> '15 mins' THEN v_partition_name := v_partition_name || '00'; ELSIF p_interval = '15 mins' THEN IF date_part('minute', v_time) < 15 THEN v_partition_name := v_partition_name || '00'; ELSIF date_part('minute', v_time) >= 15 AND date_part('minute', v_time) < 30 THEN v_partition_name := v_partition_name || '15'; ELSIF date_part('minute', v_time) >= 30 AND date_part('minute', v_time) < 45 THEN v_partition_name := v_partition_name || '30'; ELSE v_partition_name := v_partition_name || '45'; END IF; ELSIF p_interval = '30 mins' THEN IF date_part('minute', v_time) < 30 THEN v_partition_name := v_partition_name || '00'; ELSE v_partition_name := v_partition_name || '30'; END IF; END IF; END IF; -- end hour IF END IF; -- end day IF END IF; -- end month IF ELSIF p_interval = '1 week' THEN v_partition_name := v_partition_name || to_char(v_time, 'IYYY') || 'w' || to_char(v_time, 'IW'); END IF; -- end year/week IF -- pull out datetime portion of last partition's tablename v_partition_timestamp_start := to_timestamp(substring(v_partition_name from char_length(p_parent_table||'_p')+1), p_datetime_string); v_partition_timestamp_end := to_timestamp(substring(v_partition_name from char_length(p_parent_table||'_p')+1), p_datetime_string) + p_interval; -- "Q" is ignored in to_timestamp, so handle special case IF p_interval = '3 months' THEN v_year := to_char(v_time, 'YYYY'); v_quarter := to_char(v_time, 'Q'); v_partition_name := v_partition_name || v_year || 'q' || v_quarter; CASE WHEN v_quarter = '1' THEN v_partition_timestamp_start := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_partition_timestamp_start := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_partition_timestamp_start := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_partition_timestamp_start := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; v_partition_timestamp_end := v_partition_timestamp_start + p_interval; END IF; SELECT schemaname ||'.'|| tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_tablename IS NOT NULL THEN CONTINUE; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_partition_timestamp_start||' to '||(v_partition_timestamp_end-'1sec'::interval)); END IF; IF position('.' in p_parent_table) > 0 THEN v_tablename := substring(v_partition_name from position('.' in v_partition_name)+1); END IF; EXECUTE 'CREATE TABLE '||v_partition_name||' (LIKE '||p_parent_table||' INCLUDING DEFAULTS INCLUDING INDEXES)'; EXECUTE 'ALTER TABLE '||v_partition_name||' ADD CONSTRAINT '||v_tablename||'_partition_check CHECK ('||p_control||'>='||quote_literal(v_partition_timestamp_start)||' AND '||p_control||'<'||quote_literal(v_partition_timestamp_end)||')'; EXECUTE 'ALTER TABLE '||v_partition_name||' INHERIT '||p_parent_table; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP EXECUTE 'GRANT '||array_to_string(v_parent_grant.types, ',')||' ON '||v_partition_name||' TO '||v_parent_grant.grantee; SELECT array_agg(r) INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE '||array_to_string(v_revoke, ',')||' ON '||v_partition_name||' FROM '||v_parent_grant.grantee||' CASCADE'; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN SELECT array_agg(r) INTO v_revoke FROM ( SELECT DISTINCT grantee::text AS r FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_partition_name EXCEPT SELECT unnest(v_grantees)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE ALL ON '||v_partition_name||' FROM '||array_to_string(v_revoke, ','); END IF; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' OWNER TO '||v_parent_owner; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); PERFORM close_job(v_job_id); END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_partition_name; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'BAD', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; pg_partman-4.6.0/updates/pg_partman--0.4.1--0.4.2.sql000066400000000000000000000673231412761706100214360ustar00rootroot00000000000000-- The static partitioning trigger function can now handle partitions based on the configured premake value. For example, the default premake value is 4 so it can now handle data for the current partition, 4 previous partitions and 4 future partitions. Changing the premake value will cause the trigger function to be changed appropriately the next time a partition is automatically created. Except for initial setup, at no time does the automated partitioning system create old partitions (see the create_prev_* functions if you need to do this). If you change the premake value and there is no previous partition for it to put data in, it will go to the parent table. -- create_parent() now accounts for the new static partitioning rules. For time-static, it will create the current partition as well as previous and future partitions equal to the configured premake number (default premake being 4, you will end up with 9 partitions). For id-static, it will only create previous partitions if the resulting rules handle id values greater than zero. So if you're starting from zero you will only have future partitions created, and no previous. -- Constraint now ensures that premake value is greater than zero. -- create_parent() now ensures interval value for serial partitioning is greater than zero. -- Much more extensive pgTAP tests. ALTER TABLE @extschema@.part_config ADD CONSTRAINT positive_premake_check CHECK (premake > 0); /* * Create the trigger function for the parent table of a time-based partition set */ CREATE OR REPLACE FUNCTION create_time_function(p_parent_table text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_current_partition_name text; v_current_partition_timestamp timestamp; v_datetime_string text; v_final_partition_timestamp timestamp; v_job_id bigint; v_jobmon_schema text; v_old_search_path text; v_next_partition_name text; v_next_partition_timestamp timestamp; v_part_interval interval; v_premake int; v_prev_partition_name text; v_prev_partition_timestamp timestamp; v_step_id bigint; v_trig_func text; v_type text; BEGIN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE FUNCTION: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating partition function for table '||p_parent_table); END IF; SELECT type , part_interval::interval , control , premake , datetime_string INTO v_type , v_part_interval , v_control , v_premake , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_type = 'time-static' THEN CASE WHEN v_part_interval = '15 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_part_interval = '30 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_part_interval = '1 hour' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_part_interval = '1 day' THEN v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_part_interval = '1 week' THEN v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_part_interval = '1 month' THEN v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_part_interval = '3 months' THEN v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_part_interval = '1 year' THEN v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; v_current_partition_name := p_parent_table || '_p' || to_char(v_current_partition_timestamp, v_datetime_string); v_next_partition_timestamp := v_current_partition_timestamp + v_part_interval::interval; v_trig_func := 'CREATE OR REPLACE FUNCTION '||p_parent_table||'_part_trig_func() RETURNS trigger LANGUAGE plpgsql AS $t$ BEGIN IF TG_OP = ''INSERT'' THEN IF NEW.'||v_control||' >= '||quote_literal(v_current_partition_timestamp)||' AND NEW.'||v_control||' < '||quote_literal(v_next_partition_timestamp)|| ' THEN INSERT INTO '||v_current_partition_name||' VALUES (NEW.*); '; FOR i IN 1..v_premake LOOP v_prev_partition_timestamp := v_current_partition_timestamp - (v_part_interval::interval * i); v_next_partition_timestamp := v_current_partition_timestamp + (v_part_interval::interval * i); v_final_partition_timestamp := v_next_partition_timestamp + (v_part_interval::interval); v_prev_partition_name := p_parent_table || '_p' || to_char(v_prev_partition_timestamp, v_datetime_string); v_next_partition_name := p_parent_table || '_p' || to_char(v_next_partition_timestamp, v_datetime_string); v_trig_func := v_trig_func ||' ELSIF NEW.'||v_control||' >= '||quote_literal(v_prev_partition_timestamp)||' AND NEW.'||v_control||' < '|| quote_literal(v_prev_partition_timestamp + v_part_interval::interval)|| ' THEN INSERT INTO '||v_prev_partition_name||' VALUES (NEW.*); ELSIF NEW.'||v_control||' >= '||quote_literal(v_next_partition_timestamp)||' AND NEW.'||v_control||' < '|| quote_literal(v_final_partition_timestamp)|| ' THEN INSERT INTO '||v_next_partition_name||' VALUES (NEW.*); '; END LOOP; v_trig_func := v_trig_func ||' ELSE RETURN NEW; END IF; END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for current time interval: '|| v_current_partition_timestamp||' to '||(v_final_partition_timestamp-'1sec'::interval)); END IF; ELSIF v_type = 'time-dynamic' THEN v_trig_func := 'CREATE OR REPLACE FUNCTION '||p_parent_table||'_part_trig_func() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_partition_name text; v_partition_timestamp timestamp; v_schemaname text; v_tablename text; BEGIN IF TG_OP = ''INSERT'' THEN '; CASE WHEN v_part_interval = '15 mins' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''hour'', NEW.'||v_control||') + ''15min''::interval * floor(date_part(''minute'', NEW.'||v_control||') / 15.0);'; WHEN v_part_interval = '30 mins' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''hour'', NEW.'||v_control||') + ''30min''::interval * floor(date_part(''minute'', NEW.'||v_control||') / 30.0);'; WHEN v_part_interval = '1 hour' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''hour'', NEW.'||v_control||');'; WHEN v_part_interval = '1 day' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''day'', NEW.'||v_control||');'; WHEN v_part_interval = '1 week' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''week'', NEW.'||v_control||');'; WHEN v_part_interval = '1 month' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''month'', NEW.'||v_control||');'; WHEN v_part_interval = '3 months' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''quarter'', NEW.'||v_control||');'; WHEN v_part_interval = '1 year' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''year'', NEW.'||v_control||');'; END CASE; v_trig_func := v_trig_func||' v_partition_name := '''||p_parent_table||'_p''|| to_char(v_partition_timestamp, '||quote_literal(v_datetime_string)||'); v_schemaname := split_part(v_partition_name, ''.'', 1); v_tablename := split_part(v_partition_name, ''.'', 2); SELECT count(*) INTO v_count FROM pg_tables WHERE schemaname = v_schemaname AND tablename = v_tablename; IF v_count > 0 THEN EXECUTE ''INSERT INTO ''||v_partition_name||'' VALUES($1.*)'' USING NEW; ELSE RETURN NEW; END IF; END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for dynamic time table: '||p_parent_table); END IF; ELSE RAISE EXCEPTION 'ERROR: Invalid time partitioning type given: %', v_type; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN CREATE FUNCTION: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition function maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'BAD', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Create the trigger function for the parent table of an id-based partition set */ CREATE OR REPLACE FUNCTION create_id_function(p_parent_table text, p_current_id bigint) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_current_partition_name text; v_current_partition_id bigint; v_datetime_string text; v_final_partition_id bigint; v_job_id bigint; v_jobmon_schema text; v_last_partition text; v_next_partition_id bigint; v_next_partition_name text; v_old_search_path text; v_part_interval bigint; v_premake int; v_prev_partition_id bigint; v_prev_partition_name text; v_step_id bigint; v_trig_func text; v_type text; BEGIN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE FUNCTION: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating partition function for table '||p_parent_table); END IF; SELECT type , part_interval::bigint , control , premake , last_partition INTO v_type , v_part_interval , v_control , v_premake , v_last_partition FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_type = 'id-static' THEN v_current_partition_id := p_current_id - (p_current_id % v_part_interval); v_next_partition_id := v_current_partition_id + v_part_interval; v_current_partition_name := p_parent_table || '_p' || v_current_partition_id::text; v_trig_func := 'CREATE OR REPLACE FUNCTION '||p_parent_table||'_part_trig_func() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_current_partition_id bigint; v_last_partition text := '||quote_literal(v_last_partition)||'; v_next_partition_id bigint; v_next_partition_name text; BEGIN IF TG_OP = ''INSERT'' THEN IF NEW.'||v_control||' >= '||v_current_partition_id||' AND NEW.'||v_control||' < '||v_next_partition_id|| ' THEN INSERT INTO '||v_current_partition_name||' VALUES (NEW.*); '; FOR i IN 1..v_premake LOOP v_prev_partition_id := v_current_partition_id - (v_part_interval * i); v_next_partition_id := v_current_partition_id + (v_part_interval * i); v_final_partition_id := v_next_partition_id + v_part_interval; v_prev_partition_name := p_parent_table || '_p' || v_prev_partition_id::text; v_next_partition_name := p_parent_table || '_p' || v_next_partition_id::text; -- Only make previous partitions if they're starting above zero IF v_prev_partition_id >= 0 THEN v_trig_func := v_trig_func ||' ELSIF NEW.'||v_control||' >= '||v_prev_partition_id||' AND NEW.'||v_control||' < '||v_prev_partition_id + v_part_interval|| ' THEN INSERT INTO '||v_prev_partition_name||' VALUES (NEW.*); '; END IF; v_trig_func := v_trig_func ||' ELSIF NEW.'||v_control||' >= '||v_next_partition_id||' AND NEW.'||v_control||' < '||v_final_partition_id|| ' THEN INSERT INTO '||v_next_partition_name||' VALUES (NEW.*); '; END LOOP; v_trig_func := v_trig_func ||' ELSE RETURN NEW; END IF; v_current_partition_id := NEW.'||v_control||' - (NEW.'||v_control||' % '||v_part_interval||'); IF (NEW.'||v_control||' % '||v_part_interval||') > ('||v_part_interval||' / 2) THEN v_next_partition_id := (substring(v_last_partition from char_length('||quote_literal(p_parent_table||'_p')||')+1)::bigint) + '||v_part_interval||'; IF ((v_next_partition_id - v_current_partition_id) / '||v_part_interval||') <= '||v_premake||' THEN v_next_partition_name := @extschema@.create_id_partition('||quote_literal(p_parent_table)||', '||quote_literal(v_control)||',' ||v_part_interval||', ARRAY[v_next_partition_id]); UPDATE @extschema@.part_config SET last_partition = v_next_partition_name WHERE parent_table = '||quote_literal(p_parent_table)||'; PERFORM @extschema@.create_id_function('||quote_literal(p_parent_table)||', NEW.'||v_control||'); END IF; END IF; END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for current id interval: '||v_current_partition_id||' to '||v_final_partition_id-1); END IF; ELSIF v_type = 'id-dynamic' THEN v_trig_func := 'CREATE OR REPLACE FUNCTION '||p_parent_table||'_part_trig_func() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_current_partition_id bigint; v_current_partition_name text; v_last_partition text := '||quote_literal(v_last_partition)||'; v_last_partition_id bigint; v_next_partition_id bigint; v_next_partition_name text; v_schemaname text; v_tablename text; BEGIN IF TG_OP = ''INSERT'' THEN v_current_partition_id := NEW.'||v_control||' - (NEW.'||v_control||' % '||v_part_interval||'); v_current_partition_name := '''||p_parent_table||'_p''||v_current_partition_id; IF (NEW.'||v_control||' % '||v_part_interval||') > ('||v_part_interval||' / 2) THEN v_last_partition_id = substring(v_last_partition from char_length('||quote_literal(p_parent_table||'_p')||')+1)::bigint; v_next_partition_id := v_last_partition_id + '||v_part_interval||'; IF NEW.'||v_control||' >= v_next_partition_id THEN RETURN NEW; END IF; IF ((v_next_partition_id - v_current_partition_id) / '||quote_literal(v_part_interval)||') <= '||quote_literal(v_premake)||' THEN v_next_partition_name := @extschema@.create_id_partition('||quote_literal(p_parent_table)||', '||quote_literal(v_control)||',' ||quote_literal(v_part_interval)||', ARRAY[v_next_partition_id]); IF v_next_partition_name IS NOT NULL THEN UPDATE @extschema@.part_config SET last_partition = v_next_partition_name WHERE parent_table = '||quote_literal(p_parent_table)||'; PERFORM @extschema@.create_id_function('||quote_literal(p_parent_table)||', NEW.'||v_control||'); END IF; END IF; END IF; v_schemaname := split_part(v_current_partition_name, ''.'', 1); v_tablename := split_part(v_current_partition_name, ''.'', 2); SELECT count(*) INTO v_count FROM pg_tables WHERE schemaname = v_schemaname AND tablename = v_tablename; IF v_count > 0 THEN EXECUTE ''INSERT INTO ''||v_current_partition_name||'' VALUES($1.*)'' USING NEW; ELSE RETURN NEW; END IF; END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for dynamic id table: '||p_parent_table); END IF; ELSE RAISE EXCEPTION 'ERROR: Invalid id partitioning type given: %', v_type; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN CREATE FUNCTION: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition function maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'BAD', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to turn a table into the parent of a partition set */ CREATE OR REPLACE FUNCTION create_parent(p_parent_table text, p_control text, p_type text, p_interval text, p_premake int DEFAULT 4, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_current_id bigint; v_datetime_string text; v_id_interval bigint; v_job_id bigint; v_jobmon_schema text; v_last_partition_name text; v_old_search_path text; v_partition_time timestamp[]; v_partition_id bigint[]; v_max bigint; v_notnull boolean; v_starting_partition_id bigint; v_step_id bigint; v_tablename text; v_time_interval interval; BEGIN IF position('.' in p_parent_table) = 0 THEN RAISE EXCEPTION 'Parent table must be schema qualified'; END IF; SELECT tablename INTO v_tablename FROM pg_tables WHERE schemaname || '.' || tablename = p_parent_table; IF v_tablename IS NULL THEN RAISE EXCEPTION 'Please create given parent table first: %', p_parent_table; END IF; SELECT attnotnull INTO v_notnull FROM pg_attribute WHERE attrelid = p_parent_table::regclass AND attname = p_control; IF v_notnull = false THEN RAISE EXCEPTION 'Control column (%) for parent table (%) must be NOT NULL', p_control, p_parent_table; END IF; EXECUTE 'LOCK TABLE '||p_parent_table||' IN ACCESS EXCLUSIVE MODE'; SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN SETUP PARENT: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating initial partitions on new parent table: '||p_parent_table); END IF; CASE WHEN p_interval = 'yearly' THEN v_time_interval = '1 year'; v_datetime_string := 'YYYY'; WHEN p_interval = 'quarterly' THEN v_time_interval = '3 months'; v_datetime_string = 'YYYY"q"Q'; WHEN p_interval = 'monthly' THEN v_time_interval = '1 month'; v_datetime_string := 'YYYY_MM'; WHEN p_interval = 'weekly' THEN v_time_interval = '1 week'; v_datetime_string := 'IYYY"w"IW'; WHEN p_interval = 'daily' THEN v_time_interval = '1 day'; v_datetime_string := 'YYYY_MM_DD'; WHEN p_interval = 'hourly' THEN v_time_interval = '1 hour'; v_datetime_string := 'YYYY_MM_DD_HH24MI'; WHEN p_interval = 'half-hour' THEN v_time_interval = '30 mins'; v_datetime_string := 'YYYY_MM_DD_HH24MI'; WHEN p_interval = 'quarter-hour' THEN v_time_interval = '15 mins'; v_datetime_string := 'YYYY_MM_DD_HH24MI'; ELSE IF p_type = 'id-static' OR p_type = 'id-dynamic' THEN v_id_interval := p_interval::bigint; IF v_id_interval <= 0 THEN RAISE EXCEPTION 'Interval for serial partitioning must be greater than zero'; END IF; ELSE RAISE EXCEPTION 'Invalid interval for time based partitioning: %', p_interval; END IF; END CASE; IF p_type = 'time-static' OR p_type = 'time-dynamic' THEN FOR i IN 0..p_premake LOOP IF i > 0 THEN -- also create previous partitions equal to premake, but avoid duplicating current v_partition_time := array_append(v_partition_time, quote_literal(CURRENT_TIMESTAMP - (v_time_interval*i))::timestamp); END IF; v_partition_time := array_append(v_partition_time, quote_literal(CURRENT_TIMESTAMP + (v_time_interval*i))::timestamp); END LOOP; INSERT INTO @extschema@.part_config (parent_table, type, part_interval, control, premake, datetime_string) VALUES (p_parent_table, p_type, v_time_interval, p_control, p_premake, v_datetime_string); EXECUTE 'SELECT @extschema@.create_time_partition('||quote_literal(p_parent_table)||','||quote_literal(p_control)||',' ||quote_literal(v_time_interval)||','||quote_literal(v_datetime_string)||','||quote_literal(v_partition_time)||')' INTO v_last_partition_name; -- Doing separate update because create function needs parent table in config table for apply_grants() UPDATE @extschema@.part_config SET last_partition = v_last_partition_name WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Time partitions premade: '||p_premake); END IF; END IF; IF p_type = 'id-static' OR p_type = 'id-dynamic' THEN -- If there is already data, start partitioning with the highest current value EXECUTE 'SELECT COALESCE(max('||p_control||')::bigint, 0) FROM '||p_parent_table||' LIMIT 1' INTO v_max; 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 IF (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_append(v_partition_id, (v_starting_partition_id - v_id_interval*i)); END IF; v_partition_id = array_append(v_partition_id, (v_id_interval*i) + v_starting_partition_id); END LOOP; INSERT INTO @extschema@.part_config (parent_table, type, part_interval, control, premake) VALUES (p_parent_table, p_type, v_id_interval, p_control, p_premake); EXECUTE 'SELECT @extschema@.create_id_partition('||quote_literal(p_parent_table)||','||quote_literal(p_control)||',' ||v_id_interval||','||quote_literal(v_partition_id)||')' INTO v_last_partition_name; -- Doing separate update because create function needs parent table in config table for apply_grants() UPDATE @extschema@.part_config SET last_partition = v_last_partition_name WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'ID partitions premade: '||p_premake); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating partition function'); END IF; IF p_type = 'time-static' OR p_type = 'time-dynamic' THEN EXECUTE 'SELECT @extschema@.create_time_function('||quote_literal(p_parent_table)||')'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Time function created'); END IF; ELSIF p_type = 'id-static' OR p_type = 'id-dynamic' THEN v_current_id := COALESCE(v_max, 0); EXECUTE 'SELECT @extschema@.create_id_function('||quote_literal(p_parent_table)||','||v_current_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; EXECUTE 'SELECT @extschema@.create_trigger('||quote_literal(p_parent_table)||')'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN CREATE PARENT: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition creation for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'BAD', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; pg_partman-4.6.0/updates/pg_partman--0.4.2--1.0.0.sql000066400000000000000000001765331412761706100214360ustar00rootroot00000000000000-- New functions to undo partitioning. These all either move or copy data from the child tables and put it into the parent. All have an option to allow you either uninherit the child tables (default) or drop them when all their data has been put into the parent. -- undo_partition_time() & undo_partition_id are functions that move the data from the child partitions to the parent tables. Data is deleted from the child table and inserted to the parent. These functions allow smaller interval batches to be given as a parameter and are better able to handle larger partitioning sets. -- undo_partition() can work on an any parent/child table set in PostgreSQL, not just partition sets created by pg_partman. Just pass it the name of the parent table. This method only copies the data out of the child tables instead of deleting it, allowing you to keep all the partitioned data if desired. Because of this it can only process an entire partition at a time and cannot handle batches smaller than the partition interval. -- Changed create_prev_id_partition() to partition_data_id() & create_prev_time_partition() to partition_data_time(). This clarifies what these actually do since they don't always create a partition nor is it always necessarily "previous" data. -- Changed how the above functions work to move data from parent into partitions. You can now feed them a smaller interval value for the rows that you'd like moved instead of it always moving exactly one entire partition of data. This allows smaller batch sizes when you've got a lot of data even in just one partition. That interval is now the second parameter. A third parameter can tell it how many of those interval batches you'd like to move in a single run of the function. Both of these parameters are optional. If not given, the interval defaults to the partition interval and the batch count is one (so it works exactly like it used to with no parameters but the parent table given). -- Partition premake system is now able to catch up if it falls behind for some reason. Also makes it so that if the premake value is increased, within the next few runs it will have that many partitions premade automatically. -- Bug fix: create_time_partition() & create_time_function() now handle the "timestamp with time zone" data type much better. Was getting some mismatches in the trigger rules and table constraints when timestamptz was in use on server not running in UTC/GMT time. Would cause constraint violations during data insert at certain time boundaries. If you ran into this issue, there are two ways to fix it: 1) Manually recreate the constraints for the most recent partitions and any future partitions already created. You may have to move some data around as well. 2) Use the new undo functions to move all the data back into the parent table and then repartition again using the partition_data_* functions. This will fix the issue for all partitions. -- Bug fix: Determining how many partitions to premake in run_maintenance() is now more accurate. Previous date math would occasionally premake 1 extra partition depending on the time differences. This can still occur with weekly partitioning due to differing month lengths (especially February) and daylight savings. Doesn't hurt anything and will self-correct. -- Much more complete pgTAP test suite. DROP FUNCTION create_prev_id_partition(text, int); DROP FUNCTION create_prev_time_partition(text, int); ALTER TABLE @extschema@.part_config ADD COLUMN undo_in_progress boolean NOT NULL DEFAULT false; /* * Populate the child table(s) of an id-based partition set with old data from the original parent */ CREATE FUNCTION partition_data_id(p_parent_table text, p_batch_interval int DEFAULT NULL, p_batch_count int DEFAULT 1) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_last_partition_name text; v_max_partition_id bigint; v_min_control bigint; v_min_partition_id bigint; v_part_interval bigint; v_partition_id bigint[]; v_rowcount bigint; v_sql text; v_total_rows bigint := 0; BEGIN SELECT part_interval::bigint, control INTO v_part_interval, v_control FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF p_batch_interval IS NULL OR p_batch_interval > v_part_interval THEN p_batch_interval := v_part_interval; END IF; FOR i IN 1..p_batch_count LOOP EXECUTE 'SELECT min('||v_control||') FROM ONLY '||p_parent_table INTO v_min_control; IF v_min_control IS NULL THEN RETURN 0; END IF; v_min_partition_id = v_min_control - (v_min_control % v_part_interval); v_partition_id := ARRAY[v_min_partition_id]; RAISE NOTICE 'v_partition_id: %',v_partition_id; IF (v_min_control + p_batch_interval) >= (v_min_partition_id + v_part_interval) THEN v_max_partition_id := v_min_partition_id + v_part_interval; ELSE v_max_partition_id := v_min_control + p_batch_interval; END IF; RAISE NOTICE 'v_max_partition_id: %',v_max_partition_id; v_sql := 'SELECT @extschema@.create_id_partition('||quote_literal(p_parent_table)||','||quote_literal(v_control)||',' ||v_part_interval||','||quote_literal(v_partition_id)||')'; RAISE NOTICE 'v_sql: %', v_sql; EXECUTE v_sql INTO v_last_partition_name; v_sql := 'WITH partition_data AS ( DELETE FROM ONLY '||p_parent_table||' WHERE '||v_control||' >= '||v_min_control|| ' AND '||v_control||' < '||v_max_partition_id||' RETURNING *) INSERT INTO '||v_last_partition_name||' SELECT * FROM partition_data'; RAISE NOTICE 'v_sql: %', v_sql; EXECUTE v_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; RETURN v_total_rows; END $$; /* * Populate the child table(s) of a time-based partition set with old data from the original parent */ CREATE FUNCTION partition_data_time(p_parent_table text, p_batch_interval interval DEFAULT NULL, p_batch_count int DEFAULT 1) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_datetime_string text; v_last_partition_name text; v_max_partition_timestamp timestamp; v_min_control timestamp; v_min_partition_timestamp timestamp; v_part_interval interval; v_partition_timestamp timestamp[]; v_rowcount bigint; v_sql text; v_total_rows bigint := 0; BEGIN SELECT part_interval::interval, control, datetime_string INTO v_part_interval, v_control, v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF p_batch_interval IS NULL OR p_batch_interval > v_part_interval THEN p_batch_interval := v_part_interval; END IF; FOR i IN 1..p_batch_count LOOP EXECUTE 'SELECT min('||v_control||') FROM ONLY '||p_parent_table INTO v_min_control; IF v_min_control IS NULL THEN RETURN 0; END IF; CASE WHEN v_part_interval = '15 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_min_control) + '15min'::interval * floor(date_part('minute', v_min_control) / 15.0); WHEN v_part_interval = '30 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_min_control) + '30min'::interval * floor(date_part('minute', v_min_control) / 30.0); WHEN v_part_interval = '1 hour' THEN v_min_partition_timestamp := date_trunc('hour', v_min_control); WHEN v_part_interval = '1 day' THEN v_min_partition_timestamp := date_trunc('day', v_min_control); WHEN v_part_interval = '1 week' THEN v_min_partition_timestamp := date_trunc('week', v_min_control); WHEN v_part_interval = '1 month' THEN v_min_partition_timestamp := date_trunc('month', v_min_control); WHEN v_part_interval = '3 months' THEN v_min_partition_timestamp := date_trunc('quarter', v_min_control); WHEN v_part_interval = '1 year' THEN v_min_partition_timestamp := date_trunc('year', v_min_control); END CASE; v_partition_timestamp := ARRAY[v_min_partition_timestamp]; RAISE NOTICE 'v_partition_timestamp: %',v_partition_timestamp; IF (v_min_control + p_batch_interval) >= (v_min_partition_timestamp + v_part_interval) THEN v_max_partition_timestamp := v_min_partition_timestamp + v_part_interval; ELSE v_max_partition_timestamp := v_min_control + p_batch_interval; END IF; RAISE NOTICE 'v_max_partition_timestamp: %',v_max_partition_timestamp; v_sql := 'SELECT @extschema@.create_time_partition('||quote_literal(p_parent_table)||','||quote_literal(v_control)||',' ||quote_literal(v_part_interval)||','||quote_literal(v_datetime_string)||','||quote_literal(v_partition_timestamp)||')'; RAISE NOTICE 'v_sql: %', v_sql; EXECUTE v_sql INTO v_last_partition_name; v_sql := 'WITH partition_data AS ( DELETE FROM ONLY '||p_parent_table||' WHERE '||v_control||' >= '||quote_literal(v_min_control)|| ' AND '||v_control||' < '||quote_literal(v_max_partition_timestamp)||' RETURNING *) INSERT INTO '||v_last_partition_name||' SELECT * FROM partition_data'; RAISE NOTICE 'v_sql: %', v_sql; EXECUTE v_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; RETURN v_total_rows; END $$; /* * Function to manage pre-creation of the next partitions in a time-based partition set. * Also manages dropping old partitions if the retention option is set. */ CREATE OR REPLACE FUNCTION run_maintenance() RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_create_count int := 0; v_current_partition_timestamp timestamp; v_datetime_string text; v_drop_count int := 0; v_job_id bigint; v_jobmon_schema text; v_last_partition_timestamp timestamp; v_old_search_path text; v_premade_count real; v_quarter text; v_step_id bigint; v_row record; v_year text; BEGIN v_adv_lock := pg_try_advisory_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman maintenance already running.'; RETURN; END IF; SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN RUN MAINTENANCE'); v_step_id := add_step(v_job_id, 'Running maintenance loop'); END IF; FOR v_row IN SELECT parent_table , type , part_interval::interval , control , premake , datetime_string , last_partition , undo_in_progress FROM @extschema@.part_config WHERE type = 'time-static' OR type = 'time-dynamic' LOOP CONTINUE WHEN v_row.undo_in_progress; CASE WHEN v_row.part_interval = '15 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_row.part_interval = '30 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_row.part_interval = '1 hour' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '1 day' THEN v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '1 week' THEN v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '1 month' THEN v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '3 months' THEN v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '1 year' THEN v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; IF v_row.part_interval != '3 months' THEN v_last_partition_timestamp := to_timestamp(substring(v_row.last_partition from char_length(v_row.parent_table||'_p')+1), v_row.datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(v_row.last_partition from char_length(v_row.parent_table||'_p')+1), 'q', 1); v_quarter := split_part(substring(v_row.last_partition from char_length(v_row.parent_table||'_p')+1), 'q', 2); CASE WHEN v_quarter = '1' THEN v_last_partition_timestamp := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_last_partition_timestamp := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_last_partition_timestamp := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_last_partition_timestamp := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; -- Check and see how many premade partitions there are. If it's less than premake in config table, make another v_premade_count = EXTRACT('epoch' FROM age(v_last_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.part_interval::interval); -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed. WHILE v_premade_count < v_row.premake LOOP EXECUTE 'SELECT @extschema@.create_next_time_partition('||quote_literal(v_row.parent_table)||')'; v_create_count := v_create_count + 1; IF v_row.type = 'time-static' THEN EXECUTE 'SELECT @extschema@.create_time_function('||quote_literal(v_row.parent_table)||')'; END IF; v_last_partition_timestamp := v_last_partition_timestamp + v_row.part_interval; v_premade_count = EXTRACT('epoch' FROM age(v_last_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.part_interval::interval); END LOOP; END LOOP; -- end of creation loop -- Manage dropping old partitions if retention option is set FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND (type = 'time-static' OR type = 'time-dynamic') LOOP v_drop_count := v_drop_count + @extschema@.drop_time_partition(v_row.parent_table); END LOOP; FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND (type = 'id-static' OR type = 'id-dynamic') LOOP v_drop_count := v_drop_count + @extschema@.drop_id_partition(v_row.parent_table); END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Partition maintenance finished. '||v_create_count||' partitions made. '||v_drop_count||' partitions dropped.'); PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman run_maintenance')); EXCEPTION WHEN QUERY_CANCELED THEN PERFORM pg_advisory_unlock(hashtext('pg_partman run_maintenance')); RAISE EXCEPTION '%', SQLERRM; WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN RUN MAINTENANCE'); v_step_id := add_step(v_job_id, 'EXCEPTION before job logging started'); END IF; IF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'CRITICAL', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman run_maintenance')); RAISE EXCEPTION '%', SQLERRM; END $$; /* * Create the trigger function for the parent table of an id-based partition set */ CREATE OR REPLACE FUNCTION create_id_function(p_parent_table text, p_current_id bigint) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_current_partition_name text; v_current_partition_id bigint; v_datetime_string text; v_final_partition_id bigint; v_job_id bigint; v_jobmon_schema text; v_last_partition text; v_next_partition_id bigint; v_next_partition_name text; v_old_search_path text; v_part_interval bigint; v_premake int; v_prev_partition_id bigint; v_prev_partition_name text; v_step_id bigint; v_trig_func text; v_type text; BEGIN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE FUNCTION: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating partition function for table '||p_parent_table); END IF; SELECT type , part_interval::bigint , control , premake , last_partition INTO v_type , v_part_interval , v_control , v_premake , v_last_partition FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_type = 'id-static' THEN v_current_partition_id := p_current_id - (p_current_id % v_part_interval); v_next_partition_id := v_current_partition_id + v_part_interval; v_current_partition_name := p_parent_table || '_p' || v_current_partition_id::text; v_trig_func := 'CREATE OR REPLACE FUNCTION '||p_parent_table||'_part_trig_func() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_current_partition_id bigint; v_last_partition text := '||quote_literal(v_last_partition)||'; v_next_partition_id bigint; v_next_partition_name text; BEGIN IF TG_OP = ''INSERT'' THEN IF NEW.'||v_control||' >= '||v_current_partition_id||' AND NEW.'||v_control||' < '||v_next_partition_id|| ' THEN INSERT INTO '||v_current_partition_name||' VALUES (NEW.*); '; FOR i IN 1..v_premake LOOP v_prev_partition_id := v_current_partition_id - (v_part_interval * i); v_next_partition_id := v_current_partition_id + (v_part_interval * i); v_final_partition_id := v_next_partition_id + v_part_interval; v_prev_partition_name := p_parent_table || '_p' || v_prev_partition_id::text; v_next_partition_name := p_parent_table || '_p' || v_next_partition_id::text; -- Only make previous partitions if they're starting above zero IF v_prev_partition_id >= 0 THEN v_trig_func := v_trig_func ||' ELSIF NEW.'||v_control||' >= '||v_prev_partition_id||' AND NEW.'||v_control||' < '||v_prev_partition_id + v_part_interval|| ' THEN INSERT INTO '||v_prev_partition_name||' VALUES (NEW.*); '; END IF; v_trig_func := v_trig_func ||' ELSIF NEW.'||v_control||' >= '||v_next_partition_id||' AND NEW.'||v_control||' < '||v_final_partition_id|| ' THEN INSERT INTO '||v_next_partition_name||' VALUES (NEW.*); '; END LOOP; v_trig_func := v_trig_func ||' ELSE RETURN NEW; END IF; v_current_partition_id := NEW.'||v_control||' - (NEW.'||v_control||' % '||v_part_interval||'); IF (NEW.'||v_control||' % '||v_part_interval||') > ('||v_part_interval||' / 2) THEN v_next_partition_id := (substring(v_last_partition from char_length('||quote_literal(p_parent_table||'_p')||')+1)::bigint) + '||v_part_interval||'; WHILE ((v_next_partition_id - v_current_partition_id) / '||v_part_interval||') <= '||v_premake||' LOOP v_next_partition_name := @extschema@.create_id_partition('||quote_literal(p_parent_table)||', '||quote_literal(v_control)||',' ||v_part_interval||', ARRAY[v_next_partition_id]); UPDATE @extschema@.part_config SET last_partition = v_next_partition_name WHERE parent_table = '||quote_literal(p_parent_table)||'; PERFORM @extschema@.create_id_function('||quote_literal(p_parent_table)||', NEW.'||v_control||'); v_next_partition_id := v_next_partition_id + '||v_part_interval||'; END LOOP; END IF; END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for current id interval: '||v_current_partition_id||' to '||v_final_partition_id-1); END IF; ELSIF v_type = 'id-dynamic' THEN -- The return inside the partition creation check is there to keep really high ID values from creating new partitions. v_trig_func := 'CREATE OR REPLACE FUNCTION '||p_parent_table||'_part_trig_func() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_current_partition_id bigint; v_current_partition_name text; v_last_partition text := '||quote_literal(v_last_partition)||'; v_last_partition_id bigint; v_next_partition_id bigint; v_next_partition_name text; BEGIN IF TG_OP = ''INSERT'' THEN v_current_partition_id := NEW.'||v_control||' - (NEW.'||v_control||' % '||v_part_interval||'); v_current_partition_name := '''||p_parent_table||'_p''||v_current_partition_id; IF (NEW.'||v_control||' % '||v_part_interval||') > ('||v_part_interval||' / 2) THEN v_last_partition_id = substring(v_last_partition from char_length('||quote_literal(p_parent_table||'_p')||')+1)::bigint; v_next_partition_id := v_last_partition_id + '||v_part_interval||'; IF NEW.'||v_control||' >= v_next_partition_id THEN RETURN NEW; END IF; WHILE ((v_next_partition_id - v_current_partition_id) / '||v_part_interval||') <= '||v_premake||' LOOP v_next_partition_name := @extschema@.create_id_partition('||quote_literal(p_parent_table)||', '||quote_literal(v_control)||',' ||quote_literal(v_part_interval)||', ARRAY[v_next_partition_id]); IF v_next_partition_name IS NOT NULL THEN UPDATE @extschema@.part_config SET last_partition = v_next_partition_name WHERE parent_table = '||quote_literal(p_parent_table)||'; PERFORM @extschema@.create_id_function('||quote_literal(p_parent_table)||', NEW.'||v_control||'); END IF; v_next_partition_id := v_next_partition_id + '||v_part_interval||'; END LOOP; END IF; SELECT count(*) INTO v_count FROM pg_tables WHERE schemaname ||''.''|| tablename = v_current_partition_name; IF v_count > 0 THEN EXECUTE ''INSERT INTO ''||v_current_partition_name||'' VALUES($1.*)'' USING NEW; ELSE RETURN NEW; END IF; END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for dynamic id table: '||p_parent_table); END IF; ELSE RAISE EXCEPTION 'ERROR: Invalid id partitioning type given: %', v_type; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN CREATE FUNCTION: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition function maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'CRITICAL', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to create a child table in a time-based partition set */ CREATE OR REPLACE FUNCTION create_time_partition (p_parent_table text, p_control text, p_interval interval, p_datetime_string text, p_partition_times timestamp[]) RETURNS text LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_grantees text[]; v_job_id bigint; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_partition_name text; v_partition_timestamp_end timestamp; v_partition_timestamp_start timestamp; v_quarter text; v_revoke text[]; v_step_id bigint; v_tablename text; v_trunc_value text; v_time timestamp; v_year text; BEGIN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; SELECT tableowner INTO v_parent_owner FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_name := p_parent_table || '_p'; IF p_interval = '1 year' OR p_interval = '1 month' OR p_interval = '1 day' OR p_interval = '1 hour' OR p_interval = '30 mins' OR p_interval = '15 mins' THEN v_partition_name := v_partition_name || to_char(v_time, 'YYYY'); v_trunc_value := 'year'; IF p_interval = '1 month' OR p_interval = '1 day' OR p_interval = '1 hour' OR p_interval = '30 mins' OR p_interval = '15 mins' THEN v_partition_name := v_partition_name || '_' || to_char(v_time, 'MM'); v_trunc_value := 'month'; IF p_interval = '1 day' OR p_interval = '1 hour' OR p_interval = '30 mins' OR p_interval = '15 mins' THEN v_partition_name := v_partition_name || '_' || to_char(v_time, 'DD'); v_trunc_value := 'day'; IF p_interval = '1 hour' OR p_interval = '30 mins' OR p_interval = '15 mins' THEN v_partition_name := v_partition_name || '_' || to_char(v_time, 'HH24'); IF p_interval <> '30 mins' AND p_interval <> '15 mins' THEN v_partition_name := v_partition_name || '00'; v_trunc_value := 'hour'; ELSIF p_interval = '15 mins' THEN IF date_part('minute', v_time) < 15 THEN v_partition_name := v_partition_name || '00'; ELSIF date_part('minute', v_time) >= 15 AND date_part('minute', v_time) < 30 THEN v_partition_name := v_partition_name || '15'; ELSIF date_part('minute', v_time) >= 30 AND date_part('minute', v_time) < 45 THEN v_partition_name := v_partition_name || '30'; ELSE v_partition_name := v_partition_name || '45'; END IF; v_trunc_value := 'minute'; ELSIF p_interval = '30 mins' THEN IF date_part('minute', v_time) < 30 THEN v_partition_name := v_partition_name || '00'; ELSE v_partition_name := v_partition_name || '30'; END IF; v_trunc_value := 'minute'; END IF; END IF; -- end hour IF END IF; -- end day IF END IF; -- end month IF ELSIF p_interval = '1 week' THEN v_partition_name := v_partition_name || to_char(v_time, 'IYYY') || 'w' || to_char(v_time, 'IW'); v_trunc_value := 'week'; END IF; -- end year/week IF -- pull out datetime portion of last partition's tablename if it matched one of the above partitioning intervals IF v_trunc_value IS NOT NULL THEN v_partition_timestamp_start := date_trunc(v_trunc_value, to_timestamp(substring(v_partition_name from char_length(p_parent_table||'_p')+1), p_datetime_string)); v_partition_timestamp_end := date_trunc(v_trunc_value, to_timestamp(substring(v_partition_name from char_length(p_parent_table||'_p')+1), p_datetime_string) + p_interval); END IF; -- "Q" is ignored in to_timestamp, so handle special case IF p_interval = '3 months' THEN v_year := to_char(v_time, 'YYYY'); v_quarter := to_char(v_time, 'Q'); v_partition_name := v_partition_name || v_year || 'q' || v_quarter; v_trunc_value := 'quarter'; CASE WHEN v_quarter = '1' THEN v_partition_timestamp_start := date_trunc(v_trunc_value, to_timestamp(v_year || '-01-01', 'YYYY-MM-DD')); WHEN v_quarter = '2' THEN v_partition_timestamp_start := date_trunc(v_trunc_value, to_timestamp(v_year || '-04-01', 'YYYY-MM-DD')); WHEN v_quarter = '3' THEN v_partition_timestamp_start := date_trunc(v_trunc_value, to_timestamp(v_year || '-07-01', 'YYYY-MM-DD')); WHEN v_quarter = '4' THEN v_partition_timestamp_start := date_trunc(v_trunc_value, to_timestamp(v_year || '-10-01', 'YYYY-MM-DD')); END CASE; v_partition_timestamp_end := date_trunc(v_trunc_value, (v_partition_timestamp_start + p_interval)); END IF; SELECT schemaname ||'.'|| tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_tablename IS NOT NULL THEN CONTINUE; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_partition_timestamp_start||' to '||(v_partition_timestamp_end-'1sec'::interval)); END IF; IF position('.' in p_parent_table) > 0 THEN v_tablename := substring(v_partition_name from position('.' in v_partition_name)+1); END IF; EXECUTE 'CREATE TABLE '||v_partition_name||' (LIKE '||p_parent_table||' INCLUDING DEFAULTS INCLUDING INDEXES)'; EXECUTE 'ALTER TABLE '||v_partition_name||' ADD CONSTRAINT '||v_tablename||'_partition_check CHECK ('||p_control||'>='||quote_literal(v_partition_timestamp_start)||' AND '||p_control||'<'||quote_literal(v_partition_timestamp_end)||')'; EXECUTE 'ALTER TABLE '||v_partition_name||' INHERIT '||p_parent_table; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP EXECUTE 'GRANT '||array_to_string(v_parent_grant.types, ',')||' ON '||v_partition_name||' TO '||v_parent_grant.grantee; SELECT array_agg(r) INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE '||array_to_string(v_revoke, ',')||' ON '||v_partition_name||' FROM '||v_parent_grant.grantee||' CASCADE'; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN SELECT array_agg(r) INTO v_revoke FROM ( SELECT DISTINCT grantee::text AS r FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_partition_name EXCEPT SELECT unnest(v_grantees)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE ALL ON '||v_partition_name||' FROM '||array_to_string(v_revoke, ','); END IF; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' OWNER TO '||v_parent_owner; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); PERFORM close_job(v_job_id); END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_partition_name; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'CRITICAL', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Create the trigger function for the parent table of a time-based partition set */ CREATE OR REPLACE FUNCTION create_time_function(p_parent_table text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_current_partition_name text; v_current_partition_timestamp timestamptz; v_datetime_string text; v_final_partition_timestamp timestamptz; v_job_id bigint; v_jobmon_schema text; v_old_search_path text; v_next_partition_name text; v_next_partition_timestamp timestamptz; v_part_interval interval; v_premake int; v_prev_partition_name text; v_prev_partition_timestamp timestamptz; v_step_id bigint; v_trig_func text; v_type text; BEGIN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE FUNCTION: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating partition function for table '||p_parent_table); END IF; SELECT type , part_interval::interval , control , premake , datetime_string INTO v_type , v_part_interval , v_control , v_premake , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_type = 'time-static' THEN CASE WHEN v_part_interval = '15 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_part_interval = '30 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_part_interval = '1 hour' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_part_interval = '1 day' THEN v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_part_interval = '1 week' THEN v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_part_interval = '1 month' THEN v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_part_interval = '3 months' THEN v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_part_interval = '1 year' THEN v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; v_current_partition_name := p_parent_table || '_p' || to_char(v_current_partition_timestamp, v_datetime_string); v_next_partition_timestamp := v_current_partition_timestamp + v_part_interval::interval; v_trig_func := 'CREATE OR REPLACE FUNCTION '||p_parent_table||'_part_trig_func() RETURNS trigger LANGUAGE plpgsql AS $t$ BEGIN IF TG_OP = ''INSERT'' THEN IF NEW.'||v_control||' >= '||quote_literal(v_current_partition_timestamp)||' AND NEW.'||v_control||' < '||quote_literal(v_next_partition_timestamp)|| ' THEN INSERT INTO '||v_current_partition_name||' VALUES (NEW.*); '; FOR i IN 1..v_premake LOOP v_prev_partition_timestamp := v_current_partition_timestamp - (v_part_interval::interval * i); v_next_partition_timestamp := v_current_partition_timestamp + (v_part_interval::interval * i); v_final_partition_timestamp := v_next_partition_timestamp + (v_part_interval::interval); v_prev_partition_name := p_parent_table || '_p' || to_char(v_prev_partition_timestamp, v_datetime_string); v_next_partition_name := p_parent_table || '_p' || to_char(v_next_partition_timestamp, v_datetime_string); v_trig_func := v_trig_func ||' ELSIF NEW.'||v_control||' >= '||quote_literal(v_prev_partition_timestamp)||' AND NEW.'||v_control||' < '|| quote_literal(v_prev_partition_timestamp + v_part_interval::interval)|| ' THEN INSERT INTO '||v_prev_partition_name||' VALUES (NEW.*); ELSIF NEW.'||v_control||' >= '||quote_literal(v_next_partition_timestamp)||' AND NEW.'||v_control||' < '|| quote_literal(v_final_partition_timestamp)|| ' THEN INSERT INTO '||v_next_partition_name||' VALUES (NEW.*); '; END LOOP; v_trig_func := v_trig_func ||' ELSE RETURN NEW; END IF; END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for current time interval: '|| v_current_partition_timestamp||' to '||(v_final_partition_timestamp-'1sec'::interval)); END IF; ELSIF v_type = 'time-dynamic' THEN v_trig_func := 'CREATE OR REPLACE FUNCTION '||p_parent_table||'_part_trig_func() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_partition_name text; v_partition_timestamp timestamptz; v_schemaname text; v_tablename text; BEGIN IF TG_OP = ''INSERT'' THEN '; CASE WHEN v_part_interval = '15 mins' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''hour'', NEW.'||v_control||') + ''15min''::interval * floor(date_part(''minute'', NEW.'||v_control||') / 15.0);'; WHEN v_part_interval = '30 mins' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''hour'', NEW.'||v_control||') + ''30min''::interval * floor(date_part(''minute'', NEW.'||v_control||') / 30.0);'; WHEN v_part_interval = '1 hour' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''hour'', NEW.'||v_control||');'; WHEN v_part_interval = '1 day' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''day'', NEW.'||v_control||');'; WHEN v_part_interval = '1 week' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''week'', NEW.'||v_control||');'; WHEN v_part_interval = '1 month' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''month'', NEW.'||v_control||');'; WHEN v_part_interval = '3 months' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''quarter'', NEW.'||v_control||');'; WHEN v_part_interval = '1 year' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''year'', NEW.'||v_control||');'; END CASE; v_trig_func := v_trig_func||' v_partition_name := '''||p_parent_table||'_p''|| to_char(v_partition_timestamp, '||quote_literal(v_datetime_string)||'); v_schemaname := split_part(v_partition_name, ''.'', 1); v_tablename := split_part(v_partition_name, ''.'', 2); SELECT count(*) INTO v_count FROM pg_tables WHERE schemaname = v_schemaname AND tablename = v_tablename; IF v_count > 0 THEN EXECUTE ''INSERT INTO ''||v_partition_name||'' VALUES($1.*)'' USING NEW; ELSE RETURN NEW; END IF; END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for dynamic time table: '||p_parent_table); END IF; ELSE RAISE EXCEPTION 'ERROR: Invalid time partitioning type given: %', v_type; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN CREATE FUNCTION: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition function maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'CRITICAL', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to undo partitioning. * Will actually work on any parent/child table set, not just ones created by pg_partman. */ CREATE FUNCTION undo_partition(p_parent_table text, p_batch_count int DEFAULT 1, p_keep_table boolean DEFAULT true) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_child_table text; v_copy_sql text; v_job_id bigint; v_jobmon_schema text; v_old_search_path text; v_part_interval interval; v_rowcount bigint; v_step_id bigint; v_tablename text; v_total bigint := 0; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_lock(hashtext('pg_partman undo_partition')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition already running.'; RETURN 0; END IF; SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Undoing partitioning for table '||p_parent_table); END IF; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables and stop new id partitions from being made. v_tablename := substring(p_parent_table from position('.' in p_parent_table)+1); EXECUTE 'DROP TRIGGER IF EXISTS '||v_tablename||'_part_trig ON '||p_parent_table; EXECUTE 'DROP FUNCTION IF EXISTS '||p_parent_table||'_part_trig_func()'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); END IF; FOR i IN 1..p_batch_count LOOP SELECT inhrelid::regclass INTO v_child_table FROM pg_catalog.pg_inherits WHERE inhparent::regclass = p_parent_table::regclass ORDER BY inhrelid::regclass::text ASC; EXIT WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing child partition: '||v_child_table); END IF; v_copy_sql := 'INSERT INTO '||p_parent_table||' SELECT * FROM '||v_child_table; EXECUTE v_copy_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF p_keep_table = false THEN EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table DROPPED. Moved '||v_rowcount||' rows to parent'); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table UNINHERITED, not DROPPED. Copied '||v_rowcount||' rows to parent'); END IF; END IF; v_undo_count := v_undo_count + 1; END LOOP; IF v_undo_count = 0 THEN -- FOR loop never ran, so there's no child tables left. DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman (if it existed)'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) from % child table(s) to the parent: %', v_total, v_undo_count, p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', 'Copied '||v_total||' row(s) from '||v_undo_count||' child table(s) to the parent'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman undo_partition')); RETURN v_total; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition function maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'CRITICAL', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to undo time-based partitioning created by this extension */ CREATE FUNCTION undo_partition_time(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval interval DEFAULT NULL, p_keep_table boolean DEFAULT true) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_batch_loop_count int := 0; v_child_min timestamptz; v_child_loop_total bigint := 0; v_child_table text; v_control text; v_inner_loop_count int; v_job_id bigint; v_jobmon_schema text; v_move_sql text; v_old_search_path text; v_part_interval interval; v_row record; v_rowcount bigint; v_step_id bigint; v_tablename text; v_total bigint := 0; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_lock(hashtext('pg_partman undo_time_partition')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_time_partition already running.'; RETURN 0; END IF; SELECT part_interval::interval , control INTO v_part_interval , v_control FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic'); IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Undoing partitioning for table '||p_parent_table); END IF; IF p_batch_interval IS NULL THEN p_batch_interval := v_part_interval; END IF; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables and stop new id partitions from being made. v_tablename := substring(p_parent_table from position('.' in p_parent_table)+1); EXECUTE 'DROP TRIGGER IF EXISTS '||v_tablename||'_part_trig ON '||p_parent_table; EXECUTE 'DROP FUNCTION IF EXISTS '||p_parent_table||'_part_trig_func()'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); END IF; <> WHILE v_batch_loop_count < p_batch_count LOOP SELECT inhrelid::regclass INTO v_child_table FROM pg_catalog.pg_inherits WHERE inhparent::regclass = p_parent_table::regclass ORDER BY inhrelid::regclass::text ASC; EXIT WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing child partition: '||v_child_table); END IF; EXECUTE 'SELECT min('||v_control||') FROM '||v_child_table INTO v_child_min; IF v_child_min IS NULL THEN -- No rows left in this child table. Remove from partition set. EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF p_keep_table = false THEN EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table UNINHERITED, not DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; END IF; v_undo_count := v_undo_count + 1; CONTINUE outer_child_loop; END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP -- Get everything from the current child minimum up to the multiples of the given interval v_move_sql := 'WITH move_data AS (DELETE FROM '||v_child_table|| ' WHERE '||v_control||' <= '||quote_literal(v_child_min + (p_batch_interval * v_inner_loop_count))||' RETURNING *) INSERT INTO '||p_parent_table||' SELECT * FROM move_data'; EXECUTE v_move_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Moved '||v_child_loop_total||' rows to parent.'); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; IF v_batch_loop_count < p_batch_count THEN -- FOR loop never ran, so there's no child tables left. DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) to the parent. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', 'Copied '||v_total||' row(s) to the parent. Removed '||v_undo_count||' partitions.'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman undo_time_partition')); RETURN v_total; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition function maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'CRITICAL', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to undo id-based partitioning created by this extension */ CREATE FUNCTION undo_partition_id(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval bigint DEFAULT NULL, p_keep_table boolean DEFAULT true) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_batch_loop_count int := 0; v_child_loop_total bigint := 0; v_child_min bigint; v_child_table text; v_control text; v_inner_loop_count int; v_job_id bigint; v_jobmon_schema text; v_move_sql text; v_old_search_path text; v_part_interval bigint; v_row record; v_rowcount bigint; v_step_id bigint; v_tablename text; v_total bigint := 0; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_lock(hashtext('pg_partman undo_id_partition')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_id_partition already running.'; RETURN 0; END IF; SELECT part_interval::bigint , control INTO v_part_interval , v_control FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Undoing partitioning for table '||p_parent_table); END IF; IF p_batch_interval IS NULL THEN p_batch_interval := v_part_interval; END IF; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables and stop new id partitions from being made. v_tablename := substring(p_parent_table from position('.' in p_parent_table)+1); EXECUTE 'DROP TRIGGER IF EXISTS '||v_tablename||'_part_trig ON '||p_parent_table; EXECUTE 'DROP FUNCTION IF EXISTS '||p_parent_table||'_part_trig_func()'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); END IF; <> WHILE v_batch_loop_count < p_batch_count LOOP SELECT inhrelid::regclass INTO v_child_table FROM pg_catalog.pg_inherits WHERE inhparent::regclass = p_parent_table::regclass ORDER BY inhrelid::regclass::text ASC; EXIT WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing child partition: '||v_child_table); END IF; EXECUTE 'SELECT min('||v_control||') FROM '||v_child_table INTO v_child_min; IF v_child_min IS NULL THEN -- No rows left in this child table. Remove from partition set. EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF p_keep_table = false THEN EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table UNINHERITED, not DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; END IF; v_undo_count := v_undo_count + 1; CONTINUE outer_child_loop; END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP -- Get everything from the current child minimum up to the multiples of the given interval v_move_sql := 'WITH move_data AS (DELETE FROM '||v_child_table|| ' WHERE '||v_control||' <= '||quote_literal(v_child_min + (p_batch_interval * v_inner_loop_count))||' RETURNING *) INSERT INTO '||p_parent_table||' SELECT * FROM move_data'; EXECUTE v_move_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Moved '||v_child_loop_total||' rows to parent.'); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; IF v_batch_loop_count < p_batch_count THEN -- FOR loop never ran, so there's no child tables left. DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) to the parent. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', 'Copied '||v_total||' row(s) to the parent. Removed '||v_undo_count||' partitions.'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman undo_id_partition')); RETURN v_total; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition function maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'CRITICAL', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; pg_partman-4.6.0/updates/pg_partman--1.0.0--1.1.0.sql000066400000000000000000000667551412761706100214360ustar00rootroot00000000000000-- New python scripts in extras folder to allow partition creation and undoing using smaller commit batches, as is suggested in the documentation for the partition_data_* and undo_partition_* functions. This helps avoid transaction locks when there is a large amount of data to move around. There are also options to commit more slowly and ease the load on very busy systems. -- Changed the ordering of batch arguments in partition_data_id() & partition_data_time(). This makes their order the same as the undo functions and is a more sensical order (I think anyway). -- Made partition functions quieter. No more notices and just returns number of rows moved. -- Changed the undo partition functions to remove partitions in the order they were originally created. They were doing it alphabetically before, which could cause an odd order for serial based partitioning (p100 would be before p2). Creation order may not remove them in ascending order of the data at first, which would be ideal, but it makes more sense than alphabetically. -- Bug fix: undo_partition() could return 0 prematurely if some of the partitions were empty. Will now automatically uninherit/drop any empty partitions and continue on if there are still child tables, not counting them against p_batch_count if given. DROP FUNCTION @extschema@.partition_data_id(text, int, int); DROP FUNCTION @extschema@.partition_data_time(text, interval, int); /* * Populate the child table(s) of an id-based partition set with old data from the original parent */ CREATE FUNCTION partition_data_id(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval int DEFAULT NULL) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_last_partition_name text; v_max_partition_id bigint; v_min_control bigint; v_min_partition_id bigint; v_part_interval bigint; v_partition_id bigint[]; v_rowcount bigint; v_sql text; v_total_rows bigint := 0; BEGIN SELECT part_interval::bigint, control INTO v_part_interval, v_control FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF p_batch_interval IS NULL OR p_batch_interval > v_part_interval THEN p_batch_interval := v_part_interval; END IF; FOR i IN 1..p_batch_count LOOP EXECUTE 'SELECT min('||v_control||') FROM ONLY '||p_parent_table INTO v_min_control; IF v_min_control IS NULL THEN RETURN 0; END IF; v_min_partition_id = v_min_control - (v_min_control % v_part_interval); v_partition_id := ARRAY[v_min_partition_id]; -- RAISE NOTICE 'v_partition_id: %',v_partition_id; IF (v_min_control + p_batch_interval) >= (v_min_partition_id + v_part_interval) THEN v_max_partition_id := v_min_partition_id + v_part_interval; ELSE v_max_partition_id := v_min_control + p_batch_interval; END IF; -- RAISE NOTICE 'v_max_partition_id: %',v_max_partition_id; v_sql := 'SELECT @extschema@.create_id_partition('||quote_literal(p_parent_table)||','||quote_literal(v_control)||',' ||v_part_interval||','||quote_literal(v_partition_id)||')'; -- RAISE NOTICE 'v_sql: %', v_sql; EXECUTE v_sql INTO v_last_partition_name; v_sql := 'WITH partition_data AS ( DELETE FROM ONLY '||p_parent_table||' WHERE '||v_control||' >= '||v_min_control|| ' AND '||v_control||' < '||v_max_partition_id||' RETURNING *) INSERT INTO '||v_last_partition_name||' SELECT * FROM partition_data'; -- RAISE NOTICE 'v_sql: %', v_sql; EXECUTE v_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; RETURN v_total_rows; END $$; /* * Populate the child table(s) of a time-based partition set with old data from the original parent */ CREATE FUNCTION partition_data_time(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval interval DEFAULT NULL) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_datetime_string text; v_last_partition_name text; v_max_partition_timestamp timestamp; v_min_control timestamp; v_min_partition_timestamp timestamp; v_part_interval interval; v_partition_timestamp timestamp[]; v_rowcount bigint; v_sql text; v_total_rows bigint := 0; BEGIN SELECT part_interval::interval, control, datetime_string INTO v_part_interval, v_control, v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF p_batch_interval IS NULL OR p_batch_interval > v_part_interval THEN p_batch_interval := v_part_interval; END IF; FOR i IN 1..p_batch_count LOOP EXECUTE 'SELECT min('||v_control||') FROM ONLY '||p_parent_table INTO v_min_control; IF v_min_control IS NULL THEN RETURN 0; END IF; CASE WHEN v_part_interval = '15 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_min_control) + '15min'::interval * floor(date_part('minute', v_min_control) / 15.0); WHEN v_part_interval = '30 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_min_control) + '30min'::interval * floor(date_part('minute', v_min_control) / 30.0); WHEN v_part_interval = '1 hour' THEN v_min_partition_timestamp := date_trunc('hour', v_min_control); WHEN v_part_interval = '1 day' THEN v_min_partition_timestamp := date_trunc('day', v_min_control); WHEN v_part_interval = '1 week' THEN v_min_partition_timestamp := date_trunc('week', v_min_control); WHEN v_part_interval = '1 month' THEN v_min_partition_timestamp := date_trunc('month', v_min_control); WHEN v_part_interval = '3 months' THEN v_min_partition_timestamp := date_trunc('quarter', v_min_control); WHEN v_part_interval = '1 year' THEN v_min_partition_timestamp := date_trunc('year', v_min_control); END CASE; v_partition_timestamp := ARRAY[v_min_partition_timestamp]; -- RAISE NOTICE 'v_partition_timestamp: %',v_partition_timestamp; IF (v_min_control + p_batch_interval) >= (v_min_partition_timestamp + v_part_interval) THEN v_max_partition_timestamp := v_min_partition_timestamp + v_part_interval; ELSE v_max_partition_timestamp := v_min_control + p_batch_interval; END IF; -- RAISE NOTICE 'v_max_partition_timestamp: %',v_max_partition_timestamp; v_sql := 'SELECT @extschema@.create_time_partition('||quote_literal(p_parent_table)||','||quote_literal(v_control)||',' ||quote_literal(v_part_interval)||','||quote_literal(v_datetime_string)||','||quote_literal(v_partition_timestamp)||')'; -- RAISE NOTICE 'v_sql: %', v_sql; EXECUTE v_sql INTO v_last_partition_name; v_sql := 'WITH partition_data AS ( DELETE FROM ONLY '||p_parent_table||' WHERE '||v_control||' >= '||quote_literal(v_min_control)|| ' AND '||v_control||' < '||quote_literal(v_max_partition_timestamp)||' RETURNING *) INSERT INTO '||v_last_partition_name||' SELECT * FROM partition_data'; -- RAISE NOTICE 'v_sql: %', v_sql; EXECUTE v_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; RETURN v_total_rows; END $$; /* * Function to undo partitioning. * Will actually work on any parent/child table set, not just ones created by pg_partman. */ CREATE OR REPLACE FUNCTION undo_partition(p_parent_table text, p_batch_count int DEFAULT 1, p_keep_table boolean DEFAULT true) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_batch_loop_count bigint := 0; v_child_count bigint; v_child_table text; v_copy_sql text; v_job_id bigint; v_jobmon_schema text; v_old_search_path text; v_part_interval interval; v_rowcount bigint; v_step_id bigint; v_tablename text; v_total bigint := 0; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_lock(hashtext('pg_partman undo_partition')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition already running.'; RETURN 0; END IF; SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Undoing partitioning for table '||p_parent_table); END IF; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables and stop new id partitions from being made. v_tablename := substring(p_parent_table from position('.' in p_parent_table)+1); EXECUTE 'DROP TRIGGER IF EXISTS '||v_tablename||'_part_trig ON '||p_parent_table; EXECUTE 'DROP FUNCTION IF EXISTS '||p_parent_table||'_part_trig_func()'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); END IF; WHILE v_batch_loop_count < p_batch_count LOOP SELECT inhrelid::regclass INTO v_child_table FROM pg_catalog.pg_inherits WHERE inhparent::regclass = p_parent_table::regclass ORDER BY inhrelid ASC; EXIT WHEN v_child_table IS NULL; EXECUTE 'SELECT count(*) FROM '||v_child_table INTO v_child_count; IF v_child_count = 0 THEN -- No rows left in this child table. Remove from partition set. EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF p_keep_table = false THEN EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table DROPPED. Moved '||coalesce(v_rowcount, 0)||' rows to parent'); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table UNINHERITED, not DROPPED. Copied '||coalesce(v_rowcount, 0)||' rows to parent'); END IF; END IF; v_undo_count := v_undo_count + 1; CONTINUE; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing child partition: '||v_child_table); END IF; v_copy_sql := 'INSERT INTO '||p_parent_table||' SELECT * FROM '||v_child_table; EXECUTE v_copy_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF p_keep_table = false THEN EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table DROPPED. Moved '||v_rowcount||' rows to parent'); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table UNINHERITED, not DROPPED. Copied '||v_rowcount||' rows to parent'); END IF; END IF; v_batch_loop_count := v_batch_loop_count + 1; v_undo_count := v_undo_count + 1; END LOOP; IF v_undo_count = 0 THEN -- FOR loop never ran, so there's no child tables left. DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman (if it existed)'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) from % child table(s) to the parent: %', v_total, v_undo_count, p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', 'Copied '||v_total||' row(s) from '||v_undo_count||' child table(s) to the parent'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman undo_partition')); RETURN v_total; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition function maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'CRITICAL', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to undo id-based partitioning created by this extension */ CREATE OR REPLACE FUNCTION undo_partition_id(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval bigint DEFAULT NULL, p_keep_table boolean DEFAULT true) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_batch_loop_count int := 0; v_child_loop_total bigint := 0; v_child_min bigint; v_child_table text; v_control text; v_inner_loop_count int; v_job_id bigint; v_jobmon_schema text; v_move_sql text; v_old_search_path text; v_part_interval bigint; v_row record; v_rowcount bigint; v_step_id bigint; v_tablename text; v_total bigint := 0; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_lock(hashtext('pg_partman undo_id_partition')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_id_partition already running.'; RETURN 0; END IF; SELECT part_interval::bigint , control INTO v_part_interval , v_control FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Undoing partitioning for table '||p_parent_table); END IF; IF p_batch_interval IS NULL THEN p_batch_interval := v_part_interval; END IF; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables and stop new id partitions from being made. v_tablename := substring(p_parent_table from position('.' in p_parent_table)+1); EXECUTE 'DROP TRIGGER IF EXISTS '||v_tablename||'_part_trig ON '||p_parent_table; EXECUTE 'DROP FUNCTION IF EXISTS '||p_parent_table||'_part_trig_func()'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); END IF; <> WHILE v_batch_loop_count < p_batch_count LOOP SELECT inhrelid::regclass INTO v_child_table FROM pg_catalog.pg_inherits WHERE inhparent::regclass = p_parent_table::regclass ORDER BY inhrelid ASC; EXIT WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing child partition: '||v_child_table); END IF; EXECUTE 'SELECT min('||v_control||') FROM '||v_child_table INTO v_child_min; IF v_child_min IS NULL THEN -- No rows left in this child table. Remove from partition set. EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF p_keep_table = false THEN EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table UNINHERITED, not DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; END IF; v_undo_count := v_undo_count + 1; CONTINUE outer_child_loop; END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP -- Get everything from the current child minimum up to the multiples of the given interval v_move_sql := 'WITH move_data AS (DELETE FROM '||v_child_table|| ' WHERE '||v_control||' <= '||quote_literal(v_child_min + (p_batch_interval * v_inner_loop_count))||' RETURNING *) INSERT INTO '||p_parent_table||' SELECT * FROM move_data'; EXECUTE v_move_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Moved '||v_child_loop_total||' rows to parent.'); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; IF v_batch_loop_count < p_batch_count THEN -- FOR loop never ran, so there's no child tables left. DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) to the parent. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', 'Copied '||v_total||' row(s) to the parent. Removed '||v_undo_count||' partitions.'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman undo_id_partition')); RETURN v_total; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition function maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'CRITICAL', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to undo time-based partitioning created by this extension */ CREATE OR REPLACE FUNCTION undo_partition_time(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval interval DEFAULT NULL, p_keep_table boolean DEFAULT true) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_batch_loop_count int := 0; v_child_min timestamptz; v_child_loop_total bigint := 0; v_child_table text; v_control text; v_inner_loop_count int; v_job_id bigint; v_jobmon_schema text; v_move_sql text; v_old_search_path text; v_part_interval interval; v_row record; v_rowcount bigint; v_step_id bigint; v_tablename text; v_total bigint := 0; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_lock(hashtext('pg_partman undo_time_partition')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_time_partition already running.'; RETURN 0; END IF; SELECT part_interval::interval , control INTO v_part_interval , v_control FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic'); IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Undoing partitioning for table '||p_parent_table); END IF; IF p_batch_interval IS NULL THEN p_batch_interval := v_part_interval; END IF; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables and stop new id partitions from being made. v_tablename := substring(p_parent_table from position('.' in p_parent_table)+1); EXECUTE 'DROP TRIGGER IF EXISTS '||v_tablename||'_part_trig ON '||p_parent_table; EXECUTE 'DROP FUNCTION IF EXISTS '||p_parent_table||'_part_trig_func()'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); END IF; <> WHILE v_batch_loop_count < p_batch_count LOOP SELECT inhrelid::regclass INTO v_child_table FROM pg_catalog.pg_inherits WHERE inhparent::regclass = p_parent_table::regclass ORDER BY inhrelid ASC; EXIT WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing child partition: '||v_child_table); END IF; EXECUTE 'SELECT min('||v_control||') FROM '||v_child_table INTO v_child_min; IF v_child_min IS NULL THEN -- No rows left in this child table. Remove from partition set. EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF p_keep_table = false THEN EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table UNINHERITED, not DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; END IF; v_undo_count := v_undo_count + 1; CONTINUE outer_child_loop; END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP -- Get everything from the current child minimum up to the multiples of the given interval v_move_sql := 'WITH move_data AS (DELETE FROM '||v_child_table|| ' WHERE '||v_control||' <= '||quote_literal(v_child_min + (p_batch_interval * v_inner_loop_count))||' RETURNING *) INSERT INTO '||p_parent_table||' SELECT * FROM move_data'; EXECUTE v_move_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Moved '||v_child_loop_total||' rows to parent.'); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; IF v_batch_loop_count < p_batch_count THEN -- FOR loop never ran, so there's no child tables left. DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) to the parent. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', 'Copied '||v_total||' row(s) to the parent. Removed '||v_undo_count||' partitions.'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman undo_time_partition')); RETURN v_total; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition function maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'CRITICAL', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; pg_partman-4.6.0/updates/pg_partman--1.1.0--1.2.0.sql000066400000000000000000001404611412761706100214230ustar00rootroot00000000000000-- Bug fix: Make child table lookups more intelligent to be able to deal with schemas being in the current search_path. Functions this affects are: drop_time_partition(), drop_id_partition(), reapply_privileges(), undo_partition(), undo_partition_id(), undo_partition_time(). Before table names may not have matched properly when looping through all tables to drop or reset privileges. Thanks to https://github.com/terrorobe for reporting this issue. -- Bug fix: reapply_privileges() had unconditional calls to pg_jobmon functions and would fail if it wasn't installed. -- Added new parameter to drop partition functions to manually set an interval you'd like to drop. Makes it easier to cleanup a bunch of old partitions you don't need anymore without having to go through the whole retention policy setup if that's not needed. -- Renamed drop_time_partition() to drop_partition_time() and drop_id_partition() to drop_partition_id() to be more consistent with the other function names. Please check function ownership & privileges before and after update to ensure they are reset properly. DROP FUNCTION @extschema@.drop_time_partition(text, boolean, boolean); DROP FUNCTION @extschema@.drop_id_partition(text, boolean, boolean); /* * Function to drop child tables from a time-based partition set. Options to drop indexes or actually drop the table from the database. */ CREATE FUNCTION drop_partition_time(p_parent_table text, p_retention interval DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL) RETURNS int LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_child_table text; v_datetime_string text; v_drop_count int := 0; v_index record; v_job_id bigint; v_jobmon_schema text; v_old_search_path text; v_part_interval interval; v_partition_timestamp timestamp; v_quarter text; v_retention interval; v_retention_keep_index boolean; v_retention_keep_table boolean; v_step_id bigint; v_year text; BEGIN v_adv_lock := pg_try_advisory_lock(hashtext('pg_partman drop_partition_time')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_time already running.'; RETURN 0; END IF; SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; -- Allow override of configuration options IF p_retention IS NULL THEN SELECT part_interval::interval , retention::interval , retention_keep_table , retention_keep_index , datetime_string INTO v_part_interval , v_retention , v_retention_keep_table , v_retention_keep_index , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic') AND retention IS NOT NULL; IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE SELECT part_interval::interval , retention_keep_table , retention_keep_index , datetime_string INTO v_part_interval , v_retention_keep_table , v_retention_keep_index , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic'); v_retention := p_retention; IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN DROP TIME PARTITION: '|| p_parent_table); END IF; -- Loop through child tables of the given parent FOR v_child_table IN SELECT n.nspname||'.'||c.relname FROM pg_inherits i join pg_class c ON i.inhrelid = c.oid join pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC LOOP -- pull out datetime portion of last partition's tablename to make the next one IF v_part_interval != '3 months' THEN v_partition_timestamp := to_timestamp(substring(v_child_table from char_length(p_parent_table||'_p')+1), v_datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(v_child_table from char_length(p_parent_table||'_p')+1), 'q', 1); v_quarter := split_part(substring(v_child_table from char_length(p_parent_table||'_p')+1), 'q', 2); CASE WHEN v_quarter = '1' THEN v_partition_timestamp := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_partition_timestamp := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_partition_timestamp := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_partition_timestamp := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; -- Add one interval since partition names contain the start of the constraint period IF v_retention < (CURRENT_TIMESTAMP - (v_partition_timestamp + v_part_interval)) THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Uninherit table '||v_child_table||' from '||p_parent_table); END IF; EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Drop table '||v_child_table); END IF; EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN FOR v_index IN SELECT i.indexrelid::regclass AS name , c.conname FROM pg_catalog.pg_index i LEFT JOIN pg_catalog.pg_constraint c ON i.indexrelid = c.conindid WHERE i.indrelid = v_child_table::regclass LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Drop index '||v_index.name||' from '||v_child_table); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_child_table||' DROP CONSTRAINT '||v_index.conname; ELSE EXECUTE 'DROP INDEX '||v_index.name; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', v_drop_count||' partitions dropped.'); PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman drop_partition_time')); RETURN v_drop_count; EXCEPTION WHEN QUERY_CANCELED THEN PERFORM pg_advisory_unlock(hashtext('pg_partman drop_partition_time')); RAISE EXCEPTION '%', SQLERRM; WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN DROP TIME PARTITION'); v_step_id := add_step(v_job_id, 'EXCEPTION before job logging started'); END IF; IF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'CRITICAL', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman drop_partition_time')); RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to drop child tables from a time-based partition set. Options to drop indexes or actually drop the table from the database. */ CREATE FUNCTION drop_partition_id(p_parent_table text, p_retention bigint DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL) RETURNS int LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_child_table text; v_control text; v_drop_count int := 0; v_index record; v_job_id bigint; v_jobmon_schema text; v_max bigint; v_old_search_path text; v_part_interval bigint; v_partition_id bigint; v_retention bigint; v_retention_keep_index boolean; v_retention_keep_table boolean; v_step_id bigint; BEGIN v_adv_lock := pg_try_advisory_lock(hashtext('pg_partman drop_partition_id')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_id already running.'; RETURN 0; END IF; SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; -- Allow override of configuration options IF p_retention IS NULL THEN SELECT part_interval::bigint , control , retention::bigint , retention_keep_table , retention_keep_index INTO v_part_interval , v_control , v_retention , v_retention_keep_table , v_retention_keep_index FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic') AND retention IS NOT NULL; IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE SELECT part_interval::bigint , control , retention_keep_table , retention_keep_index INTO v_part_interval , v_control , v_retention_keep_table , v_retention_keep_index FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); v_retention := p_retention; IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN DROP ID PARTITION: '|| p_parent_table); END IF; EXECUTE 'SELECT max('||v_control||') FROM '||p_parent_table INTO v_max; -- Loop through child tables of the given parent FOR v_child_table IN SELECT n.nspname||'.'||c.relname FROM pg_inherits i join pg_class c ON i.inhrelid = c.oid join pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC LOOP v_partition_id := substring(v_child_table from char_length(p_parent_table||'_p')+1)::bigint; -- Add one interval since partition names contain the start of the constraint period IF v_retention <= (v_max - (v_partition_id + v_part_interval)) THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Uninherit table '||v_child_table||' from '||p_parent_table); END IF; EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Drop table '||v_child_table); END IF; EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN FOR v_index IN SELECT i.indexrelid::regclass AS name , c.conname FROM pg_catalog.pg_index i LEFT JOIN pg_catalog.pg_constraint c ON i.indexrelid = c.conindid WHERE i.indrelid = v_child_table::regclass LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Drop index '||v_index.name||' from '||v_child_table); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_child_table||' DROP CONSTRAINT '||v_index.conname; ELSE EXECUTE 'DROP INDEX '||v_index.name; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', v_drop_count||' partitions dropped.'); PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman drop_partition_id')); RETURN v_drop_count; EXCEPTION WHEN QUERY_CANCELED THEN PERFORM pg_advisory_unlock(hashtext('pg_partman drop_partition_id')); RAISE EXCEPTION '%', SQLERRM; WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN DROP ID PARTITION'); v_step_id := add_step(v_job_id, 'EXCEPTION before job logging started'); END IF; IF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'CRITICAL', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman drop_partition_id')); RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to re-apply ownership & privileges on all child tables in a partition set using parent table as reference */ CREATE OR REPLACE FUNCTION reapply_privileges(p_parent_table text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_child_owner text; v_child_table text; v_child_grant record; v_count int; v_grant text; v_grantees text[]; v_job_id bigint; v_jobmon_schema text; v_match boolean; v_old_search_path text; v_parent_owner text; v_owner_sql text; v_revoke text[]; v_parent_grant record; v_sql text; v_step_id bigint; BEGIN SELECT count(*) INTO v_count FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_count = 0 THEN RAISE EXCEPTION 'Given table is not managed by this extention: %', p_parent_table; END IF; SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN RE-APPLYING PRIVILEGES TO ALL CHILD TABLES OF: '||p_parent_table); v_step_id := add_step(v_job_id, 'Setting new child table privileges'); END IF; SELECT tableowner INTO v_parent_owner FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; FOR v_child_table IN SELECT n.nspname||'.'||c.relname FROM pg_inherits i join pg_class c ON i.inhrelid = c.oid join pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC LOOP IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'PENDING', 'Currently on child partition in ascending order: '||v_child_table); END IF; v_grantees := NULL; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP -- Compare parent & child grants. Don't re-apply if it already exists v_match := false; FOR v_child_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_child_table GROUP BY grantee LOOP IF v_parent_grant.types = v_child_grant.types AND v_parent_grant.grantee = v_child_grant.grantee THEN v_match := true; END IF; END LOOP; IF v_match = false THEN EXECUTE 'GRANT '||array_to_string(v_parent_grant.types, ',')||' ON '||v_child_table||' TO '||v_parent_grant.grantee; SELECT array_agg(r) INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE '||array_to_string(v_revoke, ',')||' ON '||v_child_table||' FROM '||v_parent_grant.grantee||' CASCADE'; END IF; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN SELECT array_agg(r) INTO v_revoke FROM ( SELECT DISTINCT grantee::text AS r FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_child_table EXCEPT SELECT unnest(v_grantees)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE ALL ON '||v_child_table||' FROM '||array_to_string(v_revoke, ','); END IF; END IF; SELECT tableowner INTO v_child_owner FROM pg_tables WHERE schemaname ||'.'|| tablename = v_child_table; IF v_parent_owner <> v_child_owner THEN EXECUTE 'ALTER TABLE '||v_child_table||' OWNER TO '||v_parent_owner; END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); PERFORM close_job(v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'BAD', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to undo partitioning. * Will actually work on any parent/child table set, not just ones created by pg_partman. */ CREATE OR REPLACE FUNCTION undo_partition(p_parent_table text, p_batch_count int DEFAULT 1, p_keep_table boolean DEFAULT true) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_batch_loop_count bigint := 0; v_child_count bigint; v_child_table text; v_copy_sql text; v_job_id bigint; v_jobmon_schema text; v_old_search_path text; v_part_interval interval; v_rowcount bigint; v_step_id bigint; v_tablename text; v_total bigint := 0; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_lock(hashtext('pg_partman undo_partition')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition already running.'; RETURN 0; END IF; SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Undoing partitioning for table '||p_parent_table); END IF; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables and stop new id partitions from being made. v_tablename := substring(p_parent_table from position('.' in p_parent_table)+1); EXECUTE 'DROP TRIGGER IF EXISTS '||v_tablename||'_part_trig ON '||p_parent_table; EXECUTE 'DROP FUNCTION IF EXISTS '||p_parent_table||'_part_trig_func()'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); END IF; WHILE v_batch_loop_count < p_batch_count LOOP SELECT n.nspname||'.'||c.relname INTO v_child_table FROM pg_inherits i JOIN pg_class c ON i.inhrelid = c.oid JOIN pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC; EXIT WHEN v_child_table IS NULL; EXECUTE 'SELECT count(*) FROM '||v_child_table INTO v_child_count; IF v_child_count = 0 THEN -- No rows left in this child table. Remove from partition set. EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF p_keep_table = false THEN EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table DROPPED. Moved '||coalesce(v_rowcount, 0)||' rows to parent'); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table UNINHERITED, not DROPPED. Copied '||coalesce(v_rowcount, 0)||' rows to parent'); END IF; END IF; v_undo_count := v_undo_count + 1; CONTINUE; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing child partition: '||v_child_table); END IF; v_copy_sql := 'INSERT INTO '||p_parent_table||' SELECT * FROM '||v_child_table; EXECUTE v_copy_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF p_keep_table = false THEN EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table DROPPED. Moved '||v_rowcount||' rows to parent'); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table UNINHERITED, not DROPPED. Copied '||v_rowcount||' rows to parent'); END IF; END IF; v_batch_loop_count := v_batch_loop_count + 1; v_undo_count := v_undo_count + 1; END LOOP; IF v_undo_count = 0 THEN -- FOR loop never ran, so there's no child tables left. DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman (if it existed)'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) from % child table(s) to the parent: %', v_total, v_undo_count, p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', 'Copied '||v_total||' row(s) from '||v_undo_count||' child table(s) to the parent'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman undo_partition')); RETURN v_total; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition function maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'CRITICAL', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to undo id-based partitioning created by this extension */ CREATE OR REPLACE FUNCTION undo_partition_id(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval bigint DEFAULT NULL, p_keep_table boolean DEFAULT true) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_batch_loop_count int := 0; v_child_loop_total bigint := 0; v_child_min bigint; v_child_table text; v_control text; v_inner_loop_count int; v_job_id bigint; v_jobmon_schema text; v_move_sql text; v_old_search_path text; v_part_interval bigint; v_row record; v_rowcount bigint; v_step_id bigint; v_tablename text; v_total bigint := 0; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_lock(hashtext('pg_partman undo_id_partition')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_id_partition already running.'; RETURN 0; END IF; SELECT part_interval::bigint , control INTO v_part_interval , v_control FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Undoing partitioning for table '||p_parent_table); END IF; IF p_batch_interval IS NULL THEN p_batch_interval := v_part_interval; END IF; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables and stop new id partitions from being made. v_tablename := substring(p_parent_table from position('.' in p_parent_table)+1); EXECUTE 'DROP TRIGGER IF EXISTS '||v_tablename||'_part_trig ON '||p_parent_table; EXECUTE 'DROP FUNCTION IF EXISTS '||p_parent_table||'_part_trig_func()'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); END IF; <> WHILE v_batch_loop_count < p_batch_count LOOP SELECT n.nspname||'.'||c.relname INTO v_child_table FROM pg_inherits i JOIN pg_class c ON i.inhrelid = c.oid JOIN pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC; EXIT WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing child partition: '||v_child_table); END IF; EXECUTE 'SELECT min('||v_control||') FROM '||v_child_table INTO v_child_min; IF v_child_min IS NULL THEN -- No rows left in this child table. Remove from partition set. EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF p_keep_table = false THEN EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table UNINHERITED, not DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; END IF; v_undo_count := v_undo_count + 1; CONTINUE outer_child_loop; END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP -- Get everything from the current child minimum up to the multiples of the given interval v_move_sql := 'WITH move_data AS (DELETE FROM '||v_child_table|| ' WHERE '||v_control||' <= '||quote_literal(v_child_min + (p_batch_interval * v_inner_loop_count))||' RETURNING *) INSERT INTO '||p_parent_table||' SELECT * FROM move_data'; EXECUTE v_move_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Moved '||v_child_loop_total||' rows to parent.'); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; IF v_batch_loop_count < p_batch_count THEN -- FOR loop never ran, so there's no child tables left. DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) to the parent. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', 'Copied '||v_total||' row(s) to the parent. Removed '||v_undo_count||' partitions.'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman undo_id_partition')); RETURN v_total; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition function maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'CRITICAL', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to undo time-based partitioning created by this extension */ CREATE OR REPLACE FUNCTION undo_partition_time(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval interval DEFAULT NULL, p_keep_table boolean DEFAULT true) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_batch_loop_count int := 0; v_child_min timestamptz; v_child_loop_total bigint := 0; v_child_table text; v_control text; v_inner_loop_count int; v_job_id bigint; v_jobmon_schema text; v_move_sql text; v_old_search_path text; v_part_interval interval; v_row record; v_rowcount bigint; v_step_id bigint; v_tablename text; v_total bigint := 0; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_lock(hashtext('pg_partman undo_time_partition')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_time_partition already running.'; RETURN 0; END IF; SELECT part_interval::interval , control INTO v_part_interval , v_control FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic'); IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Undoing partitioning for table '||p_parent_table); END IF; IF p_batch_interval IS NULL THEN p_batch_interval := v_part_interval; END IF; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables and stop new id partitions from being made. v_tablename := substring(p_parent_table from position('.' in p_parent_table)+1); EXECUTE 'DROP TRIGGER IF EXISTS '||v_tablename||'_part_trig ON '||p_parent_table; EXECUTE 'DROP FUNCTION IF EXISTS '||p_parent_table||'_part_trig_func()'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); END IF; <> WHILE v_batch_loop_count < p_batch_count LOOP SELECT n.nspname||'.'||c.relname INTO v_child_table FROM pg_inherits i JOIN pg_class c ON i.inhrelid = c.oid JOIN pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC; EXIT WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing child partition: '||v_child_table); END IF; EXECUTE 'SELECT min('||v_control||') FROM '||v_child_table INTO v_child_min; IF v_child_min IS NULL THEN -- No rows left in this child table. Remove from partition set. EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF p_keep_table = false THEN EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table UNINHERITED, not DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; END IF; v_undo_count := v_undo_count + 1; CONTINUE outer_child_loop; END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP -- Get everything from the current child minimum up to the multiples of the given interval v_move_sql := 'WITH move_data AS (DELETE FROM '||v_child_table|| ' WHERE '||v_control||' <= '||quote_literal(v_child_min + (p_batch_interval * v_inner_loop_count))||' RETURNING *) INSERT INTO '||p_parent_table||' SELECT * FROM move_data'; EXECUTE v_move_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Moved '||v_child_loop_total||' rows to parent.'); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; IF v_batch_loop_count < p_batch_count THEN -- FOR loop never ran, so there's no child tables left. DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) to the parent. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', 'Copied '||v_total||' row(s) to the parent. Removed '||v_undo_count||' partitions.'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman undo_time_partition')); RETURN v_total; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition function maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'CRITICAL', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to manage pre-creation of the next partitions in a time-based partition set. * Also manages dropping old partitions if the retention option is set. */ CREATE OR REPLACE FUNCTION run_maintenance() RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_create_count int := 0; v_current_partition_timestamp timestamp; v_datetime_string text; v_drop_count int := 0; v_job_id bigint; v_jobmon_schema text; v_last_partition_timestamp timestamp; v_old_search_path text; v_premade_count real; v_quarter text; v_step_id bigint; v_row record; v_year text; BEGIN v_adv_lock := pg_try_advisory_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman maintenance already running.'; RETURN; END IF; SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN RUN MAINTENANCE'); v_step_id := add_step(v_job_id, 'Running maintenance loop'); END IF; FOR v_row IN SELECT parent_table , type , part_interval::interval , control , premake , datetime_string , last_partition , undo_in_progress FROM @extschema@.part_config WHERE type = 'time-static' OR type = 'time-dynamic' LOOP CONTINUE WHEN v_row.undo_in_progress; CASE WHEN v_row.part_interval = '15 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_row.part_interval = '30 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_row.part_interval = '1 hour' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '1 day' THEN v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '1 week' THEN v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '1 month' THEN v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '3 months' THEN v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '1 year' THEN v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; IF v_row.part_interval != '3 months' THEN v_last_partition_timestamp := to_timestamp(substring(v_row.last_partition from char_length(v_row.parent_table||'_p')+1), v_row.datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(v_row.last_partition from char_length(v_row.parent_table||'_p')+1), 'q', 1); v_quarter := split_part(substring(v_row.last_partition from char_length(v_row.parent_table||'_p')+1), 'q', 2); CASE WHEN v_quarter = '1' THEN v_last_partition_timestamp := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_last_partition_timestamp := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_last_partition_timestamp := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_last_partition_timestamp := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; -- Check and see how many premade partitions there are. If it's less than premake in config table, make another v_premade_count = EXTRACT('epoch' FROM age(v_last_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.part_interval::interval); -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed. WHILE v_premade_count < v_row.premake LOOP EXECUTE 'SELECT @extschema@.create_next_time_partition('||quote_literal(v_row.parent_table)||')'; v_create_count := v_create_count + 1; IF v_row.type = 'time-static' THEN EXECUTE 'SELECT @extschema@.create_time_function('||quote_literal(v_row.parent_table)||')'; END IF; v_last_partition_timestamp := v_last_partition_timestamp + v_row.part_interval; v_premade_count = EXTRACT('epoch' FROM age(v_last_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.part_interval::interval); END LOOP; END LOOP; -- end of creation loop -- Manage dropping old partitions if retention option is set FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND (type = 'time-static' OR type = 'time-dynamic') LOOP v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); END LOOP; FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND (type = 'id-static' OR type = 'id-dynamic') LOOP v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Partition maintenance finished. '||v_create_count||' partitions made. '||v_drop_count||' partitions dropped.'); PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman run_maintenance')); EXCEPTION WHEN QUERY_CANCELED THEN PERFORM pg_advisory_unlock(hashtext('pg_partman run_maintenance')); RAISE EXCEPTION '%', SQLERRM; WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN RUN MAINTENANCE'); v_step_id := add_step(v_job_id, 'EXCEPTION before job logging started'); END IF; IF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'CRITICAL', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman run_maintenance')); RAISE EXCEPTION '%', SQLERRM; END $$; pg_partman-4.6.0/updates/pg_partman--1.2.0--1.3.0.sql000066400000000000000000001046341412761706100214270ustar00rootroot00000000000000-- New configuration option for retention system that allows child tables that are eligible for removal to instead be moved to another schema. Set the "retention_schema" option in the configuration table to move the table to the designated schema instead of dropping it. This overrides the retention_keep_table & retention_keep_index options. -- New python script, dump_partition.py, that will dump any tables found in a given schema using pg_dump, create a SHA-512 hash of the dumped file and then drop the table from the database. -- The combination of the retention_schema option and the dump_partition.py script give a way to reliably dump out tables for archiving when they are no longer needed in the database. Idea for this feature adapted from conversation at PGDay NYC 2013 (lost the card of the individual I was talking with :( ). -- New function show_partitions() that gives a list of child tables in a partition set. Adapted from fork by https://github.com/ebaptistella -- Previously the functions that created the new partitions were using only the "INCLUDING DEFAULTS INCLUDING INDEXES" options when using the CREATE TABLE ... (LIKE ...) syntax. This caused some contraints on the parent to be missed in child tables. Changed to include all available options as of PostgreSQL 9.1: INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS. Change will apply to all newly created child tables in all partition sets managed by pg_partman. You'll have to go back and manually fix any already existing child tables that may be missing constraints. Issue reported by Nick Ebbitt. -- Added TAP tests for drop partition functions. -- Fixed some tap tests to more accurately test for table (non)existance -- Clarified the drop_partition_id() function's retention parameter meaning. ALTER TABLE @extschema@.part_config ADD retention_schema text; CREATE TEMP TABLE pg_partman_preserve_privs_temp (statement text); INSERT INTO pg_partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.drop_partition_id(text, bigint, boolean, boolean, text) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'drop_partition_id'; INSERT INTO pg_partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.drop_partition_time(text, interval, boolean, boolean, text) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'drop_partition_time'; CREATE FUNCTION replay_preserved_privs() RETURNS void LANGUAGE plpgsql AS $$ DECLARE v_row record; BEGIN FOR v_row IN SELECT statement FROM pg_partman_preserve_privs_temp LOOP IF v_row.statement IS NOT NULL THEN EXECUTE v_row.statement; END IF; END LOOP; END $$; DROP FUNCTION drop_partition_id(text, bigint, boolean, boolean); DROP FUNCTION drop_partition_time(text, interval, boolean, boolean); /* * Function to list all child partitions in a set. * Will list all child tables in any inheritance set, * not just those managed by pg_partman. */ CREATE FUNCTION show_partitions (p_parent_table text) RETURNS SETOF text LANGUAGE plpgsql STABLE SECURITY DEFINER AS $$ BEGIN RETURN QUERY EXECUTE ' SELECT n.nspname::text ||''.''|| c.relname::text AS partition_name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = '||quote_literal(p_parent_table)||'::regclass ORDER BY c.relname'; END $$; /* * Function to create id partitions */ CREATE OR REPLACE FUNCTION create_id_partition (p_parent_table text, p_control text, p_interval bigint, p_partition_ids bigint[]) RETURNS text LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_grantees text[]; v_job_id bigint; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_partition_name text; v_revoke text[]; v_step_id bigint; v_tablename text; v_id bigint; BEGIN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; SELECT tableowner INTO v_parent_owner FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; FOREACH v_id IN ARRAY p_partition_ids LOOP v_partition_name := p_parent_table||'_p'||v_id; SELECT schemaname ||'.'|| tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_tablename IS NOT NULL THEN CONTINUE; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_id||' to '||(v_id + p_interval)-1); END IF; IF position('.' in p_parent_table) > 0 THEN v_tablename := substring(v_partition_name from position('.' in v_partition_name)+1); END IF; EXECUTE 'CREATE TABLE '||v_partition_name||' (LIKE '||p_parent_table||' INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)'; EXECUTE 'ALTER TABLE '||v_partition_name||' ADD CONSTRAINT '||v_tablename||'_partition_check CHECK ('||p_control||'>='||quote_literal(v_id)||' AND '||p_control||'<'||quote_literal(v_id + p_interval)||')'; EXECUTE 'ALTER TABLE '||v_partition_name||' INHERIT '||p_parent_table; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP EXECUTE 'GRANT '||array_to_string(v_parent_grant.types, ',')||' ON '||v_partition_name||' TO '||v_parent_grant.grantee; SELECT array_agg(r) INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE '||array_to_string(v_revoke, ',')||' ON '||v_partition_name||' FROM '||v_parent_grant.grantee||' CASCADE'; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN SELECT array_agg(r) INTO v_revoke FROM ( SELECT DISTINCT grantee::text AS r FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_partition_name EXCEPT SELECT unnest(v_grantees)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE ALL ON '||v_partition_name||' FROM '||array_to_string(v_revoke, ','); END IF; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' OWNER TO '||v_parent_owner; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); PERFORM close_job(v_job_id); END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_partition_name; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'BAD', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to create a child table in a time-based partition set */ CREATE OR REPLACE FUNCTION create_time_partition (p_parent_table text, p_control text, p_interval interval, p_datetime_string text, p_partition_times timestamp[]) RETURNS text LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_grantees text[]; v_job_id bigint; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_partition_name text; v_partition_timestamp_end timestamp; v_partition_timestamp_start timestamp; v_quarter text; v_revoke text[]; v_step_id bigint; v_tablename text; v_trunc_value text; v_time timestamp; v_year text; BEGIN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; SELECT tableowner INTO v_parent_owner FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_name := p_parent_table || '_p'; IF p_interval = '1 year' OR p_interval = '1 month' OR p_interval = '1 day' OR p_interval = '1 hour' OR p_interval = '30 mins' OR p_interval = '15 mins' THEN v_partition_name := v_partition_name || to_char(v_time, 'YYYY'); v_trunc_value := 'year'; IF p_interval = '1 month' OR p_interval = '1 day' OR p_interval = '1 hour' OR p_interval = '30 mins' OR p_interval = '15 mins' THEN v_partition_name := v_partition_name || '_' || to_char(v_time, 'MM'); v_trunc_value := 'month'; IF p_interval = '1 day' OR p_interval = '1 hour' OR p_interval = '30 mins' OR p_interval = '15 mins' THEN v_partition_name := v_partition_name || '_' || to_char(v_time, 'DD'); v_trunc_value := 'day'; IF p_interval = '1 hour' OR p_interval = '30 mins' OR p_interval = '15 mins' THEN v_partition_name := v_partition_name || '_' || to_char(v_time, 'HH24'); IF p_interval <> '30 mins' AND p_interval <> '15 mins' THEN v_partition_name := v_partition_name || '00'; v_trunc_value := 'hour'; ELSIF p_interval = '15 mins' THEN IF date_part('minute', v_time) < 15 THEN v_partition_name := v_partition_name || '00'; ELSIF date_part('minute', v_time) >= 15 AND date_part('minute', v_time) < 30 THEN v_partition_name := v_partition_name || '15'; ELSIF date_part('minute', v_time) >= 30 AND date_part('minute', v_time) < 45 THEN v_partition_name := v_partition_name || '30'; ELSE v_partition_name := v_partition_name || '45'; END IF; v_trunc_value := 'minute'; ELSIF p_interval = '30 mins' THEN IF date_part('minute', v_time) < 30 THEN v_partition_name := v_partition_name || '00'; ELSE v_partition_name := v_partition_name || '30'; END IF; v_trunc_value := 'minute'; END IF; END IF; -- end hour IF END IF; -- end day IF END IF; -- end month IF ELSIF p_interval = '1 week' THEN v_partition_name := v_partition_name || to_char(v_time, 'IYYY') || 'w' || to_char(v_time, 'IW'); v_trunc_value := 'week'; END IF; -- end year/week IF -- pull out datetime portion of last partition's tablename if it matched one of the above partitioning intervals IF v_trunc_value IS NOT NULL THEN v_partition_timestamp_start := date_trunc(v_trunc_value, to_timestamp(substring(v_partition_name from char_length(p_parent_table||'_p')+1), p_datetime_string)); v_partition_timestamp_end := date_trunc(v_trunc_value, to_timestamp(substring(v_partition_name from char_length(p_parent_table||'_p')+1), p_datetime_string) + p_interval); END IF; -- "Q" is ignored in to_timestamp, so handle special case IF p_interval = '3 months' THEN v_year := to_char(v_time, 'YYYY'); v_quarter := to_char(v_time, 'Q'); v_partition_name := v_partition_name || v_year || 'q' || v_quarter; v_trunc_value := 'quarter'; CASE WHEN v_quarter = '1' THEN v_partition_timestamp_start := date_trunc(v_trunc_value, to_timestamp(v_year || '-01-01', 'YYYY-MM-DD')); WHEN v_quarter = '2' THEN v_partition_timestamp_start := date_trunc(v_trunc_value, to_timestamp(v_year || '-04-01', 'YYYY-MM-DD')); WHEN v_quarter = '3' THEN v_partition_timestamp_start := date_trunc(v_trunc_value, to_timestamp(v_year || '-07-01', 'YYYY-MM-DD')); WHEN v_quarter = '4' THEN v_partition_timestamp_start := date_trunc(v_trunc_value, to_timestamp(v_year || '-10-01', 'YYYY-MM-DD')); END CASE; v_partition_timestamp_end := date_trunc(v_trunc_value, (v_partition_timestamp_start + p_interval)); END IF; SELECT schemaname ||'.'|| tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_tablename IS NOT NULL THEN CONTINUE; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_partition_timestamp_start||' to '||(v_partition_timestamp_end-'1sec'::interval)); END IF; IF position('.' in p_parent_table) > 0 THEN v_tablename := substring(v_partition_name from position('.' in v_partition_name)+1); END IF; EXECUTE 'CREATE TABLE '||v_partition_name||' (LIKE '||p_parent_table||' INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)'; EXECUTE 'ALTER TABLE '||v_partition_name||' ADD CONSTRAINT '||v_tablename||'_partition_check CHECK ('||p_control||'>='||quote_literal(v_partition_timestamp_start)||' AND '||p_control||'<'||quote_literal(v_partition_timestamp_end)||')'; EXECUTE 'ALTER TABLE '||v_partition_name||' INHERIT '||p_parent_table; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP EXECUTE 'GRANT '||array_to_string(v_parent_grant.types, ',')||' ON '||v_partition_name||' TO '||v_parent_grant.grantee; SELECT array_agg(r) INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE '||array_to_string(v_revoke, ',')||' ON '||v_partition_name||' FROM '||v_parent_grant.grantee||' CASCADE'; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN SELECT array_agg(r) INTO v_revoke FROM ( SELECT DISTINCT grantee::text AS r FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_partition_name EXCEPT SELECT unnest(v_grantees)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE ALL ON '||v_partition_name||' FROM '||array_to_string(v_revoke, ','); END IF; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' OWNER TO '||v_parent_owner; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); PERFORM close_job(v_job_id); END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_partition_name; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'CRITICAL', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to drop child tables from a time-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ CREATE FUNCTION drop_partition_id(p_parent_table text, p_retention bigint DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_child_table text; v_control text; v_drop_count int := 0; v_index record; v_job_id bigint; v_jobmon_schema text; v_max bigint; v_old_search_path text; v_part_interval bigint; v_partition_id bigint; v_retention bigint; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_step_id bigint; BEGIN v_adv_lock := pg_try_advisory_lock(hashtext('pg_partman drop_partition_id')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_id already running.'; RETURN 0; END IF; SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; -- Allow override of configuration options IF p_retention IS NULL THEN SELECT part_interval::bigint , control , retention::bigint , retention_keep_table , retention_keep_index , retention_schema INTO v_part_interval , v_control , v_retention , v_retention_keep_table , v_retention_keep_index , v_retention_schema FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic') AND retention IS NOT NULL; IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE SELECT part_interval::bigint , control , retention_keep_table , retention_keep_index , retention_schema INTO v_part_interval , v_control , v_retention_keep_table , v_retention_keep_index , v_retention_schema FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); v_retention := p_retention; IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN DROP ID PARTITION: '|| p_parent_table); END IF; EXECUTE 'SELECT max('||v_control||') FROM '||p_parent_table INTO v_max; -- Loop through child tables of the given parent FOR v_child_table IN SELECT n.nspname||'.'||c.relname FROM pg_inherits i join pg_class c ON i.inhrelid = c.oid join pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC LOOP v_partition_id := substring(v_child_table from char_length(p_parent_table||'_p')+1)::bigint; -- Add one interval since partition names contain the start of the constraint period IF v_retention <= (v_max - (v_partition_id + v_part_interval)) THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Uninherit table '||v_child_table||' from '||p_parent_table); END IF; EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Drop table '||v_child_table); END IF; EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN FOR v_index IN SELECT i.indexrelid::regclass AS name , c.conname FROM pg_catalog.pg_index i LEFT JOIN pg_catalog.pg_constraint c ON i.indexrelid = c.conindid WHERE i.indrelid = v_child_table::regclass LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Drop index '||v_index.name||' from '||v_child_table); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_child_table||' DROP CONSTRAINT '||v_index.conname; ELSE EXECUTE 'DROP INDEX '||v_index.name; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Moving table '||v_child_table||' to schema '||v_retention_schema); END IF; EXECUTE 'ALTER TABLE '||v_child_table||' SET SCHEMA '||v_retention_schema; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', v_drop_count||' partitions dropped.'); PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman drop_partition_id')); RETURN v_drop_count; EXCEPTION WHEN QUERY_CANCELED THEN PERFORM pg_advisory_unlock(hashtext('pg_partman drop_partition_id')); RAISE EXCEPTION '%', SQLERRM; WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN DROP ID PARTITION'); v_step_id := add_step(v_job_id, 'EXCEPTION before job logging started'); END IF; IF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'CRITICAL', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman drop_partition_id')); RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to drop child tables from a time-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ CREATE FUNCTION drop_partition_time(p_parent_table text, p_retention interval DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_child_table text; v_datetime_string text; v_drop_count int := 0; v_index record; v_job_id bigint; v_jobmon_schema text; v_old_search_path text; v_part_interval interval; v_partition_timestamp timestamp; v_quarter text; v_retention interval; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_step_id bigint; v_year text; BEGIN v_adv_lock := pg_try_advisory_lock(hashtext('pg_partman drop_partition_time')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_time already running.'; RETURN 0; END IF; SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; -- Allow override of configuration options IF p_retention IS NULL THEN SELECT part_interval::interval , retention::interval , retention_keep_table , retention_keep_index , datetime_string , retention_schema INTO v_part_interval , v_retention , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic') AND retention IS NOT NULL; IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE SELECT part_interval::interval , retention_keep_table , retention_keep_index , datetime_string , retention_schema INTO v_part_interval , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic'); v_retention := p_retention; IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN DROP TIME PARTITION: '|| p_parent_table); END IF; -- Loop through child tables of the given parent FOR v_child_table IN SELECT n.nspname||'.'||c.relname FROM pg_inherits i join pg_class c ON i.inhrelid = c.oid join pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC LOOP -- pull out datetime portion of last partition's tablename to make the next one IF v_part_interval != '3 months' THEN v_partition_timestamp := to_timestamp(substring(v_child_table from char_length(p_parent_table||'_p')+1), v_datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(v_child_table from char_length(p_parent_table||'_p')+1), 'q', 1); v_quarter := split_part(substring(v_child_table from char_length(p_parent_table||'_p')+1), 'q', 2); CASE WHEN v_quarter = '1' THEN v_partition_timestamp := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_partition_timestamp := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_partition_timestamp := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_partition_timestamp := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; -- Add one interval since partition names contain the start of the constraint period IF v_retention < (CURRENT_TIMESTAMP - (v_partition_timestamp + v_part_interval)) THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Uninherit table '||v_child_table||' from '||p_parent_table); END IF; EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Drop table '||v_child_table); END IF; EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN FOR v_index IN SELECT i.indexrelid::regclass AS name , c.conname FROM pg_catalog.pg_index i LEFT JOIN pg_catalog.pg_constraint c ON i.indexrelid = c.conindid WHERE i.indrelid = v_child_table::regclass LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Drop index '||v_index.name||' from '||v_child_table); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_child_table||' DROP CONSTRAINT '||v_index.conname; ELSE EXECUTE 'DROP INDEX '||v_index.name; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Moving table '||v_child_table||' to schema '||v_retention_schema); END IF; EXECUTE 'ALTER TABLE '||v_child_table||' SET SCHEMA '||v_retention_schema; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', v_drop_count||' partitions dropped.'); PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman drop_partition_time')); RETURN v_drop_count; EXCEPTION WHEN QUERY_CANCELED THEN PERFORM pg_advisory_unlock(hashtext('pg_partman drop_partition_time')); RAISE EXCEPTION '%', SQLERRM; WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN DROP TIME PARTITION'); v_step_id := add_step(v_job_id, 'EXCEPTION before job logging started'); END IF; IF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'CRITICAL', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman drop_partition_time')); RAISE EXCEPTION '%', SQLERRM; END $$; -- Restore original privileges to objects that were dropped SELECT @extschema@.replay_preserved_privs(); DROP FUNCTION @extschema@.replay_preserved_privs(); DROP TABLE pg_partman_preserve_privs_temp; pg_partman-4.6.0/updates/pg_partman--1.3.0--1.4.0.sql000066400000000000000000002563741412761706100214420ustar00rootroot00000000000000-- Updated creation of child partition, function & trigger names to take into account the max object length an object can have to guarentee the partition suffix. Involved extensive rewrite of many core functions. -- WARNING: If your table names were already long enough to be causing name truncation (over 63 characters), you may get duplicate child tables, functions & triggers created. Please check your object name lengths on your partition sets before installing this update to see if you may be affected by this edge case and its subsequent fix. -- New python script (reapply-indexes.py) to re-apply indexes to child tables when they have changed on the parent. See docs for more info. -- New function to check the uniqueness of a column in a partition set (check_unique_column()). Helps to overcome the inability of a unique constraint to be applied efficiently across all partitions in a set. Does not prevent a unique violation, but provides a method to monitor for it happening. -- More pgTAP tests to ensure name trunucation process is working. -- Changed pgTAP tests to assume pgTAP is installed in public schema to try and avoid issues when it isn't. /* * 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 schema given if it doesn't require truncation */ CREATE FUNCTION check_name_length (p_object_name text, p_object_schema text DEFAULT NULL, p_suffix text DEFAULT NULL, p_table_partition boolean DEFAULT FALSE) RETURNS text LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_new_length int; v_new_name text; BEGIN IF p_table_partition IS TRUE AND (p_suffix IS NULL OR p_object_schema IS NULL) THEN RAISE EXCEPTION 'Table partition name requires a schema and 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 := p_object_schema ||'.'|| substring(p_object_name from 1 for v_new_length) || '_p' || p_suffix; ELSE v_new_name := p_object_schema ||'.'||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 := COALESCE(p_object_schema ||'.', '') || substring(p_object_name from 1 for v_new_length) || COALESCE(p_suffix, ''); ELSE v_new_name := COALESCE(p_object_schema ||'.', '') || p_object_name||COALESCE(p_suffix, ''); END IF; END IF; RETURN v_new_name; END $$; CREATE TYPE check_unique_table AS (column_value text, count bigint); /* * Function to check uniqueness of a column in a partition set. * First draft that runs within database in a single transaction. * Working on version that will dump data out to perform a quicker check with less impact on DB. */ CREATE FUNCTION check_unique_column(p_parent_table text, p_column text) RETURNS SETOF check_unique_table LANGUAGE plpgsql AS $$ DECLARE v_row record; v_sql text; v_trouble @extschema@.check_unique_table%rowtype; BEGIN v_sql := 'SELECT '||p_column||'::text AS column_value, count('||p_column||') AS count FROM '||p_parent_table||' GROUP BY '||p_column||' HAVING (count('||p_column||') > 1) ORDER BY '||p_column; RAISE NOTICE 'v_sql: %', v_sql; FOR v_row IN EXECUTE v_sql LOOP v_trouble.column_value := v_row.column_value; v_trouble.count := v_row.count; RETURN NEXT v_trouble; END LOOP; END $$; /* * Function to create id partitions */ CREATE OR REPLACE FUNCTION create_id_partition (p_parent_table text, p_control text, p_interval bigint, p_partition_ids bigint[]) RETURNS text LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_grantees text[]; v_job_id bigint; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_partition_name text; v_revoke text[]; v_step_id bigint; v_tablename text; v_id bigint; BEGIN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; SELECT tableowner, schemaname, tablename INTO v_parent_owner, v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; FOREACH v_id IN ARRAY p_partition_ids LOOP v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_id::text, TRUE); -- If child table already exists, skip creation SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_tablename IS NOT NULL THEN CONTINUE; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_id||' to '||(v_id + p_interval)-1); END IF; EXECUTE 'CREATE TABLE '||v_partition_name||' (LIKE '||p_parent_table||' INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)'; SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; EXECUTE 'ALTER TABLE '||v_partition_name||' ADD CONSTRAINT '||v_tablename||'_partition_check CHECK ('||p_control||'>='||quote_literal(v_id)||' AND '||p_control||'<'||quote_literal(v_id + p_interval)||')'; EXECUTE 'ALTER TABLE '||v_partition_name||' INHERIT '||p_parent_table; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP EXECUTE 'GRANT '||array_to_string(v_parent_grant.types, ',')||' ON '||v_partition_name||' TO '||v_parent_grant.grantee; SELECT array_agg(r) INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE '||array_to_string(v_revoke, ',')||' ON '||v_partition_name||' FROM '||v_parent_grant.grantee||' CASCADE'; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN SELECT array_agg(r) INTO v_revoke FROM ( SELECT DISTINCT grantee::text AS r FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_partition_name EXCEPT SELECT unnest(v_grantees)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE ALL ON '||v_partition_name||' FROM '||array_to_string(v_revoke, ','); END IF; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' OWNER TO '||v_parent_owner; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); PERFORM close_job(v_job_id); END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_partition_name; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'BAD', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to create a child table in a time-based partition set */ CREATE OR REPLACE FUNCTION create_time_partition (p_parent_table text, p_control text, p_interval interval, p_datetime_string text, p_partition_times timestamp[]) RETURNS text LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_grantees text[]; v_job_id bigint; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_partition_name text; v_partition_suffix text; v_partition_timestamp_end timestamp; v_partition_timestamp_start timestamp; v_quarter text; v_revoke text[]; v_step_id bigint; v_tablename text; v_trunc_value text; v_time timestamp; v_year text; BEGIN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; SELECT tableowner, schemaname, tablename INTO v_parent_owner, v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; FOREACH v_time IN ARRAY p_partition_times LOOP IF p_interval <= '1 year' AND p_interval <> '3 months' AND p_interval <> '1 week' THEN v_partition_suffix := to_char(v_time, 'YYYY'); v_trunc_value := 'year'; IF p_interval <= '1 month' AND p_interval <> '1 week' THEN v_partition_suffix := v_partition_suffix ||'_'|| to_char(v_time, 'MM'); v_trunc_value := 'month'; IF p_interval <= '1 day' THEN v_partition_suffix := v_partition_suffix ||'_'|| to_char(v_time, 'DD'); v_trunc_value := 'day'; IF p_interval <= '1 hour' THEN v_partition_suffix := v_partition_suffix || '_' || to_char(v_time, 'HH24'); IF p_interval <> '30 mins' AND p_interval <> '15 mins' THEN v_partition_suffix := v_partition_suffix || '00'; v_trunc_value := 'hour'; ELSIF p_interval = '15 mins' THEN IF date_part('minute', v_time) < 15 THEN v_partition_suffix := v_partition_suffix || '00'; ELSIF date_part('minute', v_time) >= 15 AND date_part('minute', v_time) < 30 THEN v_partition_suffix := v_partition_suffix || '15'; ELSIF date_part('minute', v_time) >= 30 AND date_part('minute', v_time) < 45 THEN v_partition_suffix := v_partition_suffix || '30'; ELSE v_partition_suffix := v_partition_suffix || '45'; END IF; v_trunc_value := 'minute'; ELSIF p_interval = '30 mins' THEN IF date_part('minute', v_time) < 30 THEN v_partition_suffix := v_partition_suffix || '00'; ELSE v_partition_suffix := v_partition_suffix || '30'; END IF; v_trunc_value := 'minute'; END IF; END IF; -- end hour IF END IF; -- end day IF END IF; -- end month IF ELSIF p_interval = '1 week' THEN v_partition_suffix := to_char(v_time, 'IYYY') || 'w' || to_char(v_time, 'IW'); v_trunc_value := 'week'; END IF; -- end year/week IF -- "Q" is ignored in to_timestamp, so handle special case IF p_interval = '3 months' THEN v_year := to_char(v_time, 'YYYY'); v_quarter := to_char(v_time, 'Q'); v_partition_suffix := v_year || 'q' || v_quarter; v_trunc_value := 'quarter'; CASE WHEN v_quarter = '1' THEN v_partition_timestamp_start := date_trunc(v_trunc_value, to_timestamp(v_year || '-01-01', 'YYYY-MM-DD')); WHEN v_quarter = '2' THEN v_partition_timestamp_start := date_trunc(v_trunc_value, to_timestamp(v_year || '-04-01', 'YYYY-MM-DD')); WHEN v_quarter = '3' THEN v_partition_timestamp_start := date_trunc(v_trunc_value, to_timestamp(v_year || '-07-01', 'YYYY-MM-DD')); WHEN v_quarter = '4' THEN v_partition_timestamp_start := date_trunc(v_trunc_value, to_timestamp(v_year || '-10-01', 'YYYY-MM-DD')); END CASE; v_partition_timestamp_end := date_trunc(v_trunc_value, (v_partition_timestamp_start + p_interval)); END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_partition_suffix, TRUE); -- pull out datetime portion of last partition's tablename if it matched anything except quarterly IF p_interval <> '3 months' AND v_trunc_value IS NOT NULL THEN v_partition_timestamp_start := date_trunc(v_trunc_value, to_timestamp(v_partition_suffix, p_datetime_string)); v_partition_timestamp_end := date_trunc(v_trunc_value, to_timestamp(v_partition_suffix, p_datetime_string) + p_interval); END IF; SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_tablename IS NOT NULL THEN CONTINUE; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_partition_timestamp_start||' to '||(v_partition_timestamp_end-'1sec'::interval)); END IF; EXECUTE 'CREATE TABLE '||v_partition_name||' (LIKE '||p_parent_table||' INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)'; SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; EXECUTE 'ALTER TABLE '||v_partition_name||' ADD CONSTRAINT '||v_tablename||'_partition_check CHECK ('||p_control||'>='||quote_literal(v_partition_timestamp_start)||' AND '||p_control||'<'||quote_literal(v_partition_timestamp_end)||')'; EXECUTE 'ALTER TABLE '||v_partition_name||' INHERIT '||p_parent_table; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP EXECUTE 'GRANT '||array_to_string(v_parent_grant.types, ',')||' ON '||v_partition_name||' TO '||v_parent_grant.grantee; SELECT array_agg(r) INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE '||array_to_string(v_revoke, ',')||' ON '||v_partition_name||' FROM '||v_parent_grant.grantee||' CASCADE'; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN SELECT array_agg(r) INTO v_revoke FROM ( SELECT DISTINCT grantee::text AS r FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_partition_name EXCEPT SELECT unnest(v_grantees)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE ALL ON '||v_partition_name||' FROM '||array_to_string(v_revoke, ','); END IF; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' OWNER TO '||v_parent_owner; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); PERFORM close_job(v_job_id); END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_partition_name; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'CRITICAL', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Create the trigger function for the parent table of an id-based partition set */ CREATE OR REPLACE FUNCTION create_id_function(p_parent_table text, p_current_id bigint) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_count int; v_current_partition_name text; v_current_partition_id bigint; v_datetime_string text; v_final_partition_id bigint; v_function_name text; v_job_id bigint; v_jobmon_schema text; v_last_partition text; v_next_partition_id bigint; v_next_partition_name text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_part_interval bigint; v_premake int; v_prev_partition_id bigint; v_prev_partition_name text; v_step_id bigint; v_trig_func text; v_type text; BEGIN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE FUNCTION: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating partition function for table '||p_parent_table); END IF; SELECT type , part_interval::bigint , control , premake , last_partition INTO v_type , v_part_interval , v_control , v_premake , v_last_partition FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_function_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, '_part_trig_func', FALSE); IF v_type = 'id-static' THEN v_current_partition_id := p_current_id - (p_current_id % v_part_interval); v_next_partition_id := v_current_partition_id + v_part_interval; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_current_partition_id::text, TRUE); v_trig_func := 'CREATE OR REPLACE FUNCTION '||v_function_name||'() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_current_partition_id bigint; v_last_partition text := '||quote_literal(v_last_partition)||'; v_id_position int; v_next_partition_id bigint; v_next_partition_name text; BEGIN IF TG_OP = ''INSERT'' THEN IF NEW.'||v_control||' >= '||v_current_partition_id||' AND NEW.'||v_control||' < '||v_next_partition_id|| ' THEN INSERT INTO '||v_current_partition_name||' VALUES (NEW.*); '; FOR i IN 1..v_premake LOOP v_prev_partition_id := v_current_partition_id - (v_part_interval * i); v_next_partition_id := v_current_partition_id + (v_part_interval * i); v_final_partition_id := v_next_partition_id + v_part_interval; v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_prev_partition_id::text, TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_next_partition_id::text, TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles edge case of changing premake immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_prev_partition_name; IF v_count > 0 THEN -- Only handle previous partitions if they're starting above zero IF v_prev_partition_id >= 0 THEN v_trig_func := v_trig_func ||' ELSIF NEW.'||v_control||' >= '||v_prev_partition_id||' AND NEW.'||v_control||' < '||v_prev_partition_id + v_part_interval|| ' THEN INSERT INTO '||v_prev_partition_name||' VALUES (NEW.*); '; END IF; END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_next_partition_name; IF v_count > 0 THEN v_trig_func := v_trig_func ||' ELSIF NEW.'||v_control||' >= '||v_next_partition_id||' AND NEW.'||v_control||' < '||v_final_partition_id|| ' THEN INSERT INTO '||v_next_partition_name||' VALUES (NEW.*); '; END IF; END LOOP; v_trig_func := v_trig_func ||' ELSE RETURN NEW; END IF; v_current_partition_id := NEW.'||v_control||' - (NEW.'||v_control||' % '||v_part_interval||'); IF (NEW.'||v_control||' % '||v_part_interval||') > ('||v_part_interval||' / 2) THEN v_id_position := (length(v_last_partition) - position(''p_'' in reverse(v_last_partition))) + 2; v_next_partition_id := (substring(v_last_partition from v_id_position)::bigint) + '||v_part_interval||'; WHILE ((v_next_partition_id - v_current_partition_id) / '||v_part_interval||') <= '||v_premake||' LOOP v_next_partition_name := @extschema@.create_id_partition('||quote_literal(p_parent_table)||', '||quote_literal(v_control)||',' ||v_part_interval||', ARRAY[v_next_partition_id]); UPDATE @extschema@.part_config SET last_partition = v_next_partition_name WHERE parent_table = '||quote_literal(p_parent_table)||'; PERFORM @extschema@.create_id_function('||quote_literal(p_parent_table)||', NEW.'||v_control||'); v_next_partition_id := v_next_partition_id + '||v_part_interval||'; END LOOP; END IF; END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for current id interval: '||v_current_partition_id||' to '||v_final_partition_id-1); END IF; ELSIF v_type = 'id-dynamic' THEN -- The return inside the partition creation check is there to keep really high ID values from creating new partitions. v_trig_func := 'CREATE OR REPLACE FUNCTION '||v_function_name||'() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_current_partition_id bigint; v_current_partition_name text; v_id_position int; v_last_partition text := '||quote_literal(v_last_partition)||'; v_last_partition_id bigint; v_next_partition_id bigint; v_next_partition_name text; BEGIN IF TG_OP = ''INSERT'' THEN v_current_partition_id := NEW.'||v_control||' - (NEW.'||v_control||' % '||v_part_interval||'); v_current_partition_name := @extschema@.check_name_length('''||v_parent_tablename||''', '''||v_parent_schema||''', v_current_partition_id::text, TRUE); IF (NEW.'||v_control||' % '||v_part_interval||') > ('||v_part_interval||' / 2) THEN v_id_position := (length(v_last_partition) - position(''p_'' in reverse(v_last_partition))) + 2; v_last_partition_id = substring(v_last_partition from v_id_position)::bigint; v_next_partition_id := v_last_partition_id + '||v_part_interval||'; IF NEW.'||v_control||' >= v_next_partition_id THEN RETURN NEW; END IF; WHILE ((v_next_partition_id - v_current_partition_id) / '||v_part_interval||') <= '||v_premake||' LOOP v_next_partition_name := @extschema@.create_id_partition('||quote_literal(p_parent_table)||', '||quote_literal(v_control)||',' ||quote_literal(v_part_interval)||', ARRAY[v_next_partition_id]); IF v_next_partition_name IS NOT NULL THEN UPDATE @extschema@.part_config SET last_partition = v_next_partition_name WHERE parent_table = '||quote_literal(p_parent_table)||'; PERFORM @extschema@.create_id_function('||quote_literal(p_parent_table)||', NEW.'||v_control||'); END IF; v_next_partition_id := v_next_partition_id + '||v_part_interval||'; END LOOP; END IF; SELECT count(*) INTO v_count FROM pg_tables WHERE schemaname ||''.''|| tablename = v_current_partition_name; IF v_count > 0 THEN EXECUTE ''INSERT INTO ''||v_current_partition_name||'' VALUES($1.*)'' USING NEW; ELSE RETURN NEW; END IF; END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for dynamic id table: '||p_parent_table); END IF; ELSE RAISE EXCEPTION 'ERROR: Invalid id partitioning type given: %', v_type; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN CREATE FUNCTION: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition function maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'CRITICAL', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Create the trigger function for the parent table of a time-based partition set */ CREATE OR REPLACE FUNCTION create_time_function(p_parent_table text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_count int; v_current_partition_name text; v_current_partition_timestamp timestamptz; v_datetime_string text; v_final_partition_timestamp timestamptz; v_function_name text; v_job_id bigint; v_jobmon_schema text; v_old_search_path text; v_new_length int; v_next_partition_name text; v_next_partition_timestamp timestamptz; v_parent_schema text; v_parent_tablename text; v_part_interval interval; v_premake int; v_prev_partition_name text; v_prev_partition_timestamp timestamptz; v_step_id bigint; v_trig_func text; v_type text; BEGIN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE FUNCTION: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating partition function for table '||p_parent_table); END IF; SELECT type , part_interval::interval , control , premake , datetime_string INTO v_type , v_part_interval , v_control , v_premake , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_function_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, '_part_trig_func', FALSE); IF v_type = 'time-static' THEN CASE WHEN v_part_interval = '15 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_part_interval = '30 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_part_interval = '1 hour' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_part_interval = '1 day' THEN v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_part_interval = '1 week' THEN v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_part_interval = '1 month' THEN v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_part_interval = '3 months' THEN v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_part_interval = '1 year' THEN v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, to_char(v_current_partition_timestamp, v_datetime_string), TRUE); v_next_partition_timestamp := v_current_partition_timestamp + v_part_interval::interval; v_trig_func := 'CREATE OR REPLACE FUNCTION '||v_function_name||'() RETURNS trigger LANGUAGE plpgsql AS $t$ BEGIN IF TG_OP = ''INSERT'' THEN IF NEW.'||v_control||' >= '||quote_literal(v_current_partition_timestamp)||' AND NEW.'||v_control||' < '||quote_literal(v_next_partition_timestamp)|| ' THEN INSERT INTO '||v_current_partition_name||' VALUES (NEW.*); '; FOR i IN 1..v_premake LOOP v_prev_partition_timestamp := v_current_partition_timestamp - (v_part_interval::interval * i); v_next_partition_timestamp := v_current_partition_timestamp + (v_part_interval::interval * i); v_final_partition_timestamp := v_next_partition_timestamp + (v_part_interval::interval); v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, to_char(v_prev_partition_timestamp, v_datetime_string), TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, to_char(v_next_partition_timestamp, v_datetime_string), TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles edge case of changing premake immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_prev_partition_name; IF v_count > 0 THEN v_trig_func := v_trig_func ||' ELSIF NEW.'||v_control||' >= '||quote_literal(v_prev_partition_timestamp)||' AND NEW.'||v_control||' < '|| quote_literal(v_prev_partition_timestamp + v_part_interval::interval)|| ' THEN INSERT INTO '||v_prev_partition_name||' VALUES (NEW.*);'; END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_next_partition_name; IF v_count > 0 THEN v_trig_func := v_trig_func ||' ELSIF NEW.'||v_control||' >= '||quote_literal(v_next_partition_timestamp)||' AND NEW.'||v_control||' < '|| quote_literal(v_final_partition_timestamp)|| ' THEN INSERT INTO '||v_next_partition_name||' VALUES (NEW.*);'; END IF; END LOOP; v_trig_func := v_trig_func ||' ELSE RETURN NEW; END IF; END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for current time interval: '|| v_current_partition_timestamp||' to '||(v_final_partition_timestamp-'1sec'::interval)); END IF; ELSIF v_type = 'time-dynamic' THEN v_trig_func := 'CREATE OR REPLACE FUNCTION '||v_function_name||'() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_partition_name text; v_partition_timestamp timestamptz; BEGIN IF TG_OP = ''INSERT'' THEN '; CASE WHEN v_part_interval = '15 mins' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''hour'', NEW.'||v_control||') + ''15min''::interval * floor(date_part(''minute'', NEW.'||v_control||') / 15.0);'; WHEN v_part_interval = '30 mins' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''hour'', NEW.'||v_control||') + ''30min''::interval * floor(date_part(''minute'', NEW.'||v_control||') / 30.0);'; WHEN v_part_interval = '1 hour' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''hour'', NEW.'||v_control||');'; WHEN v_part_interval = '1 day' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''day'', NEW.'||v_control||');'; WHEN v_part_interval = '1 week' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''week'', NEW.'||v_control||');'; WHEN v_part_interval = '1 month' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''month'', NEW.'||v_control||');'; WHEN v_part_interval = '3 months' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''quarter'', NEW.'||v_control||');'; WHEN v_part_interval = '1 year' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''year'', NEW.'||v_control||');'; END CASE; v_trig_func := v_trig_func||' v_partition_name := @extschema@.check_name_length('''||v_parent_tablename||''', '''||v_parent_schema||''', to_char(v_partition_timestamp, '||quote_literal(v_datetime_string)||'), TRUE); SELECT count(*) INTO v_count FROM pg_tables WHERE schemaname ||''.''|| tablename = v_partition_name; IF v_count > 0 THEN EXECUTE ''INSERT INTO ''||v_partition_name||'' VALUES($1.*)'' USING NEW; ELSE RETURN NEW; END IF; END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for dynamic time table: '||p_parent_table); END IF; ELSE RAISE EXCEPTION 'ERROR: Invalid time partitioning type given: %', v_type; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN CREATE FUNCTION: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition function maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'CRITICAL', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; CREATE OR REPLACE FUNCTION create_trigger(p_parent_table text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_function_name text; v_new_length int; v_parent_schema text; v_parent_tablename text; v_trig_name text; v_trig_sql text; BEGIN SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); -- Ensure function name matches the naming pattern v_function_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, '_part_trig_func', FALSE); v_trig_sql := 'CREATE TRIGGER '||v_trig_name||' BEFORE INSERT ON '||p_parent_table|| ' FOR EACH ROW EXECUTE PROCEDURE '||v_function_name||'()'; EXECUTE v_trig_sql; END $$; /* * Function to manage pre-creation of the next partitions in a time-based partition set. * Also manages dropping old partitions if the retention option is set. */ CREATE OR REPLACE FUNCTION run_maintenance() RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_create_count int := 0; v_current_partition_timestamp timestamp; v_datetime_string text; v_drop_count int := 0; v_job_id bigint; v_jobmon_schema text; v_last_partition_timestamp timestamp; v_old_search_path text; v_premade_count int; v_quarter text; v_step_id bigint; v_row record; v_time_position int; v_year text; BEGIN v_adv_lock := pg_try_advisory_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman maintenance already running.'; RETURN; END IF; SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN RUN MAINTENANCE'); v_step_id := add_step(v_job_id, 'Running maintenance loop'); END IF; FOR v_row IN SELECT parent_table , type , part_interval::interval , control , premake , datetime_string , last_partition , undo_in_progress FROM @extschema@.part_config WHERE type = 'time-static' OR type = 'time-dynamic' LOOP CONTINUE WHEN v_row.undo_in_progress; CASE WHEN v_row.part_interval = '15 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_row.part_interval = '30 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_row.part_interval = '1 hour' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '1 day' THEN v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '1 week' THEN v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '1 month' THEN v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '3 months' THEN v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '1 year' THEN v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; -- Get position of last occurance of '_p' in child partition name v_time_position := (length(v_row.last_partition) - position('p_' in reverse(v_row.last_partition))) + 2; IF v_row.part_interval != '3 months' THEN v_last_partition_timestamp := to_timestamp(substring(v_row.last_partition from v_time_position), v_row.datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(v_row.last_partition from v_time_position), 'q', 1); v_quarter := split_part(substring(v_row.last_partition from v_time_position), 'q', 2); CASE WHEN v_quarter = '1' THEN v_last_partition_timestamp := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_last_partition_timestamp := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_last_partition_timestamp := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_last_partition_timestamp := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; -- Check and see how many premade partitions there are. v_premade_count = round(EXTRACT('epoch' FROM age(v_last_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.part_interval::interval)); -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed. WHILE v_premade_count < v_row.premake LOOP EXECUTE 'SELECT @extschema@.create_next_time_partition('||quote_literal(v_row.parent_table)||')'; v_create_count := v_create_count + 1; IF v_row.type = 'time-static' THEN EXECUTE 'SELECT @extschema@.create_time_function('||quote_literal(v_row.parent_table)||')'; END IF; v_last_partition_timestamp := v_last_partition_timestamp + v_row.part_interval; v_premade_count = round(EXTRACT('epoch' FROM age(v_last_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.part_interval::interval)); END LOOP; END LOOP; -- end of creation loop -- Manage dropping old partitions if retention option is set FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND (type = 'time-static' OR type = 'time-dynamic') LOOP v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); END LOOP; FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND (type = 'id-static' OR type = 'id-dynamic') LOOP v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Partition maintenance finished. '||v_create_count||' partitions made. '||v_drop_count||' partitions dropped.'); PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman run_maintenance')); EXCEPTION WHEN QUERY_CANCELED THEN PERFORM pg_advisory_unlock(hashtext('pg_partman run_maintenance')); RAISE EXCEPTION '%', SQLERRM; WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN RUN MAINTENANCE'); v_step_id := add_step(v_job_id, 'EXCEPTION before job logging started'); END IF; IF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'CRITICAL', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman run_maintenance')); RAISE EXCEPTION '%', SQLERRM; END $$; /* * Create the next partition in sequence for a time-based partition set */ CREATE OR REPLACE FUNCTION create_next_time_partition (p_parent_table text) RETURNS void LANGUAGE plpgsql AS $$ DECLARE v_control text; v_datetime_string text; v_last_partition text; v_next_partition_timestamp timestamp; v_next_year text; v_part_interval interval; v_quarter text; v_tablename text; v_time_position int; v_type text; v_year text; BEGIN SELECT type , part_interval::interval , control , datetime_string , last_partition FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic') INTO v_type, v_part_interval, v_control, v_datetime_string, v_last_partition; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; -- Double check that last created partition exists IF v_last_partition IS NOT NULL THEN SELECT tablename INTO v_tablename FROM pg_tables WHERE schemaname || '.' || tablename = v_last_partition; IF v_tablename IS NULL THEN RAISE EXCEPTION 'ERROR: previous partition table missing. Unable to determine next proper partition in sequence.'; END IF; ELSE RAISE EXCEPTION 'ERROR: last known partition missing from config table for parent table %.', p_parent_table; END IF; -- pull out datetime portion of last partition's tablename to make the next one v_time_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; IF v_part_interval != '3 months' THEN v_next_partition_timestamp := to_timestamp(substring(v_last_partition from v_time_position), v_datetime_string) + v_part_interval; ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(v_last_partition from v_time_position), 'q', 1); v_next_year := extract('year' from to_date(v_year, 'YYYY')+'1year'::interval); v_quarter := split_part(substring(v_last_partition from v_time_position), 'q', 2); CASE WHEN v_quarter = '1' THEN v_next_partition_timestamp := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_next_partition_timestamp := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_next_partition_timestamp := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_next_partition_timestamp := to_timestamp(v_next_year || '-01-01', 'YYYY-MM-DD'); END CASE; END IF; EXECUTE 'SELECT @extschema@.create_time_partition('||quote_literal(p_parent_table)||','||quote_literal(v_control)||','||quote_literal(v_part_interval)||',' ||quote_literal(v_datetime_string)||','||quote_literal(ARRAY[v_next_partition_timestamp])||')' INTO v_last_partition; IF v_last_partition IS NOT NULL THEN UPDATE @extschema@.part_config SET last_partition = v_last_partition WHERE parent_table = p_parent_table; END IF; END $$; /* * Function to undo time-based partitioning created by this extension */ CREATE OR REPLACE FUNCTION undo_partition_time(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval interval DEFAULT NULL, p_keep_table boolean DEFAULT true) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_batch_loop_count int := 0; v_child_min timestamptz; v_child_loop_total bigint := 0; v_child_table text; v_control text; v_function_name text; v_inner_loop_count int; v_job_id bigint; v_jobmon_schema text; v_move_sql text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_part_interval interval; v_row record; v_rowcount bigint; v_step_id bigint; v_total bigint := 0; v_trig_name text; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_lock(hashtext('pg_partman undo_time_partition')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_time_partition already running.'; RETURN 0; END IF; SELECT part_interval::interval , control INTO v_part_interval , v_control FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic'); IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Undoing partitioning for table '||p_parent_table); END IF; IF p_batch_interval IS NULL THEN p_batch_interval := v_part_interval; END IF; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables. SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, '_part_trig_func', FALSE); EXECUTE 'DROP TRIGGER IF EXISTS '||v_trig_name||' ON '||p_parent_table; EXECUTE 'DROP FUNCTION IF EXISTS '||v_function_name||'()'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); END IF; <> WHILE v_batch_loop_count < p_batch_count LOOP SELECT n.nspname||'.'||c.relname INTO v_child_table FROM pg_inherits i JOIN pg_class c ON i.inhrelid = c.oid JOIN pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC; EXIT WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing child partition: '||v_child_table); END IF; EXECUTE 'SELECT min('||v_control||') FROM '||v_child_table INTO v_child_min; IF v_child_min IS NULL THEN -- No rows left in this child table. Remove from partition set. EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF p_keep_table = false THEN EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table UNINHERITED, not DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; END IF; v_undo_count := v_undo_count + 1; CONTINUE outer_child_loop; END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP -- Get everything from the current child minimum up to the multiples of the given interval v_move_sql := 'WITH move_data AS (DELETE FROM '||v_child_table|| ' WHERE '||v_control||' <= '||quote_literal(v_child_min + (p_batch_interval * v_inner_loop_count))||' RETURNING *) INSERT INTO '||p_parent_table||' SELECT * FROM move_data'; EXECUTE v_move_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Moved '||v_child_loop_total||' rows to parent.'); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; IF v_batch_loop_count < p_batch_count THEN -- FOR loop never ran, so there's no child tables left. DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) to the parent. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', 'Copied '||v_total||' row(s) to the parent. Removed '||v_undo_count||' partitions.'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman undo_time_partition')); RETURN v_total; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition function maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'CRITICAL', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to undo id-based partitioning created by this extension */ CREATE OR REPLACE FUNCTION undo_partition_id(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval bigint DEFAULT NULL, p_keep_table boolean DEFAULT true) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_batch_loop_count int := 0; v_child_loop_total bigint := 0; v_child_min bigint; v_child_table text; v_control text; v_function_name text; v_inner_loop_count int; v_job_id bigint; v_jobmon_schema text; v_move_sql text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_part_interval bigint; v_row record; v_rowcount bigint; v_step_id bigint; v_trig_name text; v_total bigint := 0; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_lock(hashtext('pg_partman undo_id_partition')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_id_partition already running.'; RETURN 0; END IF; SELECT part_interval::bigint , control INTO v_part_interval , v_control FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Undoing partitioning for table '||p_parent_table); END IF; IF p_batch_interval IS NULL THEN p_batch_interval := v_part_interval; END IF; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables and stop new id partitions from being made. SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, '_part_trig_func', FALSE); EXECUTE 'DROP TRIGGER IF EXISTS '||v_trig_name||' ON '||p_parent_table; EXECUTE 'DROP FUNCTION IF EXISTS '||v_function_name||'()'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); END IF; <> WHILE v_batch_loop_count < p_batch_count LOOP SELECT n.nspname||'.'||c.relname INTO v_child_table FROM pg_inherits i JOIN pg_class c ON i.inhrelid = c.oid JOIN pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC; EXIT WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing child partition: '||v_child_table); END IF; EXECUTE 'SELECT min('||v_control||') FROM '||v_child_table INTO v_child_min; IF v_child_min IS NULL THEN -- No rows left in this child table. Remove from partition set. EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF p_keep_table = false THEN EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table UNINHERITED, not DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; END IF; v_undo_count := v_undo_count + 1; CONTINUE outer_child_loop; END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP -- Get everything from the current child minimum up to the multiples of the given interval v_move_sql := 'WITH move_data AS (DELETE FROM '||v_child_table|| ' WHERE '||v_control||' <= '||quote_literal(v_child_min + (p_batch_interval * v_inner_loop_count))||' RETURNING *) INSERT INTO '||p_parent_table||' SELECT * FROM move_data'; EXECUTE v_move_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Moved '||v_child_loop_total||' rows to parent.'); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; IF v_batch_loop_count < p_batch_count THEN -- FOR loop never ran, so there's no child tables left. DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) to the parent. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', 'Copied '||v_total||' row(s) to the parent. Removed '||v_undo_count||' partitions.'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman undo_id_partition')); RETURN v_total; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition function maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'CRITICAL', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to undo partitioning. * Will actually work on any parent/child table set, not just ones created by pg_partman. */ CREATE OR REPLACE FUNCTION undo_partition(p_parent_table text, p_batch_count int DEFAULT 1, p_keep_table boolean DEFAULT true) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_batch_loop_count bigint := 0; v_child_count bigint; v_child_table text; v_copy_sql text; v_function_name text; v_job_id bigint; v_jobmon_schema text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_part_interval interval; v_rowcount bigint; v_step_id bigint; v_total bigint := 0; v_trig_name text; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_lock(hashtext('pg_partman undo_partition')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition already running.'; RETURN 0; END IF; SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Undoing partitioning for table '||p_parent_table); END IF; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables and stop new id partitions from being made. SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, '_part_trig_func', FALSE); EXECUTE 'DROP TRIGGER IF EXISTS '||v_trig_name||' ON '||p_parent_table; EXECUTE 'DROP FUNCTION IF EXISTS '||v_function_name||'()'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); END IF; WHILE v_batch_loop_count < p_batch_count LOOP SELECT n.nspname||'.'||c.relname INTO v_child_table FROM pg_inherits i JOIN pg_class c ON i.inhrelid = c.oid JOIN pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC; EXIT WHEN v_child_table IS NULL; EXECUTE 'SELECT count(*) FROM '||v_child_table INTO v_child_count; IF v_child_count = 0 THEN -- No rows left in this child table. Remove from partition set. EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF p_keep_table = false THEN EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table DROPPED. Moved '||coalesce(v_rowcount, 0)||' rows to parent'); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table UNINHERITED, not DROPPED. Copied '||coalesce(v_rowcount, 0)||' rows to parent'); END IF; END IF; v_undo_count := v_undo_count + 1; CONTINUE; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing child partition: '||v_child_table); END IF; v_copy_sql := 'INSERT INTO '||p_parent_table||' SELECT * FROM '||v_child_table; EXECUTE v_copy_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF p_keep_table = false THEN EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table DROPPED. Moved '||v_rowcount||' rows to parent'); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table UNINHERITED, not DROPPED. Copied '||v_rowcount||' rows to parent'); END IF; END IF; v_batch_loop_count := v_batch_loop_count + 1; v_undo_count := v_undo_count + 1; END LOOP; IF v_undo_count = 0 THEN -- FOR loop never ran, so there's no child tables left. DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman (if it existed)'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) from % child table(s) to the parent: %', v_total, v_undo_count, p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', 'Copied '||v_total||' row(s) from '||v_undo_count||' child table(s) to the parent'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman undo_partition')); RETURN v_total; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition function maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'CRITICAL', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to drop child tables from a time-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ CREATE OR REPLACE FUNCTION drop_partition_id(p_parent_table text, p_retention bigint DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_child_table text; v_control text; v_drop_count int := 0; v_id_position int; v_index record; v_job_id bigint; v_jobmon_schema text; v_max bigint; v_old_search_path text; v_part_interval bigint; v_partition_id bigint; v_retention bigint; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_step_id bigint; BEGIN v_adv_lock := pg_try_advisory_lock(hashtext('pg_partman drop_partition_id')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_id already running.'; RETURN 0; END IF; SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; -- Allow override of configuration options IF p_retention IS NULL THEN SELECT part_interval::bigint , control , retention::bigint , retention_keep_table , retention_keep_index , retention_schema INTO v_part_interval , v_control , v_retention , v_retention_keep_table , v_retention_keep_index , v_retention_schema FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic') AND retention IS NOT NULL; IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE SELECT part_interval::bigint , control , retention_keep_table , retention_keep_index , retention_schema INTO v_part_interval , v_control , v_retention_keep_table , v_retention_keep_index , v_retention_schema FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); v_retention := p_retention; IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN DROP ID PARTITION: '|| p_parent_table); END IF; EXECUTE 'SELECT max('||v_control||') FROM '||p_parent_table INTO v_max; -- Loop through child tables of the given parent FOR v_child_table IN SELECT n.nspname||'.'||c.relname FROM pg_inherits i join pg_class c ON i.inhrelid = c.oid join pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC LOOP v_id_position := (length(v_child_table) - position('p_' in reverse(v_child_table))) + 2; v_partition_id := substring(v_child_table from v_id_position)::bigint; -- Add one interval since partition names contain the start of the constraint period IF v_retention <= (v_max - (v_partition_id + v_part_interval)) THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Uninherit table '||v_child_table||' from '||p_parent_table); END IF; EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Drop table '||v_child_table); END IF; EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN FOR v_index IN SELECT i.indexrelid::regclass AS name , c.conname FROM pg_catalog.pg_index i LEFT JOIN pg_catalog.pg_constraint c ON i.indexrelid = c.conindid WHERE i.indrelid = v_child_table::regclass LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Drop index '||v_index.name||' from '||v_child_table); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_child_table||' DROP CONSTRAINT '||v_index.conname; ELSE EXECUTE 'DROP INDEX '||v_index.name; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Moving table '||v_child_table||' to schema '||v_retention_schema); END IF; EXECUTE 'ALTER TABLE '||v_child_table||' SET SCHEMA '||v_retention_schema; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', v_drop_count||' partitions dropped.'); PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman drop_partition_id')); RETURN v_drop_count; EXCEPTION WHEN QUERY_CANCELED THEN PERFORM pg_advisory_unlock(hashtext('pg_partman drop_partition_id')); RAISE EXCEPTION '%', SQLERRM; WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN DROP ID PARTITION'); v_step_id := add_step(v_job_id, 'EXCEPTION before job logging started'); END IF; IF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'CRITICAL', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman drop_partition_id')); RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to drop child tables from a time-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ CREATE OR REPLACE FUNCTION drop_partition_time(p_parent_table text, p_retention interval DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_child_table text; v_datetime_string text; v_drop_count int := 0; v_index record; v_job_id bigint; v_jobmon_schema text; v_old_search_path text; v_part_interval interval; v_partition_timestamp timestamp; v_quarter text; v_retention interval; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_step_id bigint; v_time_position int; v_year text; BEGIN v_adv_lock := pg_try_advisory_lock(hashtext('pg_partman drop_partition_time')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_time already running.'; RETURN 0; END IF; SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; -- Allow override of configuration options IF p_retention IS NULL THEN SELECT part_interval::interval , retention::interval , retention_keep_table , retention_keep_index , datetime_string , retention_schema INTO v_part_interval , v_retention , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic') AND retention IS NOT NULL; IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE SELECT part_interval::interval , retention_keep_table , retention_keep_index , datetime_string , retention_schema INTO v_part_interval , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic'); v_retention := p_retention; IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN DROP TIME PARTITION: '|| p_parent_table); END IF; -- Loop through child tables of the given parent FOR v_child_table IN SELECT n.nspname||'.'||c.relname FROM pg_inherits i join pg_class c ON i.inhrelid = c.oid join pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC LOOP -- pull out datetime portion of last partition's tablename to make the next one v_time_position := (length(v_child_table) - position('p_' in reverse(v_child_table))) + 2; IF v_part_interval != '3 months' THEN v_partition_timestamp := to_timestamp(substring(v_child_table from v_time_position), v_datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(v_child_table from v_time_position), 'q', 1); v_quarter := split_part(substring(v_child_table from v_time_position), 'q', 2); CASE WHEN v_quarter = '1' THEN v_partition_timestamp := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_partition_timestamp := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_partition_timestamp := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_partition_timestamp := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; -- Add one interval since partition names contain the start of the constraint period IF v_retention < (CURRENT_TIMESTAMP - (v_partition_timestamp + v_part_interval)) THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Uninherit table '||v_child_table||' from '||p_parent_table); END IF; EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Drop table '||v_child_table); END IF; EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN FOR v_index IN SELECT i.indexrelid::regclass AS name , c.conname FROM pg_catalog.pg_index i LEFT JOIN pg_catalog.pg_constraint c ON i.indexrelid = c.conindid WHERE i.indrelid = v_child_table::regclass LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Drop index '||v_index.name||' from '||v_child_table); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_child_table||' DROP CONSTRAINT '||v_index.conname; ELSE EXECUTE 'DROP INDEX '||v_index.name; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Moving table '||v_child_table||' to schema '||v_retention_schema); END IF; EXECUTE 'ALTER TABLE '||v_child_table||' SET SCHEMA '||v_retention_schema; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', v_drop_count||' partitions dropped.'); PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman drop_partition_time')); RETURN v_drop_count; EXCEPTION WHEN QUERY_CANCELED THEN PERFORM pg_advisory_unlock(hashtext('pg_partman drop_partition_time')); RAISE EXCEPTION '%', SQLERRM; WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN DROP TIME PARTITION'); v_step_id := add_step(v_job_id, 'EXCEPTION before job logging started'); END IF; IF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'CRITICAL', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; PERFORM pg_advisory_unlock(hashtext('pg_partman drop_partition_time')); RAISE EXCEPTION '%', SQLERRM; END $$; pg_partman-4.6.0/updates/pg_partman--1.4.0--1.4.1.sql000066400000000000000000000355111412761706100214300ustar00rootroot00000000000000-- Assign child partitions to the tablespace of the parent. This will only apply to newly created partitions after this update is installed. To fix existing partitions, you will have to manually alter the child tables. Thanks to https://github.com/joelhoffman for the fix. /* * Function to create id partitions */ CREATE OR REPLACE FUNCTION create_id_partition (p_parent_table text, p_control text, p_interval bigint, p_partition_ids bigint[]) RETURNS text LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_grantees text[]; v_job_id bigint; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_parent_tablespace text; v_partition_name text; v_revoke text[]; v_step_id bigint; v_tablename text; v_id bigint; BEGIN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; FOREACH v_id IN ARRAY p_partition_ids LOOP v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_id::text, TRUE); -- If child table already exists, skip creation SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_tablename IS NOT NULL THEN CONTINUE; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_id||' to '||(v_id + p_interval)-1); END IF; EXECUTE 'CREATE TABLE '||v_partition_name||' (LIKE '||p_parent_table||' INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)'; SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_parent_tablespace IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_partition_name||' SET TABLESPACE '||v_parent_tablespace; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' ADD CONSTRAINT '||v_tablename||'_partition_check CHECK ('||p_control||'>='||quote_literal(v_id)||' AND '||p_control||'<'||quote_literal(v_id + p_interval)||')'; EXECUTE 'ALTER TABLE '||v_partition_name||' INHERIT '||p_parent_table; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP EXECUTE 'GRANT '||array_to_string(v_parent_grant.types, ',')||' ON '||v_partition_name||' TO '||v_parent_grant.grantee; SELECT array_agg(r) INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE '||array_to_string(v_revoke, ',')||' ON '||v_partition_name||' FROM '||v_parent_grant.grantee||' CASCADE'; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN SELECT array_agg(r) INTO v_revoke FROM ( SELECT DISTINCT grantee::text AS r FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_partition_name EXCEPT SELECT unnest(v_grantees)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE ALL ON '||v_partition_name||' FROM '||array_to_string(v_revoke, ','); END IF; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' OWNER TO '||v_parent_owner; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); PERFORM close_job(v_job_id); END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_partition_name; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'BAD', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to create a child table in a time-based partition set */ CREATE OR REPLACE FUNCTION create_time_partition (p_parent_table text, p_control text, p_interval interval, p_datetime_string text, p_partition_times timestamp[]) RETURNS text LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_grantees text[]; v_job_id bigint; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_partition_name text; v_partition_suffix text; v_parent_tablespace text; v_partition_timestamp_end timestamp; v_partition_timestamp_start timestamp; v_quarter text; v_revoke text[]; v_step_id bigint; v_tablename text; v_trunc_value text; v_time timestamp; v_year text; BEGIN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; FOREACH v_time IN ARRAY p_partition_times LOOP IF p_interval <= '1 year' AND p_interval <> '3 months' AND p_interval <> '1 week' THEN v_partition_suffix := to_char(v_time, 'YYYY'); v_trunc_value := 'year'; IF p_interval <= '1 month' AND p_interval <> '1 week' THEN v_partition_suffix := v_partition_suffix ||'_'|| to_char(v_time, 'MM'); v_trunc_value := 'month'; IF p_interval <= '1 day' THEN v_partition_suffix := v_partition_suffix ||'_'|| to_char(v_time, 'DD'); v_trunc_value := 'day'; IF p_interval <= '1 hour' THEN v_partition_suffix := v_partition_suffix || '_' || to_char(v_time, 'HH24'); IF p_interval <> '30 mins' AND p_interval <> '15 mins' THEN v_partition_suffix := v_partition_suffix || '00'; v_trunc_value := 'hour'; ELSIF p_interval = '15 mins' THEN IF date_part('minute', v_time) < 15 THEN v_partition_suffix := v_partition_suffix || '00'; ELSIF date_part('minute', v_time) >= 15 AND date_part('minute', v_time) < 30 THEN v_partition_suffix := v_partition_suffix || '15'; ELSIF date_part('minute', v_time) >= 30 AND date_part('minute', v_time) < 45 THEN v_partition_suffix := v_partition_suffix || '30'; ELSE v_partition_suffix := v_partition_suffix || '45'; END IF; v_trunc_value := 'minute'; ELSIF p_interval = '30 mins' THEN IF date_part('minute', v_time) < 30 THEN v_partition_suffix := v_partition_suffix || '00'; ELSE v_partition_suffix := v_partition_suffix || '30'; END IF; v_trunc_value := 'minute'; END IF; END IF; -- end hour IF END IF; -- end day IF END IF; -- end month IF ELSIF p_interval = '1 week' THEN v_partition_suffix := to_char(v_time, 'IYYY') || 'w' || to_char(v_time, 'IW'); v_trunc_value := 'week'; END IF; -- end year/week IF -- "Q" is ignored in to_timestamp, so handle special case IF p_interval = '3 months' THEN v_year := to_char(v_time, 'YYYY'); v_quarter := to_char(v_time, 'Q'); v_partition_suffix := v_year || 'q' || v_quarter; v_trunc_value := 'quarter'; CASE WHEN v_quarter = '1' THEN v_partition_timestamp_start := date_trunc(v_trunc_value, to_timestamp(v_year || '-01-01', 'YYYY-MM-DD')); WHEN v_quarter = '2' THEN v_partition_timestamp_start := date_trunc(v_trunc_value, to_timestamp(v_year || '-04-01', 'YYYY-MM-DD')); WHEN v_quarter = '3' THEN v_partition_timestamp_start := date_trunc(v_trunc_value, to_timestamp(v_year || '-07-01', 'YYYY-MM-DD')); WHEN v_quarter = '4' THEN v_partition_timestamp_start := date_trunc(v_trunc_value, to_timestamp(v_year || '-10-01', 'YYYY-MM-DD')); END CASE; v_partition_timestamp_end := date_trunc(v_trunc_value, (v_partition_timestamp_start + p_interval)); END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_partition_suffix, TRUE); -- pull out datetime portion of last partition's tablename if it matched anything except quarterly IF p_interval <> '3 months' AND v_trunc_value IS NOT NULL THEN v_partition_timestamp_start := date_trunc(v_trunc_value, to_timestamp(v_partition_suffix, p_datetime_string)); v_partition_timestamp_end := date_trunc(v_trunc_value, to_timestamp(v_partition_suffix, p_datetime_string) + p_interval); END IF; SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_tablename IS NOT NULL THEN CONTINUE; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_partition_timestamp_start||' to '||(v_partition_timestamp_end-'1sec'::interval)); END IF; EXECUTE 'CREATE TABLE '||v_partition_name||' (LIKE '||p_parent_table||' INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)'; SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_parent_tablespace IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_partition_name||' SET TABLESPACE '||v_parent_tablespace; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' ADD CONSTRAINT '||v_tablename||'_partition_check CHECK ('||p_control||'>='||quote_literal(v_partition_timestamp_start)||' AND '||p_control||'<'||quote_literal(v_partition_timestamp_end)||')'; EXECUTE 'ALTER TABLE '||v_partition_name||' INHERIT '||p_parent_table; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP EXECUTE 'GRANT '||array_to_string(v_parent_grant.types, ',')||' ON '||v_partition_name||' TO '||v_parent_grant.grantee; SELECT array_agg(r) INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE '||array_to_string(v_revoke, ',')||' ON '||v_partition_name||' FROM '||v_parent_grant.grantee||' CASCADE'; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN SELECT array_agg(r) INTO v_revoke FROM ( SELECT DISTINCT grantee::text AS r FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_partition_name EXCEPT SELECT unnest(v_grantees)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE ALL ON '||v_partition_name||' FROM '||array_to_string(v_revoke, ','); END IF; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' OWNER TO '||v_parent_owner; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); PERFORM close_job(v_job_id); END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_partition_name; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'CRITICAL', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; pg_partman-4.6.0/updates/pg_partman--1.4.1--1.4.2.sql000066400000000000000000000202761412761706100214340ustar00rootroot00000000000000DROP FUNCTION IF EXISTS partition_data_id(text, int, int); DROP FUNCTION IF EXISTS partition_data_time(text, int, interval); /* * Populate the child table(s) of an id-based partition set with old data from the original parent */ CREATE FUNCTION partition_data_id(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval int DEFAULT NULL, p_lock_wait numeric DEFAULT 0) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_last_partition_name text; v_max_partition_id bigint; v_min_control bigint; v_min_partition_id bigint; v_part_interval bigint; v_partition_id bigint[]; v_rowcount bigint; v_sql text; v_total_rows bigint := 0; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; BEGIN SELECT part_interval::bigint, control INTO v_part_interval, v_control FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF p_batch_interval IS NULL OR p_batch_interval > v_part_interval THEN p_batch_interval := v_part_interval; END IF; FOR i IN 1..p_batch_count LOOP EXECUTE 'SELECT min('||v_control||') FROM ONLY '||p_parent_table INTO v_min_control; IF v_min_control IS NULL THEN RETURN 0; END IF; v_min_partition_id = v_min_control - (v_min_control % v_part_interval); v_partition_id := ARRAY[v_min_partition_id]; -- RAISE NOTICE 'v_partition_id: %',v_partition_id; IF (v_min_control + p_batch_interval) >= (v_min_partition_id + v_part_interval) THEN v_max_partition_id := v_min_partition_id + v_part_interval; ELSE v_max_partition_id := v_min_control + p_batch_interval; END IF; -- RAISE NOTICE 'v_max_partition_id: %',v_max_partition_id; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN v_sql := 'SELECT * FROM ' || p_parent_table || ' WHERE '||v_control||' >= '||v_min_control|| ' AND '||v_control||' < '||v_max_partition_id ||' FOR UPDATE NOWAIT'; EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; v_sql := 'SELECT @extschema@.create_id_partition('||quote_literal(p_parent_table)||','||quote_literal(v_control)||',' ||v_part_interval||','||quote_literal(v_partition_id)||')'; -- RAISE NOTICE 'v_sql: %', v_sql; EXECUTE v_sql INTO v_last_partition_name; v_sql := 'WITH partition_data AS ( DELETE FROM ONLY '||p_parent_table||' WHERE '||v_control||' >= '||v_min_control|| ' AND '||v_control||' < '||v_max_partition_id||' RETURNING *) INSERT INTO '||v_last_partition_name||' SELECT * FROM partition_data'; -- RAISE NOTICE 'v_sql: %', v_sql; EXECUTE v_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; RETURN v_total_rows; END $$; /* * Populate the child table(s) of a time-based partition set with old data from the original parent */ CREATE FUNCTION partition_data_time(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval interval DEFAULT NULL, p_lock_wait numeric DEFAULT 0) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_datetime_string text; v_last_partition_name text; v_max_partition_timestamp timestamp; v_min_control timestamp; v_min_partition_timestamp timestamp; v_part_interval interval; v_partition_timestamp timestamp[]; v_rowcount bigint; v_sql text; v_total_rows bigint := 0; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; BEGIN SELECT part_interval::interval, control, datetime_string INTO v_part_interval, v_control, v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF p_batch_interval IS NULL OR p_batch_interval > v_part_interval THEN p_batch_interval := v_part_interval; END IF; FOR i IN 1..p_batch_count LOOP EXECUTE 'SELECT min('||v_control||') FROM ONLY '||p_parent_table INTO v_min_control; IF v_min_control IS NULL THEN RETURN 0; END IF; CASE WHEN v_part_interval = '15 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_min_control) + '15min'::interval * floor(date_part('minute', v_min_control) / 15.0); WHEN v_part_interval = '30 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_min_control) + '30min'::interval * floor(date_part('minute', v_min_control) / 30.0); WHEN v_part_interval = '1 hour' THEN v_min_partition_timestamp := date_trunc('hour', v_min_control); WHEN v_part_interval = '1 day' THEN v_min_partition_timestamp := date_trunc('day', v_min_control); WHEN v_part_interval = '1 week' THEN v_min_partition_timestamp := date_trunc('week', v_min_control); WHEN v_part_interval = '1 month' THEN v_min_partition_timestamp := date_trunc('month', v_min_control); WHEN v_part_interval = '3 months' THEN v_min_partition_timestamp := date_trunc('quarter', v_min_control); WHEN v_part_interval = '1 year' THEN v_min_partition_timestamp := date_trunc('year', v_min_control); END CASE; v_partition_timestamp := ARRAY[v_min_partition_timestamp]; -- RAISE NOTICE 'v_partition_timestamp: %',v_partition_timestamp; IF (v_min_control + p_batch_interval) >= (v_min_partition_timestamp + v_part_interval) THEN v_max_partition_timestamp := v_min_partition_timestamp + v_part_interval; ELSE v_max_partition_timestamp := v_min_control + p_batch_interval; END IF; -- RAISE NOTICE 'v_max_partition_timestamp: %',v_max_partition_timestamp; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN v_sql := 'SELECT * FROM ' || p_parent_table || ' WHERE '||v_control||' >= '||v_min_control|| ' AND '||v_control||' < '||v_max_partition_id ||' FOR UPDATE NOWAIT'; EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; v_sql := 'SELECT @extschema@.create_time_partition('||quote_literal(p_parent_table)||','||quote_literal(v_control)||',' ||quote_literal(v_part_interval)||','||quote_literal(v_datetime_string)||','||quote_literal(v_partition_timestamp)||')'; -- RAISE NOTICE 'v_sql: %', v_sql; EXECUTE v_sql INTO v_last_partition_name; v_sql := 'WITH partition_data AS ( DELETE FROM ONLY '||p_parent_table||' WHERE '||v_control||' >= '||quote_literal(v_min_control)|| ' AND '||v_control||' < '||quote_literal(v_max_partition_timestamp)||' RETURNING *) INSERT INTO '||v_last_partition_name||' SELECT * FROM partition_data'; -- RAISE NOTICE 'v_sql: %', v_sql; EXECUTE v_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; RETURN v_total_rows; END $$; pg_partman-4.6.0/updates/pg_partman--1.4.2--1.4.3.sql000066400000000000000000000011601412761706100214250ustar00rootroot00000000000000-- Fix "make install" to work in PostgreSQL 9.3.x without throwing an error. -- "make install" now installs the python script files to /bin. They are now also executable and have the proper #! line at the top. -- Updated the rest of the python scripts to use argparse library for options (thanks to Josh Berkus for the assistance on this). -- Some of the command line options have changed for the scripts. See the --help for each script to ensure you are using the correct parameters. -- No code changes to core extension, however this file is required to update to pg_partman 1.4.3 and higher. pg_partman-4.6.0/updates/pg_partman--1.4.3--1.4.4.sql000066400000000000000000000204661412761706100214410ustar00rootroot00000000000000-- Bug fix: Typos in partition_time_data/id() functions. Only ran into this if a lockwait was hit while trying to partition data. /* * Populate the child table(s) of a time-based partition set with old data from the original parent */ CREATE OR REPLACE FUNCTION partition_data_time(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval interval DEFAULT NULL, p_lock_wait numeric DEFAULT 0) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_datetime_string text; v_last_partition_name text; v_max_partition_timestamp timestamp; v_min_control timestamp; v_min_partition_timestamp timestamp; v_part_interval interval; v_partition_timestamp timestamp[]; v_rowcount bigint; v_sql text; v_total_rows bigint := 0; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; BEGIN SELECT part_interval::interval, control, datetime_string INTO v_part_interval, v_control, v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF p_batch_interval IS NULL OR p_batch_interval > v_part_interval THEN p_batch_interval := v_part_interval; END IF; FOR i IN 1..p_batch_count LOOP EXECUTE 'SELECT min('||v_control||') FROM ONLY '||p_parent_table INTO v_min_control; IF v_min_control IS NULL THEN RETURN 0; END IF; CASE WHEN v_part_interval = '15 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_min_control) + '15min'::interval * floor(date_part('minute', v_min_control) / 15.0); WHEN v_part_interval = '30 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_min_control) + '30min'::interval * floor(date_part('minute', v_min_control) / 30.0); WHEN v_part_interval = '1 hour' THEN v_min_partition_timestamp := date_trunc('hour', v_min_control); WHEN v_part_interval = '1 day' THEN v_min_partition_timestamp := date_trunc('day', v_min_control); WHEN v_part_interval = '1 week' THEN v_min_partition_timestamp := date_trunc('week', v_min_control); WHEN v_part_interval = '1 month' THEN v_min_partition_timestamp := date_trunc('month', v_min_control); WHEN v_part_interval = '3 months' THEN v_min_partition_timestamp := date_trunc('quarter', v_min_control); WHEN v_part_interval = '1 year' THEN v_min_partition_timestamp := date_trunc('year', v_min_control); END CASE; v_partition_timestamp := ARRAY[v_min_partition_timestamp]; -- RAISE NOTICE 'v_partition_timestamp: %',v_partition_timestamp; IF (v_min_control + p_batch_interval) >= (v_min_partition_timestamp + v_part_interval) THEN v_max_partition_timestamp := v_min_partition_timestamp + v_part_interval; ELSE v_max_partition_timestamp := v_min_control + p_batch_interval; END IF; -- RAISE NOTICE 'v_max_partition_timestamp: %',v_max_partition_timestamp; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN v_sql := 'SELECT * FROM ONLY ' || p_parent_table || ' WHERE '||v_control||' >= '||quote_literal(v_min_control)|| ' AND '||v_control||' < '||quote_literal(v_max_partition_timestamp) ||' FOR UPDATE NOWAIT'; EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; v_sql := 'SELECT @extschema@.create_time_partition('||quote_literal(p_parent_table)||','||quote_literal(v_control)||',' ||quote_literal(v_part_interval)||','||quote_literal(v_datetime_string)||','||quote_literal(v_partition_timestamp)||')'; -- RAISE NOTICE 'v_sql: %', v_sql; EXECUTE v_sql INTO v_last_partition_name; v_sql := 'WITH partition_data AS ( DELETE FROM ONLY '||p_parent_table||' WHERE '||v_control||' >= '||quote_literal(v_min_control)|| ' AND '||v_control||' < '||quote_literal(v_max_partition_timestamp)||' RETURNING *) INSERT INTO '||v_last_partition_name||' SELECT * FROM partition_data'; -- RAISE NOTICE 'v_sql: %', v_sql; EXECUTE v_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; RETURN v_total_rows; END $$; /* * Populate the child table(s) of an id-based partition set with old data from the original parent */ CREATE OR REPLACE FUNCTION partition_data_id(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval int DEFAULT NULL, p_lock_wait numeric DEFAULT 0) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_last_partition_name text; v_max_partition_id bigint; v_min_control bigint; v_min_partition_id bigint; v_part_interval bigint; v_partition_id bigint[]; v_rowcount bigint; v_sql text; v_total_rows bigint := 0; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; BEGIN SELECT part_interval::bigint, control INTO v_part_interval, v_control FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF p_batch_interval IS NULL OR p_batch_interval > v_part_interval THEN p_batch_interval := v_part_interval; END IF; FOR i IN 1..p_batch_count LOOP EXECUTE 'SELECT min('||v_control||') FROM ONLY '||p_parent_table INTO v_min_control; IF v_min_control IS NULL THEN RETURN 0; END IF; v_min_partition_id = v_min_control - (v_min_control % v_part_interval); v_partition_id := ARRAY[v_min_partition_id]; -- RAISE NOTICE 'v_partition_id: %',v_partition_id; IF (v_min_control + p_batch_interval) >= (v_min_partition_id + v_part_interval) THEN v_max_partition_id := v_min_partition_id + v_part_interval; ELSE v_max_partition_id := v_min_control + p_batch_interval; END IF; -- RAISE NOTICE 'v_max_partition_id: %',v_max_partition_id; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN v_sql := 'SELECT * FROM ONLY ' || p_parent_table || ' WHERE '||v_control||' >= '||quote_literal(v_min_control)|| ' AND '||v_control||' < '||quote_literal(v_max_partition_id) ||' FOR UPDATE NOWAIT'; EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; v_sql := 'SELECT @extschema@.create_id_partition('||quote_literal(p_parent_table)||','||quote_literal(v_control)||',' ||v_part_interval||','||quote_literal(v_partition_id)||')'; -- RAISE NOTICE 'v_sql: %', v_sql; EXECUTE v_sql INTO v_last_partition_name; v_sql := 'WITH partition_data AS ( DELETE FROM ONLY '||p_parent_table||' WHERE '||v_control||' >= '||v_min_control|| ' AND '||v_control||' < '||v_max_partition_id||' RETURNING *) INSERT INTO '||v_last_partition_name||' SELECT * FROM partition_data'; -- RAISE NOTICE 'v_sql: %', v_sql; EXECUTE v_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; RETURN v_total_rows; END $$; pg_partman-4.6.0/updates/pg_partman--1.4.4--1.4.5.sql000066400000000000000000000011261412761706100214330ustar00rootroot00000000000000-- Fixed bug in reapply_indexes.py script that could cause all new indexes to be added to the parent instead of the children. This was happening if the parent table's schema was in the search_path of the role that the script uses to connect to the database. -- Removed any unneeded library imports in all python scripts. -- Moved python scripts from "extras" folder to "bin" folder. Now that they're actually getting installed as part of "make install" they're not really extras anymore. -- No code changes to core extension, however this file is required to update to pg_partman 1.4.5 and higher. pg_partman-4.6.0/updates/pg_partman--1.4.5--1.5.0.sql000066400000000000000000001234271412761706100214410ustar00rootroot00000000000000-- New functions that can manage additional constraints on child tables older than premake value based on what their min/max values are. This allows constraint exclusion to become usable on columns other than the partitioning column. However, this is only useful if older tables are no longer editing the columns that will have constraints placed on them, otherwise you risk constraint violations. See blog post for a more thorough explanation and examples: http://www.keithf4.com/managing-constraint-exclusion-in-table-partitioning -- New python script for using above functions to drop/apply constraints on an entire partition set without causing excessive lock issues. -- show_partitions() function is now guarenteed to return a list of partitions in the order that makes sense for the partition type/interval for the set. Additional option to specify ascending or descending order (defaults to ascending). -- Check for valid parameter values in partition creation function (github issue #14 from Josh Berkus) -- Added drop index concurrently option (--drop_concurrently) to reapply_indexes.py script. Only works for 9.2+ -- Changed run_maintenance() to use advisory transaction lock instead of session level lock. -- Fixed missing library import in python scripts. -- Organized docmentation of functions. ALTER TABLE @extschema@.part_config ADD constraint_cols text[]; CREATE TEMP TABLE partman_preserve_privs_temp (statement text); INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_parent(text, text, text, text, text[], int, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_parent'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.show_partitions(text, text) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'show_partitions'; CREATE FUNCTION replay_preserved_privs() RETURNS void LANGUAGE plpgsql AS $$ DECLARE v_row record; BEGIN FOR v_row IN SELECT statement FROM partman_preserve_privs_temp LOOP IF v_row.statement IS NOT NULL THEN EXECUTE v_row.statement; END IF; END LOOP; END $$; DROP FUNCTION @extschema@.create_parent(text, text, text, text, int, boolean); DROP FUNCTION @extschema@.show_partitions(text); /* * Apply constraints managed by partman extension */ CREATE FUNCTION apply_constraints(p_parent_table text, p_child_table text DEFAULT NULL, p_debug BOOLEAN DEFAULT FALSE) RETURNS void LANGUAGE plpgsql AS $$ DECLARE v_child_table text; v_child_tablename text; v_col text; v_constraint_cols text[]; v_constraint_col_type text; v_constraint_name text; v_datetime_string text; v_existing_constraint_name text; v_job_id bigint; v_jobmon_schema text; v_last_partition text; v_last_partition_id int; v_last_partition_timestamp timestamp; v_constraint_values record; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_part_interval text; v_partition_suffix text; v_premake int; v_sql text; v_step_id bigint; v_suffix_position int; v_type text; BEGIN SELECT type , part_interval , premake , datetime_string , last_partition , constraint_cols INTO v_type , v_part_interval , v_premake , v_datetime_string , v_last_partition , v_constraint_cols FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_constraint_cols IS NULL THEN IF p_debug THEN RAISE NOTICE 'Given parent table (%) not set up for constraint management (constraint_cols is NULL)', p_parent_table; END IF; -- Returns silently to allow this function to be simply called by maintenance processes without having to check if config options are set. RETURN; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; -- If p_child_table is null, figure out the partition that is the one right before the premake value backwards. IF p_child_table IS NULL THEN v_suffix_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; IF v_type IN ('time-static', 'time-dynamic') THEN v_last_partition_timestamp := to_timestamp(substring(v_last_partition from v_suffix_position), v_datetime_string); v_partition_suffix := to_char(v_last_partition_timestamp - (v_part_interval::interval * ((v_premake * 2)+1) ), v_datetime_string); ELSIF v_type IN ('id-static', 'id-dynamic') THEN v_last_partition_id := substring(v_last_partition from v_suffix_position)::int; v_partition_suffix := (v_last_partition_id - (v_part_interval::int * ((v_premake * 2)+1) ))::text; END IF; v_child_table := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_partition_suffix, TRUE); ELSE v_child_table := p_child_table; END IF; SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE CONSTRAINT: '||p_parent_table); v_step_id := add_step(v_job_id, 'Checking if target child table exists'); END IF; SELECT tablename INTO v_child_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_child_table; IF v_child_tablename IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', 'Target child table ('||v_child_table||') does not exist. Skipping constraint creation.'); PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; IF p_debug THEN RAISE NOTICE 'Target child table (%) does not exist. Skipping constraint creation.', v_child_table; END IF; RETURN; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; FOREACH v_col IN ARRAY v_constraint_cols LOOP SELECT c.conname INTO v_existing_constraint_name FROM pg_catalog.pg_constraint c JOIN pg_catalog.pg_attribute a ON c.conrelid = a.attrelid WHERE conrelid = v_child_table::regclass AND c.conname LIKE 'partmanconstr_%' AND c.contype = 'c' AND a.attname = v_col AND ARRAY[a.attnum] <@ c.conkey AND a.attisdropped = false; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying new constraint on column: '||v_col); END IF; IF v_existing_constraint_name IS NOT NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', 'Partman managed constraint already exists on this table ('||v_child_table||') and column ('||v_col||'). Skipping creation.'); END IF; RAISE WARNING 'Partman managed constraint already exists on this table (%) and column (%). Skipping creation.', v_child_table, v_col ; CONTINUE; END IF; -- Ensure column name gets put on end of constraint name to help avoid naming conflicts v_constraint_name := @extschema@.check_name_length('partmanconstr_'||v_child_tablename, p_suffix := '_'||v_col); EXECUTE 'SELECT min('||v_col||')::text AS min, max('||v_col||')::text AS max FROM '||v_child_table INTO v_constraint_values; IF v_constraint_values IS NOT NULL THEN v_sql := concat('ALTER TABLE ', v_child_table, ' ADD CONSTRAINT ', v_constraint_name , ' CHECK (', v_col, ' >= ', quote_literal(v_constraint_values.min), ' AND ' , v_col, ' <= ', quote_literal(v_constraint_values.max), ')' ); IF p_debug THEN RAISE NOTICE 'Constraint creation query: %', v_sql; END IF; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'New constraint created: '||v_sql); END IF; ELSE IF p_debug THEN RAISE NOTICE 'Given column (%) contains all NULLs. No constraint created', v_col; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', 'Given column ('||v_col||') contains all NULLs. No constraint created'); END IF; END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE CONSTRAINT: '||p_parent_table||')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''Partition constraint maintenance for table '||p_parent_table||' failed'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Drop constraints managed by pg_partman */ CREATE FUNCTION drop_constraints(p_parent_table text, p_child_table text, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql AS $$ DECLARE v_col text; v_constraint_cols text[]; v_existing_constraint_name text; v_exists boolean := FALSE; v_job_id bigint; v_jobmon_schema text; v_old_search_path text; v_sql text; v_step_id bigint; BEGIN SELECT constraint_cols INTO v_constraint_cols FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_constraint_cols IS NULL THEN RAISE EXCEPTION 'Given parent table (%) not set up for constraint management (constraint_cols is NULL)', p_parent_table; END IF; SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN DROP CONSTRAINT: '||p_parent_table); v_step_id := add_step(v_job_id, 'Entering constraint drop loop'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; FOREACH v_col IN ARRAY v_constraint_cols LOOP SELECT c.conname INTO v_existing_constraint_name FROM pg_catalog.pg_constraint c JOIN pg_catalog.pg_attribute a ON c.conrelid = a.attrelid WHERE conrelid = p_child_table::regclass AND c.conname LIKE 'partmanconstr_%' AND c.contype = 'c' AND a.attname = v_col AND ARRAY[a.attnum] <@ c.conkey AND a.attisdropped = false; IF v_existing_constraint_name IS NOT NULL THEN v_exists := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Dropping constraint on column: '||v_col); END IF; v_sql := 'ALTER TABLE '||p_child_table||' DROP CONSTRAINT '||v_existing_constraint_name; IF p_debug THEN RAISE NOTICE 'Constraint drop query: %', v_sql; END IF; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Drop constraint query: '||v_sql); END IF; END IF; END LOOP; IF v_jobmon_schema IS NOT NULL AND v_exists IS FALSE THEN v_step_id := add_step(v_job_id, 'No constraints found to drop on child table: '||p_child_table); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN DROP CONSTRAINT: '||p_parent_table||')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''Partition constraint maintenance for table '||p_parent_table||' failed'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Create the trigger function for the parent table of an id-based partition set */ CREATE OR REPLACE FUNCTION create_id_function(p_parent_table text, p_current_id bigint) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_count int; v_current_partition_name text; v_current_partition_id bigint; v_datetime_string text; v_final_partition_id bigint; v_function_name text; v_job_id bigint; v_jobmon_schema text; v_last_partition text; v_next_partition_id bigint; v_next_partition_name text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_part_interval bigint; v_premake int; v_prev_partition_id bigint; v_prev_partition_name text; v_step_id bigint; v_trig_func text; v_type text; BEGIN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE FUNCTION: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating partition function for table '||p_parent_table); END IF; SELECT type , part_interval::bigint , control , premake , last_partition INTO v_type , v_part_interval , v_control , v_premake , v_last_partition FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_function_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, '_part_trig_func', FALSE); IF v_type = 'id-static' THEN v_current_partition_id := p_current_id - (p_current_id % v_part_interval); v_next_partition_id := v_current_partition_id + v_part_interval; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_current_partition_id::text, TRUE); v_trig_func := 'CREATE OR REPLACE FUNCTION '||v_function_name||'() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_current_partition_id bigint; v_last_partition text := '||quote_literal(v_last_partition)||'; v_id_position int; v_next_partition_id bigint; v_next_partition_name text; BEGIN IF TG_OP = ''INSERT'' THEN IF NEW.'||v_control||' >= '||v_current_partition_id||' AND NEW.'||v_control||' < '||v_next_partition_id|| ' THEN INSERT INTO '||v_current_partition_name||' VALUES (NEW.*); '; FOR i IN 1..v_premake LOOP v_prev_partition_id := v_current_partition_id - (v_part_interval * i); v_next_partition_id := v_current_partition_id + (v_part_interval * i); v_final_partition_id := v_next_partition_id + v_part_interval; v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_prev_partition_id::text, TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_next_partition_id::text, TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles edge case of changing premake immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_prev_partition_name; IF v_count > 0 THEN -- Only handle previous partitions if they're starting above zero IF v_prev_partition_id >= 0 THEN v_trig_func := v_trig_func ||' ELSIF NEW.'||v_control||' >= '||v_prev_partition_id||' AND NEW.'||v_control||' < '||v_prev_partition_id + v_part_interval|| ' THEN INSERT INTO '||v_prev_partition_name||' VALUES (NEW.*); '; END IF; END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_next_partition_name; IF v_count > 0 THEN v_trig_func := v_trig_func ||' ELSIF NEW.'||v_control||' >= '||v_next_partition_id||' AND NEW.'||v_control||' < '||v_final_partition_id|| ' THEN INSERT INTO '||v_next_partition_name||' VALUES (NEW.*); '; END IF; END LOOP; v_trig_func := v_trig_func ||' ELSE RETURN NEW; END IF; v_current_partition_id := NEW.'||v_control||' - (NEW.'||v_control||' % '||v_part_interval||'); IF (NEW.'||v_control||' % '||v_part_interval||') > ('||v_part_interval||' / 2) THEN v_id_position := (length(v_last_partition) - position(''p_'' in reverse(v_last_partition))) + 2; v_next_partition_id := (substring(v_last_partition from v_id_position)::bigint) + '||v_part_interval||'; WHILE ((v_next_partition_id - v_current_partition_id) / '||v_part_interval||') <= '||v_premake||' LOOP v_next_partition_name := @extschema@.create_id_partition('||quote_literal(p_parent_table)||', '||quote_literal(v_control)||',' ||v_part_interval||', ARRAY[v_next_partition_id]); UPDATE @extschema@.part_config SET last_partition = v_next_partition_name WHERE parent_table = '||quote_literal(p_parent_table)||'; PERFORM @extschema@.create_id_function('||quote_literal(p_parent_table)||', NEW.'||v_control||'); PERFORM @extschema@.apply_constraints('||quote_literal(p_parent_table)||'); v_next_partition_id := v_next_partition_id + '||v_part_interval||'; END LOOP; END IF; END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for current id interval: '||v_current_partition_id||' to '||v_final_partition_id-1); END IF; ELSIF v_type = 'id-dynamic' THEN -- The return inside the partition creation check is there to keep really high ID values from creating new partitions. v_trig_func := 'CREATE OR REPLACE FUNCTION '||v_function_name||'() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_current_partition_id bigint; v_current_partition_name text; v_id_position int; v_last_partition text := '||quote_literal(v_last_partition)||'; v_last_partition_id bigint; v_next_partition_id bigint; v_next_partition_name text; BEGIN IF TG_OP = ''INSERT'' THEN v_current_partition_id := NEW.'||v_control||' - (NEW.'||v_control||' % '||v_part_interval||'); v_current_partition_name := @extschema@.check_name_length('''||v_parent_tablename||''', '''||v_parent_schema||''', v_current_partition_id::text, TRUE); IF (NEW.'||v_control||' % '||v_part_interval||') > ('||v_part_interval||' / 2) THEN v_id_position := (length(v_last_partition) - position(''p_'' in reverse(v_last_partition))) + 2; v_last_partition_id = substring(v_last_partition from v_id_position)::bigint; v_next_partition_id := v_last_partition_id + '||v_part_interval||'; IF NEW.'||v_control||' >= v_next_partition_id THEN RETURN NEW; END IF; WHILE ((v_next_partition_id - v_current_partition_id) / '||v_part_interval||') <= '||v_premake||' LOOP v_next_partition_name := @extschema@.create_id_partition('||quote_literal(p_parent_table)||', '||quote_literal(v_control)||',' ||quote_literal(v_part_interval)||', ARRAY[v_next_partition_id]); IF v_next_partition_name IS NOT NULL THEN UPDATE @extschema@.part_config SET last_partition = v_next_partition_name WHERE parent_table = '||quote_literal(p_parent_table)||'; PERFORM @extschema@.create_id_function('||quote_literal(p_parent_table)||', NEW.'||v_control||'); PERFORM @extschema@.apply_constraints('||quote_literal(p_parent_table)||'); END IF; v_next_partition_id := v_next_partition_id + '||v_part_interval||'; END LOOP; END IF; SELECT count(*) INTO v_count FROM pg_tables WHERE schemaname ||''.''|| tablename = v_current_partition_name; IF v_count > 0 THEN EXECUTE ''INSERT INTO ''||v_current_partition_name||'' VALUES($1.*)'' USING NEW; ELSE RETURN NEW; END IF; END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for dynamic id table: '||p_parent_table); END IF; ELSE RAISE EXCEPTION 'ERROR: Invalid id partitioning type given: %', v_type; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE FUNCTION: '||p_parent_table||')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''Partition function maintenance for table '||p_parent_table||' failed'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to manage pre-creation of the next partitions in a time-based partition set. * Also manages dropping old partitions if the retention option is set. */ CREATE OR REPLACE FUNCTION run_maintenance() RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_create_count int := 0; v_current_partition_timestamp timestamp; v_datetime_string text; v_drop_count int := 0; v_job_id bigint; v_jobmon_schema text; v_last_partition_timestamp timestamp; v_old_search_path text; v_premade_count int; v_quarter text; v_step_id bigint; v_row record; v_time_position int; v_year text; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman maintenance already running.'; RETURN; END IF; SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN RUN MAINTENANCE'); v_step_id := add_step(v_job_id, 'Running maintenance loop'); END IF; FOR v_row IN SELECT parent_table , type , part_interval::interval , control , premake , datetime_string , last_partition , undo_in_progress FROM @extschema@.part_config WHERE type = 'time-static' OR type = 'time-dynamic' LOOP CONTINUE WHEN v_row.undo_in_progress; CASE WHEN v_row.part_interval = '15 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_row.part_interval = '30 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_row.part_interval = '1 hour' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '1 day' THEN v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '1 week' THEN v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '1 month' THEN v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '3 months' THEN v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '1 year' THEN v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; -- Get position of last occurance of '_p' in child partition name v_time_position := (length(v_row.last_partition) - position('p_' in reverse(v_row.last_partition))) + 2; IF v_row.part_interval != '3 months' THEN v_last_partition_timestamp := to_timestamp(substring(v_row.last_partition from v_time_position), v_row.datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(v_row.last_partition from v_time_position), 'q', 1); v_quarter := split_part(substring(v_row.last_partition from v_time_position), 'q', 2); CASE WHEN v_quarter = '1' THEN v_last_partition_timestamp := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_last_partition_timestamp := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_last_partition_timestamp := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_last_partition_timestamp := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; -- Check and see how many premade partitions there are. v_premade_count = round(EXTRACT('epoch' FROM age(v_last_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.part_interval::interval)); -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed. WHILE v_premade_count < v_row.premake LOOP EXECUTE 'SELECT @extschema@.create_next_time_partition('||quote_literal(v_row.parent_table)||')'; v_create_count := v_create_count + 1; IF v_row.type = 'time-static' THEN EXECUTE 'SELECT @extschema@.create_time_function('||quote_literal(v_row.parent_table)||')'; END IF; -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(v_row.parent_table); v_last_partition_timestamp := v_last_partition_timestamp + v_row.part_interval; v_premade_count = round(EXTRACT('epoch' FROM age(v_last_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.part_interval::interval)); END LOOP; END LOOP; -- end of creation loop -- Manage dropping old partitions if retention option is set FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND (type = 'time-static' OR type = 'time-dynamic') LOOP v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); END LOOP; FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND (type = 'id-static' OR type = 'id-dynamic') LOOP v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Partition maintenance finished. '||v_create_count||' partitions made. '||v_drop_count||' partitions dropped.'); PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN RUN MAINTENANCE'')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to turn a table into the parent of a partition set */ CREATE FUNCTION 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_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_current_id bigint; v_datetime_string text; v_id_interval bigint; v_job_id bigint; v_jobmon_schema text; v_last_partition_name text; v_old_search_path text; v_partition_time timestamp[]; v_partition_id bigint[]; v_max bigint; v_notnull boolean; v_starting_partition_id bigint; v_step_id bigint; v_tablename text; v_time_interval interval; v_valid_types text[] := '{"time-static", "time-dynamic", "id-static", "id-dynamic"}'; BEGIN IF position('.' in p_parent_table) = 0 THEN RAISE EXCEPTION 'Parent table must be schema qualified'; END IF; SELECT tablename INTO v_tablename FROM pg_tables WHERE schemaname || '.' || tablename = p_parent_table; IF v_tablename IS NULL THEN RAISE EXCEPTION 'Please create given parent table first: %', p_parent_table; END IF; SELECT attnotnull INTO v_notnull FROM pg_attribute WHERE attrelid = p_parent_table::regclass AND attname = p_control; IF v_notnull = false OR v_notnull IS NULL THEN RAISE EXCEPTION 'Control column (%) for parent table (%) must be NOT NULL', p_control, p_parent_table; END IF; IF NOT (string_to_array(p_type, '') <@ v_valid_types) THEN RAISE EXCEPTION '% is not a valid partitioning type (%)', p_type, array_to_string(v_valid_types, ', '); END IF; EXECUTE 'LOCK TABLE '||p_parent_table||' IN ACCESS EXCLUSIVE MODE'; SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN SETUP PARENT: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating initial partitions on new parent table: '||p_parent_table); END IF; CASE WHEN p_interval = 'yearly' THEN v_time_interval = '1 year'; v_datetime_string := 'YYYY'; WHEN p_interval = 'quarterly' THEN v_time_interval = '3 months'; v_datetime_string = 'YYYY"q"Q'; WHEN p_interval = 'monthly' THEN v_time_interval = '1 month'; v_datetime_string := 'YYYY_MM'; WHEN p_interval = 'weekly' THEN v_time_interval = '1 week'; v_datetime_string := 'IYYY"w"IW'; WHEN p_interval = 'daily' THEN v_time_interval = '1 day'; v_datetime_string := 'YYYY_MM_DD'; WHEN p_interval = 'hourly' THEN v_time_interval = '1 hour'; v_datetime_string := 'YYYY_MM_DD_HH24MI'; WHEN p_interval = 'half-hour' THEN v_time_interval = '30 mins'; v_datetime_string := 'YYYY_MM_DD_HH24MI'; WHEN p_interval = 'quarter-hour' THEN v_time_interval = '15 mins'; v_datetime_string := 'YYYY_MM_DD_HH24MI'; ELSE IF p_type = 'id-static' OR p_type = 'id-dynamic' THEN v_id_interval := p_interval::bigint; IF v_id_interval <= 0 THEN RAISE EXCEPTION 'Interval for serial partitioning must be greater than zero'; END IF; ELSE RAISE EXCEPTION 'Invalid interval for time based partitioning: %', p_interval; END IF; END CASE; IF p_type = 'time-static' OR p_type = 'time-dynamic' THEN FOR i IN 0..p_premake LOOP IF i > 0 THEN -- also create previous partitions equal to premake, but avoid duplicating current v_partition_time := array_append(v_partition_time, quote_literal(CURRENT_TIMESTAMP - (v_time_interval*i))::timestamp); END IF; v_partition_time := array_append(v_partition_time, quote_literal(CURRENT_TIMESTAMP + (v_time_interval*i))::timestamp); END LOOP; INSERT INTO @extschema@.part_config (parent_table, type, part_interval, control, premake, datetime_string) VALUES (p_parent_table, p_type, v_time_interval, p_control, p_premake, v_datetime_string); EXECUTE 'SELECT @extschema@.create_time_partition('||quote_literal(p_parent_table)||','||quote_literal(p_control)||',' ||quote_literal(v_time_interval)||','||quote_literal(v_datetime_string)||','||quote_literal(v_partition_time)||')' INTO v_last_partition_name; -- Doing separate update because create function needs parent table in config table for apply_grants() UPDATE @extschema@.part_config SET last_partition = v_last_partition_name WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Time partitions premade: '||p_premake); END IF; END IF; IF p_type = 'id-static' OR p_type = 'id-dynamic' THEN -- If there is already data, start partitioning with the highest current value EXECUTE 'SELECT COALESCE(max('||p_control||')::bigint, 0) FROM '||p_parent_table||' LIMIT 1' INTO v_max; 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 IF (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_append(v_partition_id, (v_starting_partition_id - v_id_interval*i)); END IF; v_partition_id = array_append(v_partition_id, (v_id_interval*i) + v_starting_partition_id); END LOOP; INSERT INTO @extschema@.part_config (parent_table, type, part_interval, control, premake, constraint_cols) VALUES (p_parent_table, p_type, v_id_interval, p_control, p_premake, p_constraint_cols); EXECUTE 'SELECT @extschema@.create_id_partition('||quote_literal(p_parent_table)||','||quote_literal(p_control)||',' ||v_id_interval||','||quote_literal(v_partition_id)||')' INTO v_last_partition_name; -- Doing separate update because create function needs parent table in config table for apply_grants() UPDATE @extschema@.part_config SET last_partition = v_last_partition_name WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'ID partitions premade: '||p_premake); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating partition function'); END IF; IF p_type = 'time-static' OR p_type = 'time-dynamic' THEN EXECUTE 'SELECT @extschema@.create_time_function('||quote_literal(p_parent_table)||')'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Time function created'); END IF; ELSIF p_type = 'id-static' OR p_type = 'id-dynamic' THEN v_current_id := COALESCE(v_max, 0); EXECUTE 'SELECT @extschema@.create_id_function('||quote_literal(p_parent_table)||','||v_current_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; EXECUTE 'SELECT @extschema@.create_trigger('||quote_literal(p_parent_table)||')'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE PARENT: '||p_parent_table||')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''Partition creation for table '||p_parent_table||' failed'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to list all child partitions in a set. * Will list all child tables in any inheritance set, * not just those managed by pg_partman. */ CREATE FUNCTION show_partitions (p_parent_table text, p_order text DEFAULT 'ASC') RETURNS SETOF text LANGUAGE plpgsql STABLE SECURITY DEFINER AS $$ DECLARE v_datetime_string text; v_part_interval text; v_type text; BEGIN IF p_order NOT IN ('ASC', 'DESC') THEN RAISE EXCEPTION 'p_order paramter must be one of the following values: ASC, DESC'; END IF; SELECT type , part_interval , datetime_string INTO v_type , v_part_interval , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_type IN ('time-static', 'time-dynamic') THEN RETURN QUERY EXECUTE ' SELECT n.nspname::text ||''.''|| c.relname::text AS partition_name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = '||quote_literal(p_parent_table)||'::regclass ORDER BY to_timestamp(substring(c.relname from ((length(c.relname) - position(''p_'' in reverse(c.relname))) + 2) ), '||quote_literal(v_datetime_string)||') ' || p_order; ELSIF v_type IN ('id-static', 'id-dynamic') THEN RETURN QUERY EXECUTE ' SELECT n.nspname::text ||''.''|| c.relname::text AS partition_name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = '||quote_literal(p_parent_table)||'::regclass ORDER BY substring(c.relname from ((length(c.relname) - position(''p_'' in reverse(c.relname))) + 2) )::int ' || p_order; END IF; END $$; SELECT @extschema@.replay_preserved_privs(); DROP FUNCTION @extschema@.replay_preserved_privs(); DROP TABLE partman_preserve_privs_temp; pg_partman-4.6.0/updates/pg_partman--1.5.0--1.5.1.sql000066400000000000000000000213711412761706100214310ustar00rootroot00000000000000-- Fix create_parent() to actually insert the contraint_cols value passed into the function to the config table when using time based partitioning. Thanks to Jeff Amiel for reporting the issue. /* * Function to turn a table into the parent of a partition set */ CREATE OR REPLACE FUNCTION 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_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_current_id bigint; v_datetime_string text; v_id_interval bigint; v_job_id bigint; v_jobmon_schema text; v_last_partition_name text; v_old_search_path text; v_partition_time timestamp[]; v_partition_id bigint[]; v_max bigint; v_notnull boolean; v_starting_partition_id bigint; v_step_id bigint; v_tablename text; v_time_interval interval; v_valid_types text[] := '{"time-static", "time-dynamic", "id-static", "id-dynamic"}'; BEGIN IF position('.' in p_parent_table) = 0 THEN RAISE EXCEPTION 'Parent table must be schema qualified'; END IF; SELECT tablename INTO v_tablename FROM pg_tables WHERE schemaname || '.' || tablename = p_parent_table; IF v_tablename IS NULL THEN RAISE EXCEPTION 'Please create given parent table first: %', p_parent_table; END IF; SELECT attnotnull INTO v_notnull FROM pg_attribute WHERE attrelid = p_parent_table::regclass AND attname = p_control; IF v_notnull = false OR v_notnull IS NULL THEN RAISE EXCEPTION 'Control column (%) for parent table (%) must be NOT NULL', p_control, p_parent_table; END IF; IF NOT (string_to_array(p_type, '') <@ v_valid_types) THEN RAISE EXCEPTION '% is not a valid partitioning type (%)', p_type, array_to_string(v_valid_types, ', '); END IF; EXECUTE 'LOCK TABLE '||p_parent_table||' IN ACCESS EXCLUSIVE MODE'; SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN SETUP PARENT: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating initial partitions on new parent table: '||p_parent_table); END IF; CASE WHEN p_interval = 'yearly' THEN v_time_interval = '1 year'; v_datetime_string := 'YYYY'; WHEN p_interval = 'quarterly' THEN v_time_interval = '3 months'; v_datetime_string = 'YYYY"q"Q'; WHEN p_interval = 'monthly' THEN v_time_interval = '1 month'; v_datetime_string := 'YYYY_MM'; WHEN p_interval = 'weekly' THEN v_time_interval = '1 week'; v_datetime_string := 'IYYY"w"IW'; WHEN p_interval = 'daily' THEN v_time_interval = '1 day'; v_datetime_string := 'YYYY_MM_DD'; WHEN p_interval = 'hourly' THEN v_time_interval = '1 hour'; v_datetime_string := 'YYYY_MM_DD_HH24MI'; WHEN p_interval = 'half-hour' THEN v_time_interval = '30 mins'; v_datetime_string := 'YYYY_MM_DD_HH24MI'; WHEN p_interval = 'quarter-hour' THEN v_time_interval = '15 mins'; v_datetime_string := 'YYYY_MM_DD_HH24MI'; ELSE IF p_type = 'id-static' OR p_type = 'id-dynamic' THEN v_id_interval := p_interval::bigint; IF v_id_interval <= 0 THEN RAISE EXCEPTION 'Interval for serial partitioning must be greater than zero'; END IF; ELSE RAISE EXCEPTION 'Invalid interval for time based partitioning: %', p_interval; END IF; END CASE; IF p_type = 'time-static' OR p_type = 'time-dynamic' THEN FOR i IN 0..p_premake LOOP IF i > 0 THEN -- also create previous partitions equal to premake, but avoid duplicating current v_partition_time := array_append(v_partition_time, quote_literal(CURRENT_TIMESTAMP - (v_time_interval*i))::timestamp); END IF; v_partition_time := array_append(v_partition_time, quote_literal(CURRENT_TIMESTAMP + (v_time_interval*i))::timestamp); END LOOP; INSERT INTO @extschema@.part_config (parent_table, type, part_interval, control, premake, constraint_cols, datetime_string) VALUES (p_parent_table, p_type, v_time_interval, p_control, p_premake, p_constraint_cols, v_datetime_string); EXECUTE 'SELECT @extschema@.create_time_partition('||quote_literal(p_parent_table)||','||quote_literal(p_control)||',' ||quote_literal(v_time_interval)||','||quote_literal(v_datetime_string)||','||quote_literal(v_partition_time)||')' INTO v_last_partition_name; -- Doing separate update because create function needs parent table in config table for apply_grants() UPDATE @extschema@.part_config SET last_partition = v_last_partition_name WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Time partitions premade: '||p_premake); END IF; END IF; IF p_type = 'id-static' OR p_type = 'id-dynamic' THEN -- If there is already data, start partitioning with the highest current value EXECUTE 'SELECT COALESCE(max('||p_control||')::bigint, 0) FROM '||p_parent_table||' LIMIT 1' INTO v_max; 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 IF (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_append(v_partition_id, (v_starting_partition_id - v_id_interval*i)); END IF; v_partition_id = array_append(v_partition_id, (v_id_interval*i) + v_starting_partition_id); END LOOP; INSERT INTO @extschema@.part_config (parent_table, type, part_interval, control, premake, constraint_cols) VALUES (p_parent_table, p_type, v_id_interval, p_control, p_premake, p_constraint_cols); EXECUTE 'SELECT @extschema@.create_id_partition('||quote_literal(p_parent_table)||','||quote_literal(p_control)||',' ||v_id_interval||','||quote_literal(v_partition_id)||')' INTO v_last_partition_name; -- Doing separate update because create function needs parent table in config table for apply_grants() UPDATE @extschema@.part_config SET last_partition = v_last_partition_name WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'ID partitions premade: '||p_premake); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating partition function'); END IF; IF p_type = 'time-static' OR p_type = 'time-dynamic' THEN EXECUTE 'SELECT @extschema@.create_time_function('||quote_literal(p_parent_table)||')'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Time function created'); END IF; ELSIF p_type = 'id-static' OR p_type = 'id-dynamic' THEN v_current_id := COALESCE(v_max, 0); EXECUTE 'SELECT @extschema@.create_id_function('||quote_literal(p_parent_table)||','||v_current_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; EXECUTE 'SELECT @extschema@.create_trigger('||quote_literal(p_parent_table)||')'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE PARENT: '||p_parent_table||')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''Partition creation for table '||p_parent_table||' failed'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; pg_partman-4.6.0/updates/pg_partman--1.5.1--1.6.0.sql000066400000000000000000004016711412761706100214370ustar00rootroot00000000000000-- A new partitioning type has been added to allow setting almost any desired time interval (time-custom). The smallest interval supported is 1 second and the upper limit is bounded by the minimum and maximum timestamp values that PostgreSQL supports (http://www.postgresql.org/docs/current/static/datatype-datetime.html). This feature uses the range data type for internal configuration management, so it is only supported in PostgreSQL 9.2+. -- The custom time interval is less efficient than both time-static and time-dynamic since it must use a lookup table. If your needed partitioning interval can fit in one of the pre-made intervals given in the documentation, it is highly recommended to use one of those for better performance. time-static is still the best method when performance of inserts is important. See the documentation for more details on this new partitioning type. -- New parameter to create_parent() that sets what the first partition in the set will be (p_start_partition). -- Must be a valid timestamp (for time-based) or positive integer (for id-based) value. Be aware, though, the actual paramater data type is text. -- For time-based partitioning, all partitions starting with the given timestamp up to CURRENT_TIMESTAMP (plus premake) will be created. -- For id-based partitioning, only the partition starting at the given value (plus premake) will be made. -- pg_jobmon is now truly optional. Additonal configuration option for each individual partition set to turn it off and on. run_maintenance() now has an optional parameter to turn it off when being run. If you tried to partition pg_jobmon tables before, it would cause a permanent lockwait. Turn pg_jobmon off for those tables to avoid this. -- Fixed partition_data_time() & partition_data_id() functions to recreate the parent trigger function when static partitioning is used. Without this, partitioning more recent data that may have gotten into the parent table could possibly leave the function without conditions for the new partitions. run_maintenance() would eventually fix this for time partitioning, but id partitioning could be left in a broken state forever. (Github issue #16) -- Fixed bug in partition_data_time() & partition_data_id() to reset the lock wait counter properly between loops. Bug reported & fixed by bougyman from #postgresql on Freenode. -- pg_partman only supports id intervals greater than 1. May see if I can get an interval of 1 working later, but changed create_parent() to check for this and not allow it since it won't work properly at this time. New partitions were not automatically created if interval was set to 1. (Github issue #15) -- Clarify in docs that the id interval value passed to create_parent() must actually be in text type format. -- Changed drop & undo partition functions to use transaction based advistory locks. -- Removed need for internally used function create_next_time_partition() and therefore dropped the function. -- Simplified the create_time_partition() & create_id_partition() parameter lists. ALTER TABLE @extschema@.part_config ADD jobmon boolean DEFAULT true; CREATE TEMP TABLE partman_preserve_privs_temp (statement text); INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_parent(text, text, text, text, text[], int, text, boolean, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_parent'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_time_partition (text, timestamp[]) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_time_partition'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_id_partition (text, bigint[]) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_id_partition'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.undo_partition(text, int, boolean, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'undo_partition'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_id_function(text) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_id_function'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.run_maintenance(boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'run_maintenance'; DROP FUNCTION @extschema@.create_parent(text, text, text, text, text[], int , boolean); DROP FUNCTION @extschema@.create_time_partition (text, text, interval, text, timestamp[]); DROP FUNCTION @extschema@.create_id_partition (text, text, bigint, bigint[]); DROP FUNCTION @extschema@.undo_partition(text, int, boolean); DROP FUNCTION @extschema@.create_id_function(text, bigint); DROP FUNCTION @extschema@.run_maintenance(); DROP FUNCTION @extschema@.create_next_time_partition(text); /* * Check PostgreSQL version number. Parameter must be full 3 point version. * Returns true if current version is greater than or equal to the parameter given. */ CREATE FUNCTION check_version(p_check_version text) RETURNS boolean LANGUAGE plpgsql STABLE AS $$ DECLARE v_check_version text[]; v_current_version text[] := string_to_array(current_setting('server_version'), '.'); BEGIN v_check_version := string_to_array(p_check_version, '.'); IF v_current_version[1]::int > v_check_version[1]::int THEN RETURN true; END IF; IF v_current_version[1]::int = v_check_version[1]::int THEN IF v_current_version[2]::int > v_check_version[2]::int THEN RETURN true; END IF; IF v_current_version[2]::int = v_check_version[2]::int THEN IF v_current_version[3]::int >= v_check_version[3]::int THEN RETURN true; END IF; -- 0.0.x END IF; -- 0.x.0 END IF; -- x.0.0 RETURN false; END $$; /********* 9.2+ stuff ********/ DO $$ BEGIN IF @extschema@.check_version('9.2.0') THEN CREATE TABLE custom_time_partitions ( parent_table text NOT NULL , child_table text NOT NULL , partition_range tstzrange NOT NULL , PRIMARY KEY (parent_table, child_table)); CREATE INDEX custom_time_partitions_partition_range_idx ON custom_time_partitions USING gist (partition_range); END IF; END $$; /********* end 9.2+ stuff ********/ /* * Check function for config table partition types */ CREATE OR REPLACE FUNCTION check_partition_type (p_type text) RETURNS boolean LANGUAGE plpgsql IMMUTABLE SECURITY DEFINER AS $$ DECLARE v_result boolean; BEGIN SELECT p_type IN ('time-static', 'time-dynamic', 'time-custom', 'id-static', 'id-dynamic') INTO v_result; RETURN v_result; END $$; /* * Function to turn a table into the parent of a partition set */ CREATE FUNCTION 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_start_partition text DEFAULT NULL , p_jobmon boolean DEFAULT true , p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_base_timestamp timestamp; v_count int := 1; v_datetime_string text; v_id_interval bigint; v_job_id bigint; v_jobmon_schema text; v_last_partition_name text; v_old_search_path text; v_partition_time timestamp; v_partition_time_array timestamp[]; v_partition_id bigint[]; v_max bigint; v_notnull boolean; v_start_time timestamp; v_starting_partition_id bigint; v_step_id bigint; v_step_overflow_id bigint; v_tablename text; v_time_interval interval; BEGIN IF position('.' in p_parent_table) = 0 THEN RAISE EXCEPTION 'Parent table must be schema qualified'; END IF; SELECT tablename INTO v_tablename FROM pg_tables WHERE schemaname || '.' || tablename = p_parent_table; IF v_tablename IS NULL THEN RAISE EXCEPTION 'Please create given parent table first: %', p_parent_table; END IF; SELECT attnotnull INTO v_notnull FROM pg_attribute WHERE attrelid = p_parent_table::regclass AND attname = p_control; IF v_notnull = false OR v_notnull IS NULL THEN RAISE EXCEPTION 'Control column (%) for parent table (%) must be NOT NULL', p_control, p_parent_table; END IF; IF NOT @extschema@.check_partition_type(p_type) THEN RAISE EXCEPTION '% is not a valid partitioning type', p_type; END IF; IF p_type = 'time-custom' AND @extschema@.check_version('9.2.0') IS FALSE THEN RAISE EXCEPTION 'The "time-custom" type requires a minimum PostgreSQL version of 9.2.0'; END IF; EXECUTE 'LOCK TABLE '||p_parent_table||' IN ACCESS EXCLUSIVE MODE'; IF p_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN SETUP PARENT: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating initial partitions on new parent table: '||p_parent_table); END IF; IF p_type = 'time-static' OR p_type = 'time-dynamic' OR p_type = 'time-custom' 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 <> 'time-custom' THEN RAISE EXCEPTION 'Must use a predefined time interval if not using type "time-custom". See documentation.'; 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::timestamp, 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))::timestamp; 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, type, part_interval, control, premake, constraint_cols, datetime_string, jobmon) VALUES (p_parent_table, p_type, v_time_interval, p_control, p_premake, p_constraint_cols, v_datetime_string, p_jobmon); v_last_partition_name := @extschema@.create_time_partition(p_parent_table, v_partition_time_array); -- Doing separate update because create function requires in config table last_partition to be set UPDATE @extschema@.part_config SET last_partition = v_last_partition_name WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Time partitions premade: '||p_premake); END IF; END IF; IF p_type = 'id-static' OR p_type = 'id-dynamic' THEN v_id_interval := p_interval::bigint; IF v_id_interval <= 1 THEN RAISE EXCEPTION 'Interval for serial partitioning must be greater than 1'; END IF; -- 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 EXECUTE 'SELECT COALESCE('||quote_nullable(p_start_partition::bigint)||', max('||p_control||')::bigint, 0) FROM '||p_parent_table||' LIMIT 1' INTO v_max; 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_append(v_partition_id, (v_starting_partition_id - v_id_interval*i)); END IF; v_partition_id = array_append(v_partition_id, (v_id_interval*i) + v_starting_partition_id); END LOOP; INSERT INTO @extschema@.part_config (parent_table, type, part_interval, control, premake, constraint_cols, jobmon) VALUES (p_parent_table, p_type, v_id_interval, p_control, p_premake, p_constraint_cols, p_jobmon); v_last_partition_name := @extschema@.create_id_partition(p_parent_table, v_partition_id); -- Doing separate update because create function needs parent table in config table for apply_grants() UPDATE @extschema@.part_config SET last_partition = v_last_partition_name WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'ID partitions premade: '||p_premake); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating partition function'); END IF; IF p_type = 'time-static' OR p_type = 'time-dynamic' OR p_type = 'time-custom' THEN PERFORM @extschema@.create_time_function(p_parent_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Time function created'); END IF; ELSIF p_type = 'id-static' OR p_type = 'id-dynamic' THEN PERFORM @extschema@.create_id_function(p_parent_table); 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); 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; EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE PARENT: '||p_parent_table||')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''Partition creation for table '||p_parent_table||' failed'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to create a child table in a time-based partition set */ CREATE FUNCTION create_time_partition (p_parent_table text, p_partition_times timestamp[]) RETURNS text LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_control text; v_grantees text[]; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_partition_name text; v_partition_suffix text; v_parent_tablespace text; v_part_interval interval; v_partition_timestamp_end timestamp; v_partition_timestamp_start timestamp; v_quarter text; v_revoke text[]; v_step_id bigint; v_step_overflow_id bigint; v_tablename text; v_trunc_value text; v_time timestamp; v_type text; v_year text; BEGIN SELECT type , control , part_interval , jobmon INTO v_type , v_control , v_part_interval , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic' OR type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_suffix := to_char(v_time, 'YYYY'); IF v_part_interval < '1 year' AND v_part_interval <> '1 week' THEN v_partition_suffix := v_partition_suffix ||'_'|| to_char(v_time, 'MM'); IF v_part_interval < '1 month' AND v_part_interval <> '1 week' THEN v_partition_suffix := v_partition_suffix ||'_'|| to_char(v_time, 'DD'); IF v_part_interval < '1 day' THEN v_partition_suffix := v_partition_suffix || '_' || to_char(v_time, 'HH24MI'); IF v_part_interval < '1 minute' THEN v_partition_suffix := v_partition_suffix || to_char(v_time, 'SS'); END IF; -- end < minute IF END IF; -- end < day IF END IF; -- end < month IF END IF; -- end < year IF v_partition_timestamp_start := v_time; BEGIN v_partition_timestamp_end := v_time + v_part_interval; 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_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_time||' skipped'); CONTINUE; END; IF v_part_interval = '1 week' THEN v_partition_suffix := to_char(v_time, 'IYYY') || 'w' || to_char(v_time, 'IW'); END IF; -- "Q" is ignored in to_timestamp, so handle special case IF v_part_interval = '3 months' AND (v_type = 'time-static' OR v_type = 'time-dynamic') THEN v_year := to_char(v_time, 'YYYY'); v_quarter := to_char(v_time, 'Q'); v_partition_suffix := v_year || 'q' || v_quarter; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_partition_suffix, TRUE); SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_tablename IS NOT NULL THEN CONTINUE; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_partition_timestamp_start||' to '||(v_partition_timestamp_end-'1sec'::interval)); END IF; EXECUTE 'CREATE TABLE '||v_partition_name||' (LIKE '||p_parent_table||' INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)'; SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_parent_tablespace IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_partition_name||' SET TABLESPACE '||v_parent_tablespace; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' ADD CONSTRAINT '||v_tablename||'_partition_check CHECK ('||v_control||'>='||quote_literal(v_partition_timestamp_start)||' AND '||v_control||'<'||quote_literal(v_partition_timestamp_end)||')'; EXECUTE 'ALTER TABLE '||v_partition_name||' INHERIT '||p_parent_table; -- If custom time, set extra config options. IF v_type = 'time-custom' THEN INSERT INTO @extschema@.custom_time_partitions (parent_table, child_table, partition_range) VALUES ( p_parent_table, v_partition_name, tstzrange(v_partition_timestamp_start, v_partition_timestamp_end, '[)') ); END IF; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP EXECUTE 'GRANT '||array_to_string(v_parent_grant.types, ',')||' ON '||v_partition_name||' TO '||v_parent_grant.grantee; SELECT array_agg(r) INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE '||array_to_string(v_revoke, ',')||' ON '||v_partition_name||' FROM '||v_parent_grant.grantee||' CASCADE'; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN SELECT array_agg(r) INTO v_revoke FROM ( SELECT DISTINCT grantee::text AS r FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_partition_name EXCEPT SELECT unnest(v_grantees)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE ALL ON '||v_partition_name||' FROM '||array_to_string(v_revoke, ','); END IF; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' OWNER TO '||v_parent_owner; 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; END LOOP; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_partition_name; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE TABLE: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Create the trigger function for the parent table of a time-based partition set */ CREATE OR REPLACE FUNCTION create_time_function(p_parent_table text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_count int; v_current_partition_name text; v_current_partition_timestamp timestamptz; v_datetime_string text; v_final_partition_timestamp timestamptz; v_function_name text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_new_length int; v_next_partition_name text; v_next_partition_timestamp timestamptz; v_parent_schema text; v_parent_tablename text; v_part_interval interval; v_premake int; v_prev_partition_name text; v_prev_partition_timestamp timestamptz; v_step_id bigint; v_trig_func text; v_type text; BEGIN SELECT type , part_interval::interval , control , premake , datetime_string , jobmon INTO v_type , v_part_interval , v_control , v_premake , v_datetime_string , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic' OR type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE FUNCTION: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating partition function for table '||p_parent_table); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_function_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, '_part_trig_func', FALSE); IF v_type = 'time-static' THEN CASE WHEN v_part_interval = '15 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_part_interval = '30 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_part_interval = '1 hour' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_part_interval = '1 day' THEN v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_part_interval = '1 week' THEN v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_part_interval = '1 month' THEN v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); -- Type time-static plus this interval is the special quarterly interval WHEN v_part_interval = '3 months' THEN v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_part_interval = '1 year' THEN v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, to_char(v_current_partition_timestamp, v_datetime_string), TRUE); v_next_partition_timestamp := v_current_partition_timestamp + v_part_interval::interval; v_trig_func := 'CREATE OR REPLACE FUNCTION '||v_function_name||'() RETURNS trigger LANGUAGE plpgsql AS $t$ BEGIN IF TG_OP = ''INSERT'' THEN IF NEW.'||v_control||' >= '||quote_literal(v_current_partition_timestamp)||' AND NEW.'||v_control||' < '||quote_literal(v_next_partition_timestamp)|| ' THEN INSERT INTO '||v_current_partition_name||' VALUES (NEW.*); '; FOR i IN 1..v_premake LOOP v_prev_partition_timestamp := v_current_partition_timestamp - (v_part_interval::interval * i); v_next_partition_timestamp := v_current_partition_timestamp + (v_part_interval::interval * i); v_final_partition_timestamp := v_next_partition_timestamp + (v_part_interval::interval); v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, to_char(v_prev_partition_timestamp, v_datetime_string), TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, to_char(v_next_partition_timestamp, v_datetime_string), TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles edge case of changing premake immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_prev_partition_name; IF v_count > 0 THEN v_trig_func := v_trig_func ||' ELSIF NEW.'||v_control||' >= '||quote_literal(v_prev_partition_timestamp)||' AND NEW.'||v_control||' < '|| quote_literal(v_prev_partition_timestamp + v_part_interval::interval)|| ' THEN INSERT INTO '||v_prev_partition_name||' VALUES (NEW.*);'; END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_next_partition_name; IF v_count > 0 THEN v_trig_func := v_trig_func ||' ELSIF NEW.'||v_control||' >= '||quote_literal(v_next_partition_timestamp)||' AND NEW.'||v_control||' < '|| quote_literal(v_final_partition_timestamp)|| ' THEN INSERT INTO '||v_next_partition_name||' VALUES (NEW.*);'; END IF; END LOOP; v_trig_func := v_trig_func ||' ELSE RETURN NEW; END IF; END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for current time interval: '|| v_current_partition_timestamp||' to '||(v_final_partition_timestamp-'1sec'::interval)); END IF; ELSIF v_type = 'time-dynamic' THEN v_trig_func := 'CREATE OR REPLACE FUNCTION '||v_function_name||'() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_partition_name text; v_partition_timestamp timestamptz; BEGIN IF TG_OP = ''INSERT'' THEN '; CASE WHEN v_part_interval = '15 mins' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''hour'', NEW.'||v_control||') + ''15min''::interval * floor(date_part(''minute'', NEW.'||v_control||') / 15.0);'; WHEN v_part_interval = '30 mins' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''hour'', NEW.'||v_control||') + ''30min''::interval * floor(date_part(''minute'', NEW.'||v_control||') / 30.0);'; WHEN v_part_interval = '1 hour' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''hour'', NEW.'||v_control||');'; WHEN v_part_interval = '1 day' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''day'', NEW.'||v_control||');'; WHEN v_part_interval = '1 week' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''week'', NEW.'||v_control||');'; WHEN v_part_interval = '1 month' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''month'', NEW.'||v_control||');'; WHEN v_part_interval = '3 months' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''quarter'', NEW.'||v_control||');'; WHEN v_part_interval = '1 year' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''year'', NEW.'||v_control||');'; END CASE; v_trig_func := v_trig_func||' v_partition_name := @extschema@.check_name_length('''||v_parent_tablename||''', '''||v_parent_schema||''', to_char(v_partition_timestamp, '||quote_literal(v_datetime_string)||'), TRUE); SELECT count(*) INTO v_count FROM pg_tables WHERE schemaname ||''.''|| tablename = v_partition_name; IF v_count > 0 THEN EXECUTE ''INSERT INTO ''||v_partition_name||'' VALUES($1.*)'' USING NEW; ELSE RETURN NEW; END IF; END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for dynamic time table: '||p_parent_table); END IF; ELSIF v_type = 'time-custom' THEN v_trig_func := 'CREATE OR REPLACE FUNCTION '||v_function_name||'() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_child_table text; v_count int; BEGIN SELECT child_table INTO v_child_table FROM @extschema@.custom_time_partitions WHERE partition_range @> NEW.'||v_control||' AND parent_table = '||quote_literal(p_parent_table)||'; SELECT count(*) INTO v_count FROM pg_tables WHERE schemaname ||''.''|| tablename = v_child_table; IF v_count > 0 THEN EXECUTE ''INSERT INTO ''||v_child_table||'' VALUES ($1.*)'' USING NEW; ELSE RETURN NEW; END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for custom time table: '||p_parent_table); END IF; ELSE RAISE EXCEPTION 'ERROR: Invalid time partitioning type given: %', v_type; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE FUNCTION: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Populate the child table(s) of a time-based partition set with old data from the original parent */ CREATE OR REPLACE FUNCTION partition_data_time(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval interval DEFAULT NULL, p_lock_wait numeric DEFAULT 0) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_datetime_string text; v_last_partition_name text; v_max_partition_timestamp timestamp; v_last_partition text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_min_control timestamp; v_min_partition_timestamp timestamp; v_part_interval interval; v_partition_timestamp timestamp[]; v_rowcount bigint; v_sql text; v_time_position int; v_total_rows bigint := 0; v_type text; BEGIN SELECT type , part_interval::interval , control , datetime_string , last_partition INTO v_type , v_part_interval , v_control , v_datetime_string , v_last_partition FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic' OR type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF p_batch_interval IS NULL OR p_batch_interval > v_part_interval THEN p_batch_interval := v_part_interval; END IF; FOR i IN 1..p_batch_count LOOP EXECUTE 'SELECT min('||v_control||') FROM ONLY '||p_parent_table INTO v_min_control; IF v_min_control IS NULL THEN RETURN 0; END IF; IF v_type = 'time-static' OR v_type = 'time-dynamic' THEN CASE WHEN v_part_interval = '15 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_min_control) + '15min'::interval * floor(date_part('minute', v_min_control) / 15.0); WHEN v_part_interval = '30 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_min_control) + '30min'::interval * floor(date_part('minute', v_min_control) / 30.0); WHEN v_part_interval = '1 hour' THEN v_min_partition_timestamp := date_trunc('hour', v_min_control); WHEN v_part_interval = '1 day' THEN v_min_partition_timestamp := date_trunc('day', v_min_control); WHEN v_part_interval = '1 week' THEN v_min_partition_timestamp := date_trunc('week', v_min_control); WHEN v_part_interval = '1 month' THEN v_min_partition_timestamp := date_trunc('month', v_min_control); WHEN v_part_interval = '3 months' THEN v_min_partition_timestamp := date_trunc('quarter', v_min_control); WHEN v_part_interval = '1 year' THEN v_min_partition_timestamp := date_trunc('year', v_min_control); END CASE; ELSIF v_type = 'time-custom' THEN -- Keep going backwards, checking if the time interval encompases the current v_min_control value v_time_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; v_min_partition_timestamp := to_timestamp(substring(v_last_partition from v_time_position), v_datetime_string); v_max_partition_timestamp := v_min_partition_timestamp + v_part_interval; LOOP IF v_min_control >= v_min_partition_timestamp AND v_min_control < v_max_partition_timestamp THEN EXIT; ELSE v_max_partition_timestamp := v_min_partition_timestamp; BEGIN v_min_partition_timestamp := v_min_partition_timestamp - v_part_interval; EXCEPTION WHEN datetime_field_overflow THEN RAISE EXCEPTION 'Attempted partition time interval is outside PostgreSQL''s supported time range. Unable to create partition with interval before timestamp % ', v_min_partition_interval; END; END IF; END LOOP; END IF; v_partition_timestamp := ARRAY[v_min_partition_timestamp]; IF (v_min_control + p_batch_interval) >= (v_min_partition_timestamp + v_part_interval) THEN v_max_partition_timestamp := v_min_partition_timestamp + v_part_interval; ELSE v_max_partition_timestamp := v_min_control + p_batch_interval; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN v_sql := 'SELECT * FROM ONLY ' || p_parent_table || ' WHERE '||v_control||' >= '||quote_literal(v_min_control)|| ' AND '||v_control||' < '||quote_literal(v_max_partition_timestamp) ||' FOR UPDATE NOWAIT'; EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; v_last_partition_name := @extschema@.create_time_partition(p_parent_table, v_partition_timestamp); EXECUTE 'WITH partition_data AS ( DELETE FROM ONLY '||p_parent_table||' WHERE '||v_control||' >= '||quote_literal(v_min_control)|| ' AND '||v_control||' < '||quote_literal(v_max_partition_timestamp)||' RETURNING *) INSERT INTO '||v_last_partition_name||' SELECT * FROM partition_data'; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; IF v_type = 'time-static' THEN PERFORM @extschema@.create_time_function(p_parent_table); END IF; RETURN v_total_rows; END $$; /* * Function to manage pre-creation of the next partitions in a time-based partition set. * Also manages dropping old partitions if the retention option is set. */ CREATE FUNCTION run_maintenance(p_jobmon BOOLEAN DEFAULT true) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_create_count int := 0; v_current_partition text; v_current_partition_timestamp timestamp; v_datetime_string text; v_drop_count int := 0; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_timestamp timestamp; v_next_partition_timestamp timestamp; v_old_search_path text; v_premade_count int; v_quarter text; v_step_id bigint; v_step_overflow_id bigint; v_row record; v_tablename text; v_time_position int; v_year text; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman maintenance already running.'; RETURN; END IF; 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' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN RUN MAINTENANCE'); v_step_id := add_step(v_job_id, 'Running maintenance loop'); END IF; FOR v_row IN SELECT parent_table , type , part_interval::interval , control , premake , datetime_string , last_partition , undo_in_progress FROM @extschema@.part_config WHERE type = 'time-static' OR type = 'time-dynamic' OR type = 'time-custom' LOOP CONTINUE WHEN v_row.undo_in_progress; -- Double check that last created partition exists IF v_row.last_partition IS NOT NULL THEN SELECT tablename INTO v_tablename FROM pg_tables WHERE schemaname || '.' || tablename = v_row.last_partition; IF v_tablename IS NULL THEN RAISE EXCEPTION 'ERROR: Last known partition table missing for parent table %. Unable to determine next partition in sequence.', v_row.parent_table; END IF; ELSE RAISE EXCEPTION 'ERROR: Last known partition missing from config table for parent table %.', p_parent_table; END IF; IF v_row.type = 'time-static' OR v_row.type = 'time-dynamic' THEN CASE WHEN v_row.part_interval = '15 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_row.part_interval = '30 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_row.part_interval = '1 hour' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '1 day' THEN v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '1 week' THEN v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '1 month' THEN v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '3 months' THEN v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_row.part_interval = '1 year' THEN v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; ELSIF v_row.type = 'time-custom' THEN SELECT child_table INTO v_current_partition FROM @extschema@.custom_time_partitions WHERE parent_table = v_row.parent_table AND partition_range @> CURRENT_TIMESTAMP; IF v_current_partition IS NULL THEN RAISE EXCEPTION 'Current time partition missing from custom_time_partitions config table for table % and timestamp %', CURRENT_TIMESTAMP, v_row.parent_table; END IF; v_time_position := (length(v_current_partition) - position('p_' in reverse(v_current_partition))) + 2; v_current_partition_timestamp := to_timestamp(substring(v_current_partition from v_time_position), v_row.datetime_string); END IF; v_time_position := (length(v_row.last_partition) - position('p_' in reverse(v_row.last_partition))) + 2; IF v_row.part_interval <> '3 months' OR (v_row.part_interval = '3 months' AND v_row.type = 'time-custom') THEN v_last_partition_timestamp := to_timestamp(substring(v_row.last_partition from v_time_position), v_row.datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(v_row.last_partition from v_time_position), 'q', 1); v_quarter := split_part(substring(v_row.last_partition from v_time_position), 'q', 2); CASE WHEN v_quarter = '1' THEN v_last_partition_timestamp := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_last_partition_timestamp := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_last_partition_timestamp := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_last_partition_timestamp := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; -- Check and see how many premade partitions there are. v_premade_count = round(EXTRACT('epoch' FROM age(v_last_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.part_interval::interval)); v_next_partition_timestamp := v_last_partition_timestamp; -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed. WHILE v_premade_count < v_row.premake LOOP BEGIN v_next_partition_timestamp := v_next_partition_timestamp + v_row.part_interval; EXCEPTION WHEN datetime_field_overflow THEN RAISE WARNING 'Attempted partition time interval is outside PostgreSQL''s supported time range. Child partition creation skipped for parent table %', v_row.parent_table; v_premade_count = v_row.premake; -- do this so it can exit the premake check loop and continue in the outer for loop 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 skippd for parent table '||v_partition_time); CONTINUE; END; v_last_partition := @extschema@.create_time_partition(v_row.parent_table, ARRAY[v_next_partition_timestamp]); IF v_last_partition IS NOT NULL THEN UPDATE @extschema@.part_config SET last_partition = v_last_partition WHERE parent_table = v_row.parent_table; END IF; v_create_count := v_create_count + 1; IF v_row.type = 'time-static' THEN PERFORM @extschema@.create_time_function(v_row.parent_table); END IF; -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(v_row.parent_table); v_premade_count = round(EXTRACT('epoch' FROM age(v_next_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.part_interval::interval)); END LOOP; END LOOP; -- end of creation loop -- Manage dropping old partitions if retention option is set FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND (type = 'time-static' OR type = 'time-dynamic' OR type = 'time-custom') LOOP v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); END LOOP; FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND (type = 'id-static' OR type = 'id-dynamic') LOOP v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Partition maintenance finished. '||v_create_count||' partitions made. '||v_drop_count||' partitions dropped.'); IF v_step_overflow_id IS NOT NULL THEN PERFORM fail_job(v_job_id); ELSE PERFORM close_job(v_job_id); END IF; EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN RUN MAINTENANCE'')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to drop child tables from a time-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ CREATE OR REPLACE FUNCTION drop_partition_time(p_parent_table text, p_retention interval DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_child_table text; v_datetime_string text; v_drop_count int := 0; v_index record; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_part_interval interval; v_partition_timestamp timestamp; v_quarter text; v_retention interval; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_step_id bigint; v_time_position int; v_type text; v_year text; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman drop_partition_time')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_time already running.'; RETURN 0; END IF; -- Allow override of configuration options IF p_retention IS NULL THEN SELECT type , part_interval::interval , retention::interval , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_type , v_part_interval , v_retention , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic' OR type = 'time-custom') AND retention IS NOT NULL; IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE SELECT type , part_interval::interval , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_type , v_part_interval , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic' OR type = 'time-custom'); v_retention := p_retention; IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN DROP TIME PARTITION: '|| p_parent_table); END IF; -- Loop through child tables of the given parent FOR v_child_table IN SELECT n.nspname||'.'||c.relname FROM pg_inherits i join pg_class c ON i.inhrelid = c.oid join pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC LOOP -- pull out datetime portion of partition's tablename to make the next one v_time_position := (length(v_child_table) - position('p_' in reverse(v_child_table))) + 2; IF v_part_interval <> '3 months' OR (v_part_interval = '3 months' AND v_type = 'time-custom') THEN v_partition_timestamp := to_timestamp(substring(v_child_table from v_time_position), v_datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(v_child_table from v_time_position), 'q', 1); v_quarter := split_part(substring(v_child_table from v_time_position), 'q', 2); CASE WHEN v_quarter = '1' THEN v_partition_timestamp := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_partition_timestamp := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_partition_timestamp := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_partition_timestamp := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; -- Add one interval since partition names contain the start of the constraint period IF v_retention < (CURRENT_TIMESTAMP - (v_partition_timestamp + v_part_interval)) THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Uninherit table '||v_child_table||' from '||p_parent_table); END IF; EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Drop table '||v_child_table); END IF; EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN FOR v_index IN SELECT i.indexrelid::regclass AS name , c.conname FROM pg_catalog.pg_index i LEFT JOIN pg_catalog.pg_constraint c ON i.indexrelid = c.conindid WHERE i.indrelid = v_child_table::regclass LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Drop index '||v_index.name||' from '||v_child_table); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_child_table||' DROP CONSTRAINT '||v_index.conname; ELSE EXECUTE 'DROP INDEX '||v_index.name; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Moving table '||v_child_table||' to schema '||v_retention_schema); END IF; EXECUTE 'ALTER TABLE '||v_child_table||' SET SCHEMA '||v_retention_schema; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', v_drop_count||' partitions dropped.'); PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_drop_count; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN DROP TIME PARTITION: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to undo time-based partitioning created by this extension */ CREATE OR REPLACE FUNCTION undo_partition_time(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval interval DEFAULT NULL, p_keep_table boolean DEFAULT true) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_batch_loop_count int := 0; v_child_min timestamptz; v_child_loop_total bigint := 0; v_child_table text; v_control text; v_function_name text; v_inner_loop_count int; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_move_sql text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_part_interval interval; v_row record; v_rowcount bigint; v_step_id bigint; v_total bigint := 0; v_trig_name text; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_time_partition')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_time_partition already running.'; RETURN 0; END IF; SELECT part_interval::interval , control , jobmon INTO v_part_interval , v_control , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic' OR type = 'time-custom'); IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Undoing partitioning for table '||p_parent_table); END IF; IF p_batch_interval IS NULL THEN p_batch_interval := v_part_interval; END IF; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables. SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, '_part_trig_func', FALSE); EXECUTE 'DROP TRIGGER IF EXISTS '||v_trig_name||' ON '||p_parent_table; EXECUTE 'DROP FUNCTION IF EXISTS '||v_function_name||'()'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); END IF; <> WHILE v_batch_loop_count < p_batch_count LOOP SELECT n.nspname||'.'||c.relname INTO v_child_table FROM pg_inherits i JOIN pg_class c ON i.inhrelid = c.oid JOIN pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC; EXIT WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing child partition: '||v_child_table); END IF; EXECUTE 'SELECT min('||v_control||') FROM '||v_child_table INTO v_child_min; IF v_child_min IS NULL THEN -- No rows left in this child table. Remove from partition set. EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF p_keep_table = false THEN EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table UNINHERITED, not DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; END IF; v_undo_count := v_undo_count + 1; CONTINUE outer_child_loop; END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP -- Get everything from the current child minimum up to the multiples of the given interval v_move_sql := 'WITH move_data AS (DELETE FROM '||v_child_table|| ' WHERE '||v_control||' <= '||quote_literal(v_child_min + (p_batch_interval * v_inner_loop_count))||' RETURNING *) INSERT INTO '||p_parent_table||' SELECT * FROM move_data'; EXECUTE v_move_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Moved '||v_child_loop_total||' rows to parent.'); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; IF v_batch_loop_count < p_batch_count THEN -- FOR loop never ran, so there's no child tables left. DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) to the parent. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', 'Copied '||v_total||' row(s) to the parent. Removed '||v_undo_count||' partitions.'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_total; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN UNDO PARTITIONING: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to create id partitions */ CREATE FUNCTION create_id_partition (p_parent_table text, p_partition_ids bigint[]) RETURNS text LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_control text; v_grantees text[]; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_parent_tablespace text; v_part_interval bigint; v_partition_name text; v_revoke text[]; v_step_id bigint; v_tablename text; v_id bigint; BEGIN SELECT control , part_interval , jobmon INTO v_control , v_part_interval , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; FOREACH v_id IN ARRAY p_partition_ids LOOP v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_id::text, TRUE); -- If child table already exists, skip creation SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_tablename IS NOT NULL THEN CONTINUE; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_id||' to '||(v_id + v_part_interval)-1); END IF; EXECUTE 'CREATE TABLE '||v_partition_name||' (LIKE '||p_parent_table||' INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)'; SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_parent_tablespace IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_partition_name||' SET TABLESPACE '||v_parent_tablespace; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' ADD CONSTRAINT '||v_tablename||'_partition_check CHECK ('||v_control||'>='||quote_literal(v_id)||' AND '||v_control||'<'||quote_literal(v_id + v_part_interval)||')'; EXECUTE 'ALTER TABLE '||v_partition_name||' INHERIT '||p_parent_table; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP EXECUTE 'GRANT '||array_to_string(v_parent_grant.types, ',')||' ON '||v_partition_name||' TO '||v_parent_grant.grantee; SELECT array_agg(r) INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE '||array_to_string(v_revoke, ',')||' ON '||v_partition_name||' FROM '||v_parent_grant.grantee||' CASCADE'; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN SELECT array_agg(r) INTO v_revoke FROM ( SELECT DISTINCT grantee::text AS r FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_partition_name EXCEPT SELECT unnest(v_grantees)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE ALL ON '||v_partition_name||' FROM '||array_to_string(v_revoke, ','); END IF; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' OWNER TO '||v_parent_owner; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); PERFORM close_job(v_job_id); END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_partition_name; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE TABLE: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Create the trigger function for the parent table of an id-based partition set */ CREATE OR REPLACE FUNCTION create_id_function(p_parent_table text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_count int; v_current_partition_name text; v_current_partition_id bigint; v_datetime_string text; v_final_partition_id bigint; v_function_name text; v_job_id bigint; v_jobmon text; v_jobmon_schema text; v_last_partition text; v_max bigint; v_next_partition_id bigint; v_next_partition_name text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_part_interval bigint; v_premake int; v_prev_partition_id bigint; v_prev_partition_name text; v_step_id bigint; v_trig_func text; v_type text; BEGIN SELECT type , part_interval::bigint , control , premake , last_partition , jobmon INTO v_type , v_part_interval , v_control , v_premake , v_last_partition , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE FUNCTION: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating partition function for table '||p_parent_table); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_function_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, '_part_trig_func', FALSE); IF v_type = 'id-static' THEN EXECUTE 'SELECT COALESCE(max('||v_control||'), 0) FROM '||p_parent_table INTO v_max; v_current_partition_id = v_max - (v_max % v_part_interval); v_next_partition_id := v_current_partition_id + v_part_interval; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_current_partition_id::text, TRUE); v_trig_func := 'CREATE OR REPLACE FUNCTION '||v_function_name||'() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_current_partition_id bigint; v_last_partition text := '||quote_literal(v_last_partition)||'; v_id_position int; v_next_partition_id bigint; v_next_partition_name text; BEGIN IF TG_OP = ''INSERT'' THEN IF NEW.'||v_control||' >= '||v_current_partition_id||' AND NEW.'||v_control||' < '||v_next_partition_id|| ' THEN INSERT INTO '||v_current_partition_name||' VALUES (NEW.*); '; FOR i IN 1..v_premake LOOP v_prev_partition_id := v_current_partition_id - (v_part_interval * i); v_next_partition_id := v_current_partition_id + (v_part_interval * i); v_final_partition_id := v_next_partition_id + v_part_interval; v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_prev_partition_id::text, TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_next_partition_id::text, TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles edge case of changing premake immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_prev_partition_name; IF v_count > 0 THEN -- Only handle previous partitions if they're starting above zero IF v_prev_partition_id >= 0 THEN v_trig_func := v_trig_func ||' ELSIF NEW.'||v_control||' >= '||v_prev_partition_id||' AND NEW.'||v_control||' < '||v_prev_partition_id + v_part_interval|| ' THEN INSERT INTO '||v_prev_partition_name||' VALUES (NEW.*); '; END IF; END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_next_partition_name; IF v_count > 0 THEN v_trig_func := v_trig_func ||' ELSIF NEW.'||v_control||' >= '||v_next_partition_id||' AND NEW.'||v_control||' < '||v_final_partition_id|| ' THEN INSERT INTO '||v_next_partition_name||' VALUES (NEW.*); '; END IF; END LOOP; v_trig_func := v_trig_func ||' ELSE RETURN NEW; END IF; v_current_partition_id := NEW.'||v_control||' - (NEW.'||v_control||' % '||v_part_interval||'); IF (NEW.'||v_control||' % '||v_part_interval||') > ('||v_part_interval||' / 2) THEN v_id_position := (length(v_last_partition) - position(''p_'' in reverse(v_last_partition))) + 2; v_next_partition_id := (substring(v_last_partition from v_id_position)::bigint) + '||v_part_interval||'; WHILE ((v_next_partition_id - v_current_partition_id) / '||v_part_interval||') <= '||v_premake||' LOOP v_next_partition_name := @extschema@.create_id_partition('||quote_literal(p_parent_table)||', ARRAY[v_next_partition_id]); UPDATE @extschema@.part_config SET last_partition = v_next_partition_name WHERE parent_table = '||quote_literal(p_parent_table)||'; PERFORM @extschema@.create_id_function('||quote_literal(p_parent_table)||'); PERFORM @extschema@.apply_constraints('||quote_literal(p_parent_table)||'); v_next_partition_id := v_next_partition_id + '||v_part_interval||'; END LOOP; END IF; END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for current id interval: '||v_current_partition_id||' to '||v_final_partition_id-1); END IF; ELSIF v_type = 'id-dynamic' THEN -- The return inside the partition creation check is there to keep really high ID values from creating new partitions. v_trig_func := 'CREATE OR REPLACE FUNCTION '||v_function_name||'() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_current_partition_id bigint; v_current_partition_name text; v_id_position int; v_last_partition text := '||quote_literal(v_last_partition)||'; v_last_partition_id bigint; v_next_partition_id bigint; v_next_partition_name text; BEGIN IF TG_OP = ''INSERT'' THEN v_current_partition_id := NEW.'||v_control||' - (NEW.'||v_control||' % '||v_part_interval||'); v_current_partition_name := @extschema@.check_name_length('''||v_parent_tablename||''', '''||v_parent_schema||''', v_current_partition_id::text, TRUE); IF (NEW.'||v_control||' % '||v_part_interval||') > ('||v_part_interval||' / 2) THEN v_id_position := (length(v_last_partition) - position(''p_'' in reverse(v_last_partition))) + 2; v_last_partition_id = substring(v_last_partition from v_id_position)::bigint; v_next_partition_id := v_last_partition_id + '||v_part_interval||'; IF NEW.'||v_control||' >= v_next_partition_id THEN RETURN NEW; END IF; WHILE ((v_next_partition_id - v_current_partition_id) / '||v_part_interval||') <= '||v_premake||' LOOP v_next_partition_name := @extschema@.create_id_partition('||quote_literal(p_parent_table)||', ARRAY[v_next_partition_id]); IF v_next_partition_name IS NOT NULL THEN UPDATE @extschema@.part_config SET last_partition = v_next_partition_name WHERE parent_table = '||quote_literal(p_parent_table)||'; PERFORM @extschema@.create_id_function('||quote_literal(p_parent_table)||'); PERFORM @extschema@.apply_constraints('||quote_literal(p_parent_table)||'); END IF; v_next_partition_id := v_next_partition_id + '||v_part_interval||'; END LOOP; END IF; SELECT count(*) INTO v_count FROM pg_tables WHERE schemaname ||''.''|| tablename = v_current_partition_name; IF v_count > 0 THEN EXECUTE ''INSERT INTO ''||v_current_partition_name||'' VALUES($1.*)'' USING NEW; ELSE RETURN NEW; END IF; END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for dynamic id table: '||p_parent_table); END IF; ELSE RAISE EXCEPTION 'ERROR: Invalid id partitioning type given: %', v_type; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE FUNCTION: '||p_parent_table||')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''Partition function maintenance for table '||p_parent_table||' failed'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Populate the child table(s) of an id-based partition set with old data from the original parent */ CREATE OR REPLACE FUNCTION partition_data_id(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval int DEFAULT NULL, p_lock_wait numeric DEFAULT 0) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_last_partition_name text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_id bigint; v_min_control bigint; v_min_partition_id bigint; v_part_interval bigint; v_partition_id bigint[]; v_rowcount bigint; v_sql text; v_total_rows bigint := 0; v_type text; BEGIN SELECT type , part_interval::bigint , control INTO v_type , v_part_interval , v_control FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF p_batch_interval IS NULL OR p_batch_interval > v_part_interval THEN p_batch_interval := v_part_interval; END IF; FOR i IN 1..p_batch_count LOOP EXECUTE 'SELECT min('||v_control||') FROM ONLY '||p_parent_table INTO v_min_control; IF v_min_control IS NULL THEN RETURN 0; END IF; v_min_partition_id = v_min_control - (v_min_control % v_part_interval); v_partition_id := ARRAY[v_min_partition_id]; -- RAISE NOTICE 'v_partition_id: %',v_partition_id; IF (v_min_control + p_batch_interval) >= (v_min_partition_id + v_part_interval) THEN v_max_partition_id := v_min_partition_id + v_part_interval; ELSE v_max_partition_id := v_min_control + p_batch_interval; END IF; -- RAISE NOTICE 'v_max_partition_id: %',v_max_partition_id; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN v_sql := 'SELECT * FROM ONLY ' || p_parent_table || ' WHERE '||v_control||' >= '||quote_literal(v_min_control)|| ' AND '||v_control||' < '||quote_literal(v_max_partition_id) ||' FOR UPDATE NOWAIT'; EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; v_last_partition_name := @extschema@.create_id_partition(p_parent_table, v_partition_id); EXECUTE 'WITH partition_data AS ( DELETE FROM ONLY '||p_parent_table||' WHERE '||v_control||' >= '||v_min_control|| ' AND '||v_control||' < '||v_max_partition_id||' RETURNING *) INSERT INTO '||v_last_partition_name||' SELECT * FROM partition_data'; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; IF v_type = 'id-static' THEN PERFORM @extschema@.create_id_function(p_parent_table); END IF; RETURN v_total_rows; END $$; /* * Function to drop child tables from an id-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ CREATE OR REPLACE FUNCTION drop_partition_id(p_parent_table text, p_retention bigint DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_child_table text; v_control text; v_drop_count int := 0; v_id_position int; v_index record; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_max bigint; v_old_search_path text; v_part_interval bigint; v_partition_id bigint; v_retention bigint; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_step_id bigint; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman drop_partition_id')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_id already running.'; RETURN 0; END IF; -- Allow override of configuration options IF p_retention IS NULL THEN SELECT part_interval::bigint , control , retention::bigint , retention_keep_table , retention_keep_index , retention_schema , jobmon INTO v_part_interval , v_control , v_retention , v_retention_keep_table , v_retention_keep_index , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic') AND retention IS NOT NULL; IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE SELECT part_interval::bigint , control , retention_keep_table , retention_keep_index , retention_schema , jobmon INTO v_part_interval , v_control , v_retention_keep_table , v_retention_keep_index , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); v_retention := p_retention; IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN DROP ID PARTITION: '|| p_parent_table); END IF; EXECUTE 'SELECT max('||v_control||') FROM '||p_parent_table INTO v_max; -- Loop through child tables of the given parent FOR v_child_table IN SELECT n.nspname||'.'||c.relname FROM pg_inherits i join pg_class c ON i.inhrelid = c.oid join pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC LOOP v_id_position := (length(v_child_table) - position('p_' in reverse(v_child_table))) + 2; v_partition_id := substring(v_child_table from v_id_position)::bigint; -- Add one interval since partition names contain the start of the constraint period IF v_retention <= (v_max - (v_partition_id + v_part_interval)) THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Uninherit table '||v_child_table||' from '||p_parent_table); END IF; EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Drop table '||v_child_table); END IF; EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN FOR v_index IN SELECT i.indexrelid::regclass AS name , c.conname FROM pg_catalog.pg_index i LEFT JOIN pg_catalog.pg_constraint c ON i.indexrelid = c.conindid WHERE i.indrelid = v_child_table::regclass LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Drop index '||v_index.name||' from '||v_child_table); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_child_table||' DROP CONSTRAINT '||v_index.conname; ELSE EXECUTE 'DROP INDEX '||v_index.name; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Moving table '||v_child_table||' to schema '||v_retention_schema); END IF; EXECUTE 'ALTER TABLE '||v_child_table||' SET SCHEMA '||v_retention_schema; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', v_drop_count||' partitions dropped.'); PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_drop_count; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN DROP ID PARTITION: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Apply constraints managed by partman extension */ CREATE OR REPLACE FUNCTION apply_constraints(p_parent_table text, p_child_table text DEFAULT NULL, p_debug BOOLEAN DEFAULT FALSE) RETURNS void LANGUAGE plpgsql AS $$ DECLARE v_child_table text; v_child_tablename text; v_col text; v_constraint_cols text[]; v_constraint_col_type text; v_constraint_name text; v_datetime_string text; v_existing_constraint_name text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_id int; v_last_partition_timestamp timestamp; v_constraint_values record; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_part_interval text; v_partition_suffix text; v_premake int; v_sql text; v_step_id bigint; v_suffix_position int; v_type text; BEGIN SELECT type , part_interval , premake , datetime_string , last_partition , constraint_cols , jobmon INTO v_type , v_part_interval , v_premake , v_datetime_string , v_last_partition , v_constraint_cols , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_constraint_cols IS NULL THEN IF p_debug THEN RAISE NOTICE 'Given parent table (%) not set up for constraint management (constraint_cols is NULL)', p_parent_table; END IF; -- Returns silently to allow this function to be simply called by maintenance processes without having to check if config options are set. RETURN; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE CONSTRAINT: '||p_parent_table); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; -- If p_child_table is null, figure out the partition that is the one right before the premake value backwards. IF p_child_table IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Automatically determining most recent child on which to apply constraints'); END IF; v_suffix_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; IF v_type IN ('time-static', 'time-dynamic') THEN v_last_partition_timestamp := to_timestamp(substring(v_last_partition from v_suffix_position), v_datetime_string); v_partition_suffix := to_char(v_last_partition_timestamp - (v_part_interval::interval * ((v_premake * 2)+1) ), v_datetime_string); ELSIF v_type IN ('id-static', 'id-dynamic') THEN v_last_partition_id := substring(v_last_partition from v_suffix_position)::int; v_partition_suffix := (v_last_partition_id - (v_part_interval::int * ((v_premake * 2)+1) ))::text; END IF; v_child_table := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_partition_suffix, TRUE); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Target child table: '||v_child_table); END IF; ELSE v_child_table := p_child_table; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Checking if target child table exists'); END IF; SELECT tablename INTO v_child_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_child_table; IF v_child_tablename IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', 'Target child table ('||v_child_table||') does not exist. Skipping constraint creation.'); PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; IF p_debug THEN RAISE NOTICE 'Target child table (%) does not exist. Skipping constraint creation.', v_child_table; END IF; RETURN; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; FOREACH v_col IN ARRAY v_constraint_cols LOOP SELECT c.conname INTO v_existing_constraint_name FROM pg_catalog.pg_constraint c JOIN pg_catalog.pg_attribute a ON c.conrelid = a.attrelid WHERE conrelid = v_child_table::regclass AND c.conname LIKE 'partmanconstr_%' AND c.contype = 'c' AND a.attname = v_col AND ARRAY[a.attnum] <@ c.conkey AND a.attisdropped = false; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying new constraint on column: '||v_col); END IF; IF v_existing_constraint_name IS NOT NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', 'Partman managed constraint already exists on this table ('||v_child_table||') and column ('||v_col||'). Skipping creation.'); END IF; RAISE WARNING 'Partman managed constraint already exists on this table (%) and column (%). Skipping creation.', v_child_table, v_col ; CONTINUE; END IF; -- Ensure column name gets put on end of constraint name to help avoid naming conflicts v_constraint_name := @extschema@.check_name_length('partmanconstr_'||v_child_tablename, p_suffix := '_'||v_col); EXECUTE 'SELECT min('||v_col||')::text AS min, max('||v_col||')::text AS max FROM '||v_child_table INTO v_constraint_values; IF v_constraint_values IS NOT NULL THEN v_sql := concat('ALTER TABLE ', v_child_table, ' ADD CONSTRAINT ', v_constraint_name , ' CHECK (', v_col, ' >= ', quote_literal(v_constraint_values.min), ' AND ' , v_col, ' <= ', quote_literal(v_constraint_values.max), ')' ); IF p_debug THEN RAISE NOTICE 'Constraint creation query: %', v_sql; END IF; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'New constraint created: '||v_sql); END IF; ELSE IF p_debug THEN RAISE NOTICE 'Given column (%) contains all NULLs. No constraint created', v_col; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', 'Given column ('||v_col||') contains all NULLs. No constraint created'); END IF; END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE CONSTRAINT: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Drop constraints managed by pg_partman */ CREATE OR REPLACE FUNCTION drop_constraints(p_parent_table text, p_child_table text, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql AS $$ DECLARE v_col text; v_constraint_cols text[]; v_existing_constraint_name text; v_exists boolean := FALSE; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_sql text; v_step_id bigint; BEGIN SELECT constraint_cols , jobmon INTO v_constraint_cols , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_constraint_cols IS NULL THEN RAISE EXCEPTION 'Given parent table (%) not set up for constraint management (constraint_cols is NULL)', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN DROP CONSTRAINT: '||p_parent_table); v_step_id := add_step(v_job_id, 'Entering constraint drop loop'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; FOREACH v_col IN ARRAY v_constraint_cols LOOP SELECT c.conname INTO v_existing_constraint_name FROM pg_catalog.pg_constraint c JOIN pg_catalog.pg_attribute a ON c.conrelid = a.attrelid WHERE conrelid = p_child_table::regclass AND c.conname LIKE 'partmanconstr_%' AND c.contype = 'c' AND a.attname = v_col AND ARRAY[a.attnum] <@ c.conkey AND a.attisdropped = false; IF v_existing_constraint_name IS NOT NULL THEN v_exists := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Dropping constraint on column: '||v_col); END IF; v_sql := 'ALTER TABLE '||p_child_table||' DROP CONSTRAINT '||v_existing_constraint_name; IF p_debug THEN RAISE NOTICE 'Constraint drop query: %', v_sql; END IF; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Drop constraint query: '||v_sql); END IF; END IF; END LOOP; IF v_jobmon_schema IS NOT NULL AND v_exists IS FALSE THEN v_step_id := add_step(v_job_id, 'No constraints found to drop on child table: '||p_child_table); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN DROP CONSTRAINT: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to re-apply ownership & privileges on all child tables in a partition set using parent table as reference */ CREATE OR REPLACE FUNCTION reapply_privileges(p_parent_table text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_child_owner text; v_child_table text; v_child_grant record; v_grant text; v_grantees text[]; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_match boolean; v_old_search_path text; v_parent_owner text; v_owner_sql text; v_revoke text[]; v_parent_grant record; v_sql text; v_step_id bigint; BEGIN SELECT jobmon INTO v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon IS NULL THEN RAISE EXCEPTION 'Given table is not managed by this extention: %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN RE-APPLYING PRIVILEGES TO ALL CHILD TABLES OF: '||p_parent_table); v_step_id := add_step(v_job_id, 'Setting new child table privileges'); END IF; SELECT tableowner INTO v_parent_owner FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; FOR v_child_table IN SELECT n.nspname||'.'||c.relname FROM pg_inherits i join pg_class c ON i.inhrelid = c.oid join pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC LOOP IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'PENDING', 'Currently on child partition in ascending order: '||v_child_table); END IF; v_grantees := NULL; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP -- Compare parent & child grants. Don't re-apply if it already exists v_match := false; FOR v_child_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_child_table GROUP BY grantee LOOP IF v_parent_grant.types = v_child_grant.types AND v_parent_grant.grantee = v_child_grant.grantee THEN v_match := true; END IF; END LOOP; IF v_match = false THEN EXECUTE 'GRANT '||array_to_string(v_parent_grant.types, ',')||' ON '||v_child_table||' TO '||v_parent_grant.grantee; SELECT array_agg(r) INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE '||array_to_string(v_revoke, ',')||' ON '||v_child_table||' FROM '||v_parent_grant.grantee||' CASCADE'; END IF; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN SELECT array_agg(r) INTO v_revoke FROM ( SELECT DISTINCT grantee::text AS r FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_child_table EXCEPT SELECT unnest(v_grantees)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE ALL ON '||v_child_table||' FROM '||array_to_string(v_revoke, ','); END IF; END IF; SELECT tableowner INTO v_child_owner FROM pg_tables WHERE schemaname ||'.'|| tablename = v_child_table; IF v_parent_owner <> v_child_owner THEN EXECUTE 'ALTER TABLE '||v_child_table||' OWNER TO '||v_parent_owner; END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); PERFORM close_job(v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN RE-APPLYING PRIVILEGES TO ALL CHILD TABLES OF: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to undo id-based partitioning created by this extension */ CREATE OR REPLACE FUNCTION undo_partition_id(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval bigint DEFAULT NULL, p_keep_table boolean DEFAULT true) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_batch_loop_count int := 0; v_child_loop_total bigint := 0; v_child_min bigint; v_child_table text; v_control text; v_function_name text; v_inner_loop_count int; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_move_sql text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_part_interval bigint; v_row record; v_rowcount bigint; v_step_id bigint; v_trig_name text; v_total bigint := 0; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_id_partition')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_id_partition already running.'; RETURN 0; END IF; SELECT part_interval::bigint , control , jobmon INTO v_part_interval , v_control , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Undoing partitioning for table '||p_parent_table); END IF; IF p_batch_interval IS NULL THEN p_batch_interval := v_part_interval; END IF; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables and stop new id partitions from being made. SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, '_part_trig_func', FALSE); EXECUTE 'DROP TRIGGER IF EXISTS '||v_trig_name||' ON '||p_parent_table; EXECUTE 'DROP FUNCTION IF EXISTS '||v_function_name||'()'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); END IF; <> WHILE v_batch_loop_count < p_batch_count LOOP SELECT n.nspname||'.'||c.relname INTO v_child_table FROM pg_inherits i JOIN pg_class c ON i.inhrelid = c.oid JOIN pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC; EXIT WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing child partition: '||v_child_table); END IF; EXECUTE 'SELECT min('||v_control||') FROM '||v_child_table INTO v_child_min; IF v_child_min IS NULL THEN -- No rows left in this child table. Remove from partition set. EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF p_keep_table = false THEN EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table UNINHERITED, not DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; END IF; v_undo_count := v_undo_count + 1; CONTINUE outer_child_loop; END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP -- Get everything from the current child minimum up to the multiples of the given interval v_move_sql := 'WITH move_data AS (DELETE FROM '||v_child_table|| ' WHERE '||v_control||' <= '||quote_literal(v_child_min + (p_batch_interval * v_inner_loop_count))||' RETURNING *) INSERT INTO '||p_parent_table||' SELECT * FROM move_data'; EXECUTE v_move_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Moved '||v_child_loop_total||' rows to parent.'); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; IF v_batch_loop_count < p_batch_count THEN -- FOR loop never ran, so there's no child tables left. DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) to the parent. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', 'Copied '||v_total||' row(s) to the parent. Removed '||v_undo_count||' partitions.'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_total; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN UNDO PARTITIONING: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to undo partitioning. * Will actually work on any parent/child table set, not just ones created by pg_partman. */ CREATE FUNCTION undo_partition(p_parent_table text, p_batch_count int DEFAULT 1, p_keep_table boolean DEFAULT true, p_jobmon boolean DEFAULT true) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_batch_loop_count bigint := 0; v_child_count bigint; v_child_table text; v_copy_sql text; v_function_name text; v_job_id bigint; v_jobmon_schema text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_part_interval interval; v_rowcount bigint; v_step_id bigint; v_total bigint := 0; v_trig_name text; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition already running.'; RETURN 0; END IF; IF p_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Undoing partitioning for table '||p_parent_table); END IF; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables and stop new id partitions from being made. SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, '_part_trig_func', FALSE); EXECUTE 'DROP TRIGGER IF EXISTS '||v_trig_name||' ON '||p_parent_table; EXECUTE 'DROP FUNCTION IF EXISTS '||v_function_name||'()'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); END IF; WHILE v_batch_loop_count < p_batch_count LOOP SELECT n.nspname||'.'||c.relname INTO v_child_table FROM pg_inherits i JOIN pg_class c ON i.inhrelid = c.oid JOIN pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC; EXIT WHEN v_child_table IS NULL; EXECUTE 'SELECT count(*) FROM '||v_child_table INTO v_child_count; IF v_child_count = 0 THEN -- No rows left in this child table. Remove from partition set. EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF p_keep_table = false THEN EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table DROPPED. Moved '||coalesce(v_rowcount, 0)||' rows to parent'); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table UNINHERITED, not DROPPED. Copied '||coalesce(v_rowcount, 0)||' rows to parent'); END IF; END IF; v_undo_count := v_undo_count + 1; CONTINUE; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing child partition: '||v_child_table); END IF; v_copy_sql := 'INSERT INTO '||p_parent_table||' SELECT * FROM '||v_child_table; EXECUTE v_copy_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF p_keep_table = false THEN EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table DROPPED. Moved '||v_rowcount||' rows to parent'); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table UNINHERITED, not DROPPED. Copied '||v_rowcount||' rows to parent'); END IF; END IF; v_batch_loop_count := v_batch_loop_count + 1; v_undo_count := v_undo_count + 1; END LOOP; IF v_undo_count = 0 THEN -- FOR loop never ran, so there's no child tables left. DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman (if it existed)'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) from % child table(s) to the parent: %', v_total, v_undo_count, p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', 'Copied '||v_total||' row(s) from '||v_undo_count||' child table(s) to the parent'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_total; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; IF v_job_id IS NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Partition function maintenance for table '||p_parent_table||' failed'); ELSIF v_step_id IS NULL THEN v_step_id := add_step(v_job_id, 'EXCEPTION before first step logged'); END IF; PERFORM update_step(v_step_id, 'CRITICAL', 'ERROR: '||coalesce(SQLERRM,'unknown')); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; DO $$ DECLARE v_row record; BEGIN FOR v_row IN SELECT statement FROM partman_preserve_privs_temp LOOP IF v_row.statement IS NOT NULL THEN EXECUTE v_row.statement; END IF; END LOOP; END $$; DROP TABLE partman_preserve_privs_temp; pg_partman-4.6.0/updates/pg_partman--1.6.0--1.6.1.sql000066400000000000000000001251571412761706100214420ustar00rootroot00000000000000-- The python partitioning script now turns off autovacuum on the entire partition set while it is running. This should help reduce load since it will prevent the autovacuum daemon from kicking off while data is being migrated. When the script is done running, the default value for autovacuum is restored to all tables in the partition set. Also, VACUUM ANALYZE is run on the parent table when all data has finished moving as well. There is an option to disable the turning off of autovacuum if the ALTER TABLE statements are causing more contention and issues than the autovacuum. There is no option for turning off autovacuum when using the plpgsql partitioning functions (inability to COMMIT within function loop would cause too much contention). -- The order that data is migrated from the parent to the children can now be determined via an option to the partition_data_id/time() functions or the python script. The default is the way it originally moved data (ascending order). Thanks for bougyman from #postgresql on freenode for this idea. -- Removed plpgsql function "check_unique_column()" and created python script "check_unique_constraint.py". This runs far more efficiently and causes less contention within the database while checking if a unique constraint is consistent across all child tables. Also now supports checking multi-column constraints. See doc file for more info on script options. -- Fixed syntax error in create_parent(), create_id_function() exception blocks. Reported by bougyman. -- Added pgtap tests for additional constraints feature. CREATE TEMP TABLE partman_preserve_privs_temp (statement text); INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.partition_data_id(text, int, int, numeric, text) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'partition_data_id'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.partition_data_time(text, int, interval, numeric, text) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'partition_data_time'; DROP FUNCTION @extschema@.check_unique_column(text, text); DROP TYPE @extschema@.check_unique_table; DROP FUNCTION partition_data_id(text, int, int, numeric); DROP FUNCTION partition_data_time(text, int, interval, numeric); DROP FUNCTION IF EXISTS create_next_time_partition (text); /* * Populate the child table(s) of an id-based partition set with old data from the original parent */ CREATE FUNCTION partition_data_id(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval int DEFAULT NULL, p_lock_wait numeric DEFAULT 0, p_order text DEFAULT 'ASC') RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_current_partition_name text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_id bigint; v_min_partition_id bigint; v_part_interval bigint; v_partition_id bigint[]; v_rowcount bigint; v_sql text; v_start_control bigint; v_total_rows bigint := 0; v_type text; BEGIN SELECT type , part_interval::bigint , control INTO v_type , v_part_interval , v_control FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF p_batch_interval IS NULL OR p_batch_interval > v_part_interval THEN p_batch_interval := v_part_interval; END IF; FOR i IN 1..p_batch_count LOOP IF p_order = 'ASC' THEN EXECUTE 'SELECT min('||v_control||') FROM ONLY '||p_parent_table INTO v_start_control; IF v_start_control IS NULL THEN RETURN 0; END IF; v_min_partition_id = v_start_control - (v_start_control % v_part_interval); v_partition_id := ARRAY[v_min_partition_id]; -- Check if custom batch interval overflows current partition maximum IF (v_start_control + p_batch_interval) >= (v_min_partition_id + v_part_interval) THEN v_max_partition_id := v_min_partition_id + v_part_interval; ELSE v_max_partition_id := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN EXECUTE 'SELECT max('||v_control||') FROM ONLY '||p_parent_table INTO v_start_control; IF v_start_control IS NULL THEN RETURN 0; END IF; v_min_partition_id = v_start_control - (v_start_control % v_part_interval); -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_id := v_min_partition_id + v_part_interval; v_partition_id := ARRAY[v_min_partition_id]; -- Make sure minimum doesn't underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_id THEN v_min_partition_id = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN v_sql := 'SELECT * FROM ONLY ' || p_parent_table || ' WHERE '||v_control||' >= '||quote_literal(v_min_partition_id)|| ' AND '||v_control||' < '||quote_literal(v_max_partition_id) ||' FOR UPDATE NOWAIT'; EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; v_current_partition_name := @extschema@.create_id_partition(p_parent_table, v_partition_id); EXECUTE 'WITH partition_data AS ( DELETE FROM ONLY '||p_parent_table||' WHERE '||v_control||' >= '||v_min_partition_id|| ' AND '||v_control||' < '||v_max_partition_id||' RETURNING *) INSERT INTO '||v_current_partition_name||' SELECT * FROM partition_data'; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; IF v_type = 'id-static' THEN PERFORM @extschema@.create_id_function(p_parent_table); END IF; RETURN v_total_rows; END $$; /* * Populate the child table(s) of a time-based partition set with old data from the original parent */ CREATE FUNCTION partition_data_time(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval interval DEFAULT NULL, p_lock_wait numeric DEFAULT 0, p_order text DEFAULT 'ASC') RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_datetime_string text; v_current_partition_name text; v_max_partition_timestamp timestamp; v_last_partition text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_min_partition_timestamp timestamp; v_part_interval interval; v_partition_timestamp timestamp[]; v_rowcount bigint; v_sql text; v_start_control timestamp; v_time_position int; v_total_rows bigint := 0; v_type text; BEGIN SELECT type , part_interval::interval , control , datetime_string , last_partition INTO v_type , v_part_interval , v_control , v_datetime_string , v_last_partition FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic' OR type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF p_batch_interval IS NULL OR p_batch_interval > v_part_interval THEN p_batch_interval := v_part_interval; END IF; FOR i IN 1..p_batch_count LOOP IF p_order = 'ASC' THEN EXECUTE 'SELECT min('||v_control||') FROM ONLY '||p_parent_table INTO v_start_control; ELSIF p_order = 'DESC' THEN EXECUTE 'SELECT max('||v_control||') FROM ONLY '||p_parent_table INTO v_start_control; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; IF v_start_control IS NULL THEN RETURN 0; END IF; IF v_type = 'time-static' OR v_type = 'time-dynamic' THEN CASE WHEN v_part_interval = '15 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '15min'::interval * floor(date_part('minute', v_start_control) / 15.0); WHEN v_part_interval = '30 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '30min'::interval * floor(date_part('minute', v_start_control) / 30.0); WHEN v_part_interval = '1 hour' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control); WHEN v_part_interval = '1 day' THEN v_min_partition_timestamp := date_trunc('day', v_start_control); WHEN v_part_interval = '1 week' THEN v_min_partition_timestamp := date_trunc('week', v_start_control); WHEN v_part_interval = '1 month' THEN v_min_partition_timestamp := date_trunc('month', v_start_control); WHEN v_part_interval = '3 months' THEN v_min_partition_timestamp := date_trunc('quarter', v_start_control); WHEN v_part_interval = '1 year' THEN v_min_partition_timestamp := date_trunc('year', v_start_control); END CASE; ELSIF v_type = 'time-custom' THEN -- Keep going backwards, checking if the time interval encompases the current v_start_control value v_time_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; v_min_partition_timestamp := to_timestamp(substring(v_last_partition from v_time_position), v_datetime_string); v_max_partition_timestamp := v_min_partition_timestamp + v_part_interval; LOOP IF v_start_control >= v_min_partition_timestamp AND v_start_control < v_max_partition_timestamp THEN EXIT; ELSE v_max_partition_timestamp := v_min_partition_timestamp; BEGIN v_min_partition_timestamp := v_min_partition_timestamp - v_part_interval; EXCEPTION WHEN datetime_field_overflow THEN RAISE EXCEPTION 'Attempted partition time interval is outside PostgreSQL''s supported time range. Unable to create partition with interval before timestamp % ', v_min_partition_interval; END; END IF; END LOOP; END IF; v_partition_timestamp := ARRAY[v_min_partition_timestamp]; IF p_order = 'ASC' THEN IF (v_start_control + p_batch_interval) >= (v_min_partition_timestamp + v_part_interval) THEN v_max_partition_timestamp := v_min_partition_timestamp + v_part_interval; ELSE v_max_partition_timestamp := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_timestamp := v_min_partition_timestamp + v_part_interval; -- Make sure minimum doesn't underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_timestamp THEN v_min_partition_timestamp = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN v_sql := 'SELECT * FROM ONLY ' || p_parent_table || ' WHERE '||v_control||' >= '||quote_literal(v_min_partition_timestamp)|| ' AND '||v_control||' < '||quote_literal(v_max_partition_timestamp) ||' FOR UPDATE NOWAIT'; EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; v_current_partition_name := @extschema@.create_time_partition(p_parent_table, v_partition_timestamp); EXECUTE 'WITH partition_data AS ( DELETE FROM ONLY '||p_parent_table||' WHERE '||v_control||' >= '||quote_literal(v_min_partition_timestamp)|| ' AND '||v_control||' < '||quote_literal(v_max_partition_timestamp)||' RETURNING *) INSERT INTO '||v_current_partition_name||' SELECT * FROM partition_data'; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; IF v_type = 'time-static' THEN PERFORM @extschema@.create_time_function(p_parent_table); END IF; RETURN v_total_rows; END $$; /* * Function to turn a table into the parent of a partition set */ CREATE OR REPLACE FUNCTION 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_start_partition text DEFAULT NULL , p_jobmon boolean DEFAULT true , p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_base_timestamp timestamp; v_count int := 1; v_datetime_string text; v_id_interval bigint; v_job_id bigint; v_jobmon_schema text; v_last_partition_name text; v_old_search_path text; v_partition_time timestamp; v_partition_time_array timestamp[]; v_partition_id bigint[]; v_max bigint; v_notnull boolean; v_start_time timestamp; v_starting_partition_id bigint; v_step_id bigint; v_step_overflow_id bigint; v_tablename text; v_time_interval interval; BEGIN IF position('.' in p_parent_table) = 0 THEN RAISE EXCEPTION 'Parent table must be schema qualified'; END IF; SELECT tablename INTO v_tablename FROM pg_tables WHERE schemaname || '.' || tablename = p_parent_table; IF v_tablename IS NULL THEN RAISE EXCEPTION 'Please create given parent table first: %', p_parent_table; END IF; SELECT attnotnull INTO v_notnull FROM pg_attribute WHERE attrelid = p_parent_table::regclass AND attname = p_control; IF v_notnull = false OR v_notnull IS NULL THEN RAISE EXCEPTION 'Control column (%) for parent table (%) must be NOT NULL', p_control, p_parent_table; END IF; IF NOT @extschema@.check_partition_type(p_type) THEN RAISE EXCEPTION '% is not a valid partitioning type', p_type; END IF; IF p_type = 'time-custom' AND @extschema@.check_version('9.2.0') IS FALSE THEN RAISE EXCEPTION 'The "time-custom" type requires a minimum PostgreSQL version of 9.2.0'; END IF; EXECUTE 'LOCK TABLE '||p_parent_table||' IN ACCESS EXCLUSIVE MODE'; IF p_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN SETUP PARENT: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating initial partitions on new parent table: '||p_parent_table); END IF; IF p_type = 'time-static' OR p_type = 'time-dynamic' OR p_type = 'time-custom' 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 <> 'time-custom' THEN RAISE EXCEPTION 'Must use a predefined time interval if not using type "time-custom". See documentation.'; 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::timestamp, 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))::timestamp; 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, type, part_interval, control, premake, constraint_cols, datetime_string, jobmon) VALUES (p_parent_table, p_type, v_time_interval, p_control, p_premake, p_constraint_cols, v_datetime_string, p_jobmon); v_last_partition_name := @extschema@.create_time_partition(p_parent_table, v_partition_time_array); -- Doing separate update because create function requires in config table last_partition to be set UPDATE @extschema@.part_config SET last_partition = v_last_partition_name WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Time partitions premade: '||p_premake); END IF; END IF; IF p_type = 'id-static' OR p_type = 'id-dynamic' THEN v_id_interval := p_interval::bigint; IF v_id_interval <= 1 THEN RAISE EXCEPTION 'Interval for serial partitioning must be greater than 1'; END IF; -- 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 EXECUTE 'SELECT COALESCE('||quote_nullable(p_start_partition::bigint)||', max('||p_control||')::bigint, 0) FROM '||p_parent_table||' LIMIT 1' INTO v_max; 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_append(v_partition_id, (v_starting_partition_id - v_id_interval*i)); END IF; v_partition_id = array_append(v_partition_id, (v_id_interval*i) + v_starting_partition_id); END LOOP; INSERT INTO @extschema@.part_config (parent_table, type, part_interval, control, premake, constraint_cols, jobmon) VALUES (p_parent_table, p_type, v_id_interval, p_control, p_premake, p_constraint_cols, p_jobmon); v_last_partition_name := @extschema@.create_id_partition(p_parent_table, v_partition_id); -- Doing separate update because create function needs parent table in config table for apply_grants() UPDATE @extschema@.part_config SET last_partition = v_last_partition_name WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'ID partitions premade: '||p_premake); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating partition function'); END IF; IF p_type = 'time-static' OR p_type = 'time-dynamic' OR p_type = 'time-custom' THEN PERFORM @extschema@.create_time_function(p_parent_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Time function created'); END IF; ELSIF p_type = 'id-static' OR p_type = 'id-dynamic' THEN PERFORM @extschema@.create_id_function(p_parent_table); 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); 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; EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE PARENT: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''Partition creation for table '||p_parent_table||' failed'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Create the trigger function for the parent table of an id-based partition set */ CREATE OR REPLACE FUNCTION create_id_function(p_parent_table text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_count int; v_current_partition_name text; v_current_partition_id bigint; v_datetime_string text; v_final_partition_id bigint; v_function_name text; v_job_id bigint; v_jobmon text; v_jobmon_schema text; v_last_partition text; v_max bigint; v_next_partition_id bigint; v_next_partition_name text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_part_interval bigint; v_premake int; v_prev_partition_id bigint; v_prev_partition_name text; v_step_id bigint; v_trig_func text; v_type text; BEGIN SELECT type , part_interval::bigint , control , premake , last_partition , jobmon INTO v_type , v_part_interval , v_control , v_premake , v_last_partition , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE FUNCTION: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating partition function for table '||p_parent_table); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_function_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, '_part_trig_func', FALSE); IF v_type = 'id-static' THEN EXECUTE 'SELECT COALESCE(max('||v_control||'), 0) FROM '||p_parent_table INTO v_max; v_current_partition_id = v_max - (v_max % v_part_interval); v_next_partition_id := v_current_partition_id + v_part_interval; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_current_partition_id::text, TRUE); v_trig_func := 'CREATE OR REPLACE FUNCTION '||v_function_name||'() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_current_partition_id bigint; v_last_partition text := '||quote_literal(v_last_partition)||'; v_id_position int; v_next_partition_id bigint; v_next_partition_name text; BEGIN IF TG_OP = ''INSERT'' THEN IF NEW.'||v_control||' >= '||v_current_partition_id||' AND NEW.'||v_control||' < '||v_next_partition_id|| ' THEN INSERT INTO '||v_current_partition_name||' VALUES (NEW.*); '; FOR i IN 1..v_premake LOOP v_prev_partition_id := v_current_partition_id - (v_part_interval * i); v_next_partition_id := v_current_partition_id + (v_part_interval * i); v_final_partition_id := v_next_partition_id + v_part_interval; v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_prev_partition_id::text, TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_next_partition_id::text, TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles edge case of changing premake immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_prev_partition_name; IF v_count > 0 THEN -- Only handle previous partitions if they're starting above zero IF v_prev_partition_id >= 0 THEN v_trig_func := v_trig_func ||' ELSIF NEW.'||v_control||' >= '||v_prev_partition_id||' AND NEW.'||v_control||' < '||v_prev_partition_id + v_part_interval|| ' THEN INSERT INTO '||v_prev_partition_name||' VALUES (NEW.*); '; END IF; END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_next_partition_name; IF v_count > 0 THEN v_trig_func := v_trig_func ||' ELSIF NEW.'||v_control||' >= '||v_next_partition_id||' AND NEW.'||v_control||' < '||v_final_partition_id|| ' THEN INSERT INTO '||v_next_partition_name||' VALUES (NEW.*); '; END IF; END LOOP; v_trig_func := v_trig_func ||' ELSE RETURN NEW; END IF; v_current_partition_id := NEW.'||v_control||' - (NEW.'||v_control||' % '||v_part_interval||'); IF (NEW.'||v_control||' % '||v_part_interval||') > ('||v_part_interval||' / 2) THEN v_id_position := (length(v_last_partition) - position(''p_'' in reverse(v_last_partition))) + 2; v_next_partition_id := (substring(v_last_partition from v_id_position)::bigint) + '||v_part_interval||'; WHILE ((v_next_partition_id - v_current_partition_id) / '||v_part_interval||') <= '||v_premake||' LOOP v_next_partition_name := @extschema@.create_id_partition('||quote_literal(p_parent_table)||', ARRAY[v_next_partition_id]); UPDATE @extschema@.part_config SET last_partition = v_next_partition_name WHERE parent_table = '||quote_literal(p_parent_table)||'; PERFORM @extschema@.create_id_function('||quote_literal(p_parent_table)||'); PERFORM @extschema@.apply_constraints('||quote_literal(p_parent_table)||'); v_next_partition_id := v_next_partition_id + '||v_part_interval||'; END LOOP; END IF; END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for current id interval: '||v_current_partition_id||' to '||v_final_partition_id-1); END IF; ELSIF v_type = 'id-dynamic' THEN -- The return inside the partition creation check is there to keep really high ID values from creating new partitions. v_trig_func := 'CREATE OR REPLACE FUNCTION '||v_function_name||'() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_current_partition_id bigint; v_current_partition_name text; v_id_position int; v_last_partition text := '||quote_literal(v_last_partition)||'; v_last_partition_id bigint; v_next_partition_id bigint; v_next_partition_name text; BEGIN IF TG_OP = ''INSERT'' THEN v_current_partition_id := NEW.'||v_control||' - (NEW.'||v_control||' % '||v_part_interval||'); v_current_partition_name := @extschema@.check_name_length('''||v_parent_tablename||''', '''||v_parent_schema||''', v_current_partition_id::text, TRUE); IF (NEW.'||v_control||' % '||v_part_interval||') > ('||v_part_interval||' / 2) THEN v_id_position := (length(v_last_partition) - position(''p_'' in reverse(v_last_partition))) + 2; v_last_partition_id = substring(v_last_partition from v_id_position)::bigint; v_next_partition_id := v_last_partition_id + '||v_part_interval||'; IF NEW.'||v_control||' >= v_next_partition_id THEN RETURN NEW; END IF; WHILE ((v_next_partition_id - v_current_partition_id) / '||v_part_interval||') <= '||v_premake||' LOOP v_next_partition_name := @extschema@.create_id_partition('||quote_literal(p_parent_table)||', ARRAY[v_next_partition_id]); IF v_next_partition_name IS NOT NULL THEN UPDATE @extschema@.part_config SET last_partition = v_next_partition_name WHERE parent_table = '||quote_literal(p_parent_table)||'; PERFORM @extschema@.create_id_function('||quote_literal(p_parent_table)||'); PERFORM @extschema@.apply_constraints('||quote_literal(p_parent_table)||'); END IF; v_next_partition_id := v_next_partition_id + '||v_part_interval||'; END LOOP; END IF; SELECT count(*) INTO v_count FROM pg_tables WHERE schemaname ||''.''|| tablename = v_current_partition_name; IF v_count > 0 THEN EXECUTE ''INSERT INTO ''||v_current_partition_name||'' VALUES($1.*)'' USING NEW; ELSE RETURN NEW; END IF; END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for dynamic id table: '||p_parent_table); END IF; ELSE RAISE EXCEPTION 'ERROR: Invalid id partitioning type given: %', v_type; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE FUNCTION: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''Partition function maintenance for table '||p_parent_table||' failed'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to undo partitioning. * Will actually work on any parent/child table set, not just ones created by pg_partman. */ CREATE OR REPLACE FUNCTION undo_partition(p_parent_table text, p_batch_count int DEFAULT 1, p_keep_table boolean DEFAULT true, p_jobmon boolean DEFAULT true) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_batch_loop_count bigint := 0; v_child_count bigint; v_child_table text; v_copy_sql text; v_function_name text; v_job_id bigint; v_jobmon_schema text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_part_interval interval; v_rowcount bigint; v_step_id bigint; v_total bigint := 0; v_trig_name text; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition already running.'; RETURN 0; END IF; IF p_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Undoing partitioning for table '||p_parent_table); END IF; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables and stop new id partitions from being made. SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, '_part_trig_func', FALSE); EXECUTE 'DROP TRIGGER IF EXISTS '||v_trig_name||' ON '||p_parent_table; EXECUTE 'DROP FUNCTION IF EXISTS '||v_function_name||'()'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); END IF; WHILE v_batch_loop_count < p_batch_count LOOP SELECT n.nspname||'.'||c.relname INTO v_child_table FROM pg_inherits i JOIN pg_class c ON i.inhrelid = c.oid JOIN pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC; EXIT WHEN v_child_table IS NULL; EXECUTE 'SELECT count(*) FROM '||v_child_table INTO v_child_count; IF v_child_count = 0 THEN -- No rows left in this child table. Remove from partition set. EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF p_keep_table = false THEN EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table DROPPED. Moved '||coalesce(v_rowcount, 0)||' rows to parent'); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table UNINHERITED, not DROPPED. Copied '||coalesce(v_rowcount, 0)||' rows to parent'); END IF; END IF; v_undo_count := v_undo_count + 1; CONTINUE; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing child partition: '||v_child_table); END IF; v_copy_sql := 'INSERT INTO '||p_parent_table||' SELECT * FROM '||v_child_table; EXECUTE v_copy_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF p_keep_table = false THEN EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table DROPPED. Moved '||v_rowcount||' rows to parent'); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table UNINHERITED, not DROPPED. Copied '||v_rowcount||' rows to parent'); END IF; END IF; v_batch_loop_count := v_batch_loop_count + 1; v_undo_count := v_undo_count + 1; END LOOP; IF v_undo_count = 0 THEN -- FOR loop never ran, so there's no child tables left. DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman (if it existed)'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) from % child table(s) to the parent: %', v_total, v_undo_count, p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', 'Copied '||v_total||' row(s) from '||v_undo_count||' child table(s) to the parent'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_total; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN UNDO PARTITIONING: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''Partition function maintenance for table '||p_parent_table||' failed'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; DO $$ DECLARE v_row record; BEGIN FOR v_row IN SELECT statement FROM partman_preserve_privs_temp LOOP IF v_row.statement IS NOT NULL THEN EXECUTE v_row.statement; END IF; END LOOP; END $$; DROP TABLE partman_preserve_privs_temp; pg_partman-4.6.0/updates/pg_partman--1.6.1--1.7.0.sql000066400000000000000000002265141412761706100214420ustar00rootroot00000000000000-- New configuration option to allow serial partitioning to use run_maintenance() instead of creating next partition via trigger. -- Use "p_use_run_maintenance" argument to create_parent() to set this during partition creation. -- part_config table has new boolean column "use_run_maintenance" -- Serial/ID based partitioning defaults to FALSE. This means serial partitioning uses the parent partition trigger function to make new child partitions when the current one reaches 50% of its configured capacity (the same way it used to work). If set to TRUE, then you must schedule run_maintenance() to run often enough to keep up with your insertion rate. Otherwise rows will get inserted to the parent table. -- Time based partitioning defaults to TRUE and config values for using run_maintenance cannot be set to false. All time-based partitioning still requires run_maintenance() for creating new child tables. -- Existing partition sets have their config table values set to the defaults above. -- If you'd like to change an existing serial partition set to use run_maintenance instead of the trigger, update the "use_run_maintenance" column in part_config to set it to TRUE for that parent table. You must then run the "create_id_function()" function giving it a parameter of the schema qualified parent table of the set. This will remove the code in the trigger that automatically makes new child tables. -- Ex: SELECT partman.create_id_function('parent_schema.parent_table'); -- reapply_indexes.py can now handle too long or duplicate index names. Please see docs for how this is handled since it can change index naming patterns (Github Issue #21). -- Fixed partition_data_id() & partition_data_time() to properly return the number of rows moved when the parent table is empty before the batch limit is reached (Github Issue #22). -- Fixed creation of new child partition tables not working when parent tables had OIDs turned on. (Github Issue #20) -- Fixed check_unique_constraint.py to avoid index scans and check underlying table data. Option added to try and allow index scans if desired. -- Fixed reapply_constraints.py & reapply_indexes.py to properly run jobs in parallel. -- Ensure an analyze is run on parent table of a set after any child table is created so that constraint exclusion works properly for all child tables. -- Ensure an analyze is run on a child table whenever additional column constraints are automatically added. Also analyze partition set if reapply_constraints.py is run. -- Added pgtap tests that ensure the partitioning functions are returning the proper number of rows. -- Added pgtap tests for new features in reapply_index.py ALTER TABLE @extschema@.part_config ADD COLUMN use_run_maintenance BOOLEAN NOT NULL DEFAULT true; UPDATE @extschema@.part_config SET use_run_maintenance = false WHERE type = 'id-static' OR type = 'id-dynamic'; CREATE TEMP TABLE partman_preserve_privs_temp (statement text); INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_parent(text, text, text, text, text[], int, boolean, text, boolean, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_parent'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.apply_constraints(text, text, boolean, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'apply_constraints'; DROP FUNCTION create_parent(text, text, text, text, text[], int, text, boolean, boolean); DROP FUNCTION apply_constraints(text, text, boolean); /* * Trigger function to enforce that time based partitioning must use run_maintenance() */ CREATE FUNCTION time_partition_maintenance_true_trig() RETURNS trigger LANGUAGE plpgsql SECURITY DEFINER AS $$ BEGIN IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN IF NEW.type IN ('time-static', 'time-dynamic', 'time-custom') AND NEW.use_run_maintenance = FALSE THEN RAISE EXCEPTION 'use_run_maintenance cannot be set to FALSE for time based partitioning'; END IF; END IF; RETURN NEW; END $$; CREATE TRIGGER time_partition_maintenance_true_trig BEFORE INSERT OR UPDATE OF type, use_run_maintenance ON @extschema@.part_config FOR EACH ROW EXECUTE PROCEDURE @extschema@.time_partition_maintenance_true_trig(); /* * Populate the child table(s) of an id-based partition set with old data from the original parent */ CREATE OR REPLACE FUNCTION partition_data_id(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval int DEFAULT NULL, p_lock_wait numeric DEFAULT 0, p_order text DEFAULT 'ASC') RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_current_partition_name text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_id bigint; v_min_partition_id bigint; v_part_interval bigint; v_partition_id bigint[]; v_rowcount bigint; v_sql text; v_start_control bigint; v_total_rows bigint := 0; v_type text; BEGIN SELECT type , part_interval::bigint , control INTO v_type , v_part_interval , v_control FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF p_batch_interval IS NULL OR p_batch_interval > v_part_interval THEN p_batch_interval := v_part_interval; END IF; FOR i IN 1..p_batch_count LOOP IF p_order = 'ASC' THEN EXECUTE 'SELECT min('||v_control||') FROM ONLY '||p_parent_table INTO v_start_control; IF v_start_control IS NULL THEN EXIT; END IF; v_min_partition_id = v_start_control - (v_start_control % v_part_interval); v_partition_id := ARRAY[v_min_partition_id]; -- Check if custom batch interval overflows current partition maximum IF (v_start_control + p_batch_interval) >= (v_min_partition_id + v_part_interval) THEN v_max_partition_id := v_min_partition_id + v_part_interval; ELSE v_max_partition_id := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN EXECUTE 'SELECT max('||v_control||') FROM ONLY '||p_parent_table INTO v_start_control; IF v_start_control IS NULL THEN EXIT; END IF; v_min_partition_id = v_start_control - (v_start_control % v_part_interval); -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_id := v_min_partition_id + v_part_interval; v_partition_id := ARRAY[v_min_partition_id]; -- Make sure minimum doesn't underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_id THEN v_min_partition_id = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN v_sql := 'SELECT * FROM ONLY ' || p_parent_table || ' WHERE '||v_control||' >= '||quote_literal(v_min_partition_id)|| ' AND '||v_control||' < '||quote_literal(v_max_partition_id) ||' FOR UPDATE NOWAIT'; EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; v_current_partition_name := @extschema@.create_id_partition(p_parent_table, v_partition_id); EXECUTE 'WITH partition_data AS ( DELETE FROM ONLY '||p_parent_table||' WHERE '||v_control||' >= '||v_min_partition_id|| ' AND '||v_control||' < '||v_max_partition_id||' RETURNING *) INSERT INTO '||v_current_partition_name||' SELECT * FROM partition_data'; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; IF v_type = 'id-static' THEN PERFORM @extschema@.create_id_function(p_parent_table); END IF; RETURN v_total_rows; END $$; /* * Populate the child table(s) of a time-based partition set with old data from the original parent */ CREATE OR REPLACE FUNCTION partition_data_time(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval interval DEFAULT NULL, p_lock_wait numeric DEFAULT 0, p_order text DEFAULT 'ASC') RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_datetime_string text; v_current_partition_name text; v_max_partition_timestamp timestamp; v_last_partition text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_min_partition_timestamp timestamp; v_part_interval interval; v_partition_timestamp timestamp[]; v_rowcount bigint; v_sql text; v_start_control timestamp; v_time_position int; v_total_rows bigint := 0; v_type text; BEGIN SELECT type , part_interval::interval , control , datetime_string , last_partition INTO v_type , v_part_interval , v_control , v_datetime_string , v_last_partition FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic' OR type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF p_batch_interval IS NULL OR p_batch_interval > v_part_interval THEN p_batch_interval := v_part_interval; END IF; FOR i IN 1..p_batch_count LOOP IF p_order = 'ASC' THEN EXECUTE 'SELECT min('||v_control||') FROM ONLY '||p_parent_table INTO v_start_control; ELSIF p_order = 'DESC' THEN EXECUTE 'SELECT max('||v_control||') FROM ONLY '||p_parent_table INTO v_start_control; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; IF v_start_control IS NULL THEN EXIT; END IF; IF v_type = 'time-static' OR v_type = 'time-dynamic' THEN CASE WHEN v_part_interval = '15 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '15min'::interval * floor(date_part('minute', v_start_control) / 15.0); WHEN v_part_interval = '30 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '30min'::interval * floor(date_part('minute', v_start_control) / 30.0); WHEN v_part_interval = '1 hour' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control); WHEN v_part_interval = '1 day' THEN v_min_partition_timestamp := date_trunc('day', v_start_control); WHEN v_part_interval = '1 week' THEN v_min_partition_timestamp := date_trunc('week', v_start_control); WHEN v_part_interval = '1 month' THEN v_min_partition_timestamp := date_trunc('month', v_start_control); WHEN v_part_interval = '3 months' THEN v_min_partition_timestamp := date_trunc('quarter', v_start_control); WHEN v_part_interval = '1 year' THEN v_min_partition_timestamp := date_trunc('year', v_start_control); END CASE; ELSIF v_type = 'time-custom' THEN -- Keep going backwards, checking if the time interval encompases the current v_start_control value v_time_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; v_min_partition_timestamp := to_timestamp(substring(v_last_partition from v_time_position), v_datetime_string); v_max_partition_timestamp := v_min_partition_timestamp + v_part_interval; LOOP IF v_start_control >= v_min_partition_timestamp AND v_start_control < v_max_partition_timestamp THEN EXIT; ELSE v_max_partition_timestamp := v_min_partition_timestamp; BEGIN v_min_partition_timestamp := v_min_partition_timestamp - v_part_interval; EXCEPTION WHEN datetime_field_overflow THEN RAISE EXCEPTION 'Attempted partition time interval is outside PostgreSQL''s supported time range. Unable to create partition with interval before timestamp % ', v_min_partition_interval; END; END IF; END LOOP; END IF; v_partition_timestamp := ARRAY[v_min_partition_timestamp]; IF p_order = 'ASC' THEN IF (v_start_control + p_batch_interval) >= (v_min_partition_timestamp + v_part_interval) THEN v_max_partition_timestamp := v_min_partition_timestamp + v_part_interval; ELSE v_max_partition_timestamp := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_timestamp := v_min_partition_timestamp + v_part_interval; -- Make sure minimum doesn't underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_timestamp THEN v_min_partition_timestamp = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN v_sql := 'SELECT * FROM ONLY ' || p_parent_table || ' WHERE '||v_control||' >= '||quote_literal(v_min_partition_timestamp)|| ' AND '||v_control||' < '||quote_literal(v_max_partition_timestamp) ||' FOR UPDATE NOWAIT'; EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; v_current_partition_name := @extschema@.create_time_partition(p_parent_table, v_partition_timestamp); EXECUTE 'WITH partition_data AS ( DELETE FROM ONLY '||p_parent_table||' WHERE '||v_control||' >= '||quote_literal(v_min_partition_timestamp)|| ' AND '||v_control||' < '||quote_literal(v_max_partition_timestamp)||' RETURNING *) INSERT INTO '||v_current_partition_name||' SELECT * FROM partition_data'; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; IF v_type = 'time-static' THEN PERFORM @extschema@.create_time_function(p_parent_table); END IF; RETURN v_total_rows; END $$; /* * Function to manage pre-creation of the next partitions in a time-based partition set. * Also manages dropping old partitions if the retention option is set. */ CREATE OR REPLACE FUNCTION run_maintenance(p_jobmon BOOLEAN DEFAULT true) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_create_count int := 0; v_current_partition text; v_current_partition_id bigint; v_current_partition_timestamp timestamp; v_datetime_string text; v_drop_count int := 0; v_id_position int; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_id bigint; v_last_partition_timestamp timestamp; v_next_partition_id bigint; v_next_partition_timestamp timestamp; v_old_search_path text; v_premade_count int; v_quarter text; v_step_id bigint; v_step_overflow_id bigint; v_step_serial_id bigint; v_row record; v_tablename text; v_time_position int; v_year text; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman maintenance already running.'; RETURN; END IF; 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' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN RUN MAINTENANCE'); v_step_id := add_step(v_job_id, 'Running maintenance loop'); END IF; FOR v_row IN SELECT parent_table , type , part_interval , control , premake , datetime_string , last_partition , undo_in_progress FROM @extschema@.part_config WHERE use_run_maintenance = true LOOP CONTINUE WHEN v_row.undo_in_progress; -- Double check that last created partition exists IF v_row.last_partition IS NOT NULL THEN SELECT tablename INTO v_tablename FROM pg_tables WHERE schemaname || '.' || tablename = v_row.last_partition; IF v_tablename IS NULL THEN RAISE EXCEPTION 'Last known partition table missing for parent table %. Unable to determine next partition in sequence.', v_row.parent_table; END IF; ELSE RAISE EXCEPTION 'Last known partition missing from config table for parent table %.', p_parent_table; END IF; IF v_row.type = 'time-static' OR v_row.type = 'time-dynamic' OR v_row.type = 'time-custom' THEN IF v_row.type = 'time-static' OR v_row.type = 'time-dynamic' THEN CASE WHEN v_row.part_interval::interval = '15 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_row.part_interval::interval = '30 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_row.part_interval::interval = '1 hour' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_row.part_interval::interval = '1 day' THEN v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_row.part_interval::interval = '1 week' THEN v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_row.part_interval::interval = '1 month' THEN v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_row.part_interval::interval = '3 months' THEN v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_row.part_interval::interval = '1 year' THEN v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; ELSIF v_row.type = 'time-custom' THEN SELECT child_table INTO v_current_partition FROM @extschema@.custom_time_partitions WHERE parent_table = v_row.parent_table AND partition_range @> CURRENT_TIMESTAMP; IF v_current_partition IS NULL THEN RAISE EXCEPTION 'Current time partition missing from custom_time_partitions config table for table % and timestamp %', CURRENT_TIMESTAMP, v_row.parent_table; END IF; v_time_position := (length(v_current_partition) - position('p_' in reverse(v_current_partition))) + 2; v_current_partition_timestamp := to_timestamp(substring(v_current_partition from v_time_position), v_row.datetime_string); END IF; v_time_position := (length(v_row.last_partition) - position('p_' in reverse(v_row.last_partition))) + 2; IF v_row.part_interval::interval <> '3 months' OR (v_row.part_interval::interval = '3 months' AND v_row.type = 'time-custom') THEN v_last_partition_timestamp := to_timestamp(substring(v_row.last_partition from v_time_position), v_row.datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(v_row.last_partition from v_time_position), 'q', 1); v_quarter := split_part(substring(v_row.last_partition from v_time_position), 'q', 2); CASE WHEN v_quarter = '1' THEN v_last_partition_timestamp := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_last_partition_timestamp := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_last_partition_timestamp := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_last_partition_timestamp := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; -- Check and see how many premade partitions there are. v_premade_count = round(EXTRACT('epoch' FROM age(v_last_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.part_interval::interval)); v_next_partition_timestamp := v_last_partition_timestamp; -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed. WHILE v_premade_count < v_row.premake LOOP BEGIN v_next_partition_timestamp := v_next_partition_timestamp + v_row.part_interval::interval; EXCEPTION WHEN datetime_field_overflow THEN v_premade_count := v_row.premake; -- do this so it can exit the premake check loop and continue in the outer for loop IF v_jobmon_schema IS NOT NULL THEN 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 skippd for parent table '||v_partition_time); END IF; RAISE WARNING 'Attempted partition time interval is outside PostgreSQL''s supported time range. Child partition creation skipped for parent table %', v_row.parent_table; CONTINUE; END; v_last_partition := @extschema@.create_time_partition(v_row.parent_table, ARRAY[v_next_partition_timestamp]); IF v_last_partition IS NOT NULL THEN UPDATE @extschema@.part_config SET last_partition = v_last_partition WHERE parent_table = v_row.parent_table; END IF; v_create_count := v_create_count + 1; IF v_row.type = 'time-static' THEN PERFORM @extschema@.create_time_function(v_row.parent_table); END IF; -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(v_row.parent_table); v_premade_count = round(EXTRACT('epoch' FROM age(v_next_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.part_interval::interval)); END LOOP; ELSIF v_row.type = 'id-static' OR v_row.type ='id-dynamic' THEN -- EXECUTE 'SELECT max('||v_row.control||') - ('||v_row.control||' % '||v_row.part_interval::int||') FROM '||v_row.parent_table INTO v_current_partition_id; EXECUTE 'SELECT '||v_row.control||' - ('||v_row.control||' % '||v_row.part_interval::int||') FROM '||v_row.parent_table||' WHERE '||v_row.control||' = (SELECT max('||v_row.control||') FROM '||v_row.parent_table||')' INTO v_current_partition_id; v_id_position := (length(v_row.last_partition) - position('p_' in reverse(v_row.last_partition))) + 2; v_last_partition_id = substring(v_row.last_partition from v_id_position)::bigint; -- This catches if there's invalid data in a parent table set that's outside all child table ranges. IF v_last_partition_id < v_current_partition_id THEN IF v_jobmon_schema IS NOT NULL THEN v_step_serial_id := add_step(v_job_id, 'Found inconsistent data in serial partition set.'); PERFORM update_step(v_step_serial_id, 'CRITICAL', 'Child partition creation skipped for parent table '||v_row.parent_table||'. Current max serial id value ('||v_current_partition_id||') is greater than the id range covered by the last partition created ('||v_row.last_partition||'). Run check_parent() to find possible cause.'); END IF; RAISE WARNING 'Child partition creation skipped for parent table %. Found inconsistent data in serial partition set. Current max serial id value (%) is greater than the id range covered by the last partition created (%). Run check_parent() to find possible cause.', v_row.parent_table, v_current_partition_id, v_row.last_partition; CONTINUE; END IF; v_next_partition_id := v_last_partition_id + v_row.part_interval::bigint; WHILE ((v_next_partition_id - v_current_partition_id) / v_row.part_interval::bigint) <= v_row.premake LOOP v_last_partition := @extschema@.create_id_partition(v_row.parent_table, ARRAY[v_next_partition_id]); IF v_last_partition IS NOT NULL THEN UPDATE @extschema@.part_config SET last_partition = v_last_partition WHERE parent_table = v_row.parent_table; PERFORM @extschema@.create_id_function(v_row.parent_table); PERFORM @extschema@.apply_constraints(v_row.parent_table); END IF; v_next_partition_id := v_next_partition_id + v_row.part_interval::bigint; END LOOP; END IF; -- end main IF check for time or id END LOOP; -- end of creation loop -- Manage dropping old partitions if retention option is set FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND (type = 'time-static' OR type = 'time-dynamic' OR type = 'time-custom') LOOP v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); END LOOP; FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND (type = 'id-static' OR type = 'id-dynamic') LOOP v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Partition maintenance finished. '||v_create_count||' partitions made. '||v_drop_count||' partitions dropped.'); IF v_step_overflow_id IS NOT NULL OR v_step_serial_id IS NOT NULL THEN PERFORM fail_job(v_job_id); ELSE PERFORM close_job(v_job_id); END IF; EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN RUN MAINTENANCE'')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to turn a table into the parent of a partition set */ CREATE FUNCTION 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_use_run_maintenance boolean DEFAULT NULL , p_start_partition text DEFAULT NULL , p_jobmon boolean DEFAULT true , p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_base_timestamp timestamp; v_count int := 1; v_datetime_string text; v_id_interval bigint; v_job_id bigint; v_jobmon_schema text; v_last_partition_name text; v_old_search_path text; v_partition_time timestamp; v_partition_time_array timestamp[]; v_partition_id bigint[]; v_max bigint; v_notnull boolean; v_run_maint boolean; v_start_time timestamp; v_starting_partition_id bigint; v_step_id bigint; v_step_overflow_id bigint; v_tablename text; v_time_interval interval; BEGIN IF position('.' in p_parent_table) = 0 THEN RAISE EXCEPTION 'Parent table must be schema qualified'; END IF; SELECT tablename INTO v_tablename FROM pg_tables WHERE schemaname || '.' || tablename = p_parent_table; IF v_tablename IS NULL THEN RAISE EXCEPTION 'Please create given parent table first: %', p_parent_table; END IF; SELECT attnotnull INTO v_notnull FROM pg_attribute WHERE attrelid = p_parent_table::regclass AND attname = p_control; IF v_notnull = false OR v_notnull IS NULL THEN RAISE EXCEPTION 'Control column (%) for parent table (%) must be NOT NULL', p_control, p_parent_table; END IF; IF NOT @extschema@.check_partition_type(p_type) THEN RAISE EXCEPTION '% is not a valid partitioning type', p_type; END IF; IF p_type = 'time-custom' AND @extschema@.check_version('9.2.0') IS FALSE THEN RAISE EXCEPTION 'The "time-custom" type requires a minimum PostgreSQL version of 9.2.0'; END IF; EXECUTE 'LOCK TABLE '||p_parent_table||' IN ACCESS EXCLUSIVE MODE'; IF p_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF p_use_run_maintenance IS NOT NULL THEN IF p_use_run_maintenance IS FALSE AND (p_type = 'time-static' OR p_type = 'time-dynamic' OR p_type = 'time-custom') THEN RAISE EXCEPTION 'p_run_maintenance cannot be set to false for time based partitioning'; END IF; v_run_maint := p_use_run_maintenance; ELSIF p_type = 'time-static' OR p_type = 'time-dynamic' OR p_type = 'time-custom' THEN v_run_maint := TRUE; ELSIF p_type = 'id-static' OR p_type ='id-dynamic' THEN v_run_maint := FALSE; ELSE RAISE EXCEPTION 'use_run_maintenance value cannot be set NULL'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN SETUP PARENT: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating initial partitions on new parent table: '||p_parent_table); END IF; IF p_type = 'time-static' OR p_type = 'time-dynamic' OR p_type = 'time-custom' 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 <> 'time-custom' THEN RAISE EXCEPTION 'Must use a predefined time interval if not using type "time-custom". See documentation.'; 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::timestamp, 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))::timestamp; 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, type, part_interval, control, premake, constraint_cols, datetime_string, use_run_maintenance, jobmon) VALUES (p_parent_table, p_type, v_time_interval, p_control, p_premake, p_constraint_cols, v_datetime_string, v_run_maint, p_jobmon); v_last_partition_name := @extschema@.create_time_partition(p_parent_table, v_partition_time_array); -- Doing separate update because create function requires in config table last_partition to be set UPDATE @extschema@.part_config SET last_partition = v_last_partition_name WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Time partitions premade: '||p_premake); END IF; END IF; IF p_type = 'id-static' OR p_type = 'id-dynamic' THEN v_id_interval := p_interval::bigint; IF v_id_interval <= 1 THEN RAISE EXCEPTION 'Interval for serial partitioning must be greater than 1'; END IF; -- 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 EXECUTE 'SELECT COALESCE('||quote_nullable(p_start_partition::bigint)||', max('||p_control||')::bigint, 0) FROM '||p_parent_table||' LIMIT 1' INTO v_max; 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_append(v_partition_id, (v_starting_partition_id - v_id_interval*i)); END IF; v_partition_id = array_append(v_partition_id, (v_id_interval*i) + v_starting_partition_id); END LOOP; INSERT INTO @extschema@.part_config (parent_table, type, part_interval, control, premake, constraint_cols, use_run_maintenance, jobmon) VALUES (p_parent_table, p_type, v_id_interval, p_control, p_premake, p_constraint_cols, v_run_maint, p_jobmon); v_last_partition_name := @extschema@.create_id_partition(p_parent_table, v_partition_id); -- Doing separate update because create function needs parent table in config table for apply_grants() UPDATE @extschema@.part_config SET last_partition = v_last_partition_name WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'ID partitions premade: '||p_premake); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating partition function'); END IF; IF p_type = 'time-static' OR p_type = 'time-dynamic' OR p_type = 'time-custom' THEN PERFORM @extschema@.create_time_function(p_parent_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Time function created'); END IF; ELSIF p_type = 'id-static' OR p_type = 'id-dynamic' THEN PERFORM @extschema@.create_id_function(p_parent_table); 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); 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; EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE PARENT: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''Partition creation for table '||p_parent_table||' failed'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Create the trigger function for the parent table of an id-based partition set */ CREATE OR REPLACE FUNCTION create_id_function(p_parent_table text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_count int; v_current_partition_name text; v_current_partition_id bigint; v_datetime_string text; v_final_partition_id bigint; v_function_name text; v_job_id bigint; v_jobmon text; v_jobmon_schema text; v_last_partition text; v_max bigint; v_next_partition_id bigint; v_next_partition_name text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_part_interval bigint; v_premake int; v_prev_partition_id bigint; v_prev_partition_name text; v_run_maint boolean; v_step_id bigint; v_trig_func text; v_type text; BEGIN SELECT type , part_interval::bigint , control , premake , last_partition , use_run_maintenance , jobmon INTO v_type , v_part_interval , v_control , v_premake , v_last_partition , v_run_maint , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE FUNCTION: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating partition function for table '||p_parent_table); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_function_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, '_part_trig_func', FALSE); IF v_type = 'id-static' THEN EXECUTE 'SELECT COALESCE(max('||v_control||'), 0) FROM '||p_parent_table INTO v_max; v_current_partition_id = v_max - (v_max % v_part_interval); v_next_partition_id := v_current_partition_id + v_part_interval; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_current_partition_id::text, TRUE); v_trig_func := 'CREATE OR REPLACE FUNCTION '||v_function_name||'() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_current_partition_id bigint; v_last_partition text := '||quote_literal(v_last_partition)||'; v_id_position int; v_next_partition_id bigint; v_next_partition_name text; BEGIN IF TG_OP = ''INSERT'' THEN IF NEW.'||v_control||' >= '||v_current_partition_id||' AND NEW.'||v_control||' < '||v_next_partition_id|| ' THEN INSERT INTO '||v_current_partition_name||' VALUES (NEW.*); '; FOR i IN 1..v_premake LOOP v_prev_partition_id := v_current_partition_id - (v_part_interval * i); v_next_partition_id := v_current_partition_id + (v_part_interval * i); v_final_partition_id := v_next_partition_id + v_part_interval; v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_prev_partition_id::text, TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_next_partition_id::text, TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles edge case of changing premake immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_prev_partition_name; IF v_count > 0 THEN -- Only handle previous partitions if they're starting above zero IF v_prev_partition_id >= 0 THEN v_trig_func := v_trig_func ||' ELSIF NEW.'||v_control||' >= '||v_prev_partition_id||' AND NEW.'||v_control||' < '||v_prev_partition_id + v_part_interval|| ' THEN INSERT INTO '||v_prev_partition_name||' VALUES (NEW.*); '; END IF; END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_next_partition_name; IF v_count > 0 THEN v_trig_func := v_trig_func ||' ELSIF NEW.'||v_control||' >= '||v_next_partition_id||' AND NEW.'||v_control||' < '||v_final_partition_id|| ' THEN INSERT INTO '||v_next_partition_name||' VALUES (NEW.*);'; END IF; END LOOP; v_trig_func := v_trig_func ||' ELSE RETURN NEW; END IF;'; IF v_run_maint IS FALSE THEN v_trig_func := v_trig_func ||' v_current_partition_id := NEW.'||v_control||' - (NEW.'||v_control||' % '||v_part_interval||'); IF (NEW.'||v_control||' % '||v_part_interval||') > ('||v_part_interval||' / 2) THEN v_id_position := (length(v_last_partition) - position(''p_'' in reverse(v_last_partition))) + 2; v_next_partition_id := (substring(v_last_partition from v_id_position)::bigint) + '||v_part_interval||'; WHILE ((v_next_partition_id - v_current_partition_id) / '||v_part_interval||') <= '||v_premake||' LOOP v_next_partition_name := @extschema@.create_id_partition('||quote_literal(p_parent_table)||', ARRAY[v_next_partition_id]); UPDATE @extschema@.part_config SET last_partition = v_next_partition_name WHERE parent_table = '||quote_literal(p_parent_table)||'; PERFORM @extschema@.create_id_function('||quote_literal(p_parent_table)||'); PERFORM @extschema@.apply_constraints('||quote_literal(p_parent_table)||'); v_next_partition_id := v_next_partition_id + '||v_part_interval||'; END LOOP; END IF;'; END IF; v_trig_func := v_trig_func ||' END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for current id interval: '||v_current_partition_id||' to '||v_final_partition_id-1); END IF; ELSIF v_type = 'id-dynamic' THEN -- The return inside the partition creation check is there to keep really high ID values from creating new partitions. v_trig_func := 'CREATE OR REPLACE FUNCTION '||v_function_name||'() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_current_partition_id bigint; v_current_partition_name text; v_id_position int; v_last_partition text := '||quote_literal(v_last_partition)||'; v_last_partition_id bigint; v_next_partition_id bigint; v_next_partition_name text; BEGIN IF TG_OP = ''INSERT'' THEN v_current_partition_id := NEW.'||v_control||' - (NEW.'||v_control||' % '||v_part_interval||'); v_current_partition_name := @extschema@.check_name_length('''||v_parent_tablename||''', '''||v_parent_schema||''', v_current_partition_id::text, TRUE); SELECT count(*) INTO v_count FROM pg_tables WHERE schemaname ||''.''|| tablename = v_current_partition_name; IF v_count > 0 THEN EXECUTE ''INSERT INTO ''||v_current_partition_name||'' VALUES($1.*)'' USING NEW; ELSE RETURN NEW; END IF;'; IF v_run_maint IS FALSE THEN v_trig_func := v_trig_func ||' IF (NEW.'||v_control||' % '||v_part_interval||') > ('||v_part_interval||' / 2) THEN v_id_position := (length(v_last_partition) - position(''p_'' in reverse(v_last_partition))) + 2; v_last_partition_id = substring(v_last_partition from v_id_position)::bigint; v_next_partition_id := v_last_partition_id + '||v_part_interval||'; IF NEW.'||v_control||' >= v_next_partition_id THEN RETURN NEW; END IF; WHILE ((v_next_partition_id - v_current_partition_id) / '||v_part_interval||') <= '||v_premake||' LOOP v_next_partition_name := @extschema@.create_id_partition('||quote_literal(p_parent_table)||', ARRAY[v_next_partition_id]); IF v_next_partition_name IS NOT NULL THEN UPDATE @extschema@.part_config SET last_partition = v_next_partition_name WHERE parent_table = '||quote_literal(p_parent_table)||'; PERFORM @extschema@.create_id_function('||quote_literal(p_parent_table)||'); PERFORM @extschema@.apply_constraints('||quote_literal(p_parent_table)||'); END IF; v_next_partition_id := v_next_partition_id + '||v_part_interval||'; END LOOP; END IF;'; END IF; v_trig_func := v_trig_func ||' END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for dynamic id table: '||p_parent_table); END IF; ELSE RAISE EXCEPTION 'ERROR: Invalid id partitioning type given: %', v_type; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE FUNCTION: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''Partition function maintenance for table '||p_parent_table||' failed'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to create id partitions */ CREATE OR REPLACE FUNCTION create_id_partition (p_parent_table text, p_partition_ids bigint[]) RETURNS text LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_grantees text[]; v_hasoids boolean; v_id bigint; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_parent_tablespace text; v_part_interval bigint; v_partition_name text; v_revoke text[]; v_sql text; v_step_id bigint; v_tablename text; BEGIN SELECT control , part_interval , jobmon INTO v_control , v_part_interval , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; FOREACH v_id IN ARRAY p_partition_ids LOOP v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_id::text, TRUE); -- If child table already exists, skip creation SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_tablename IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_id||' to '||(v_id + v_part_interval)-1); END IF; v_sql := 'CREATE TABLE '||v_partition_name||' (LIKE '||p_parent_table||' INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)'; SELECT relhasoids INTO v_hasoids FROM pg_class WHERE oid::regclass = p_parent_table::regclass; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_parent_tablespace IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_partition_name||' SET TABLESPACE '||v_parent_tablespace; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' ADD CONSTRAINT '||v_tablename||'_partition_check CHECK ('||v_control||'>='||quote_literal(v_id)||' AND '||v_control||'<'||quote_literal(v_id + v_part_interval)||')'; EXECUTE 'ALTER TABLE '||v_partition_name||' INHERIT '||p_parent_table; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP EXECUTE 'GRANT '||array_to_string(v_parent_grant.types, ',')||' ON '||v_partition_name||' TO '||v_parent_grant.grantee; SELECT array_agg(r) INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE '||array_to_string(v_revoke, ',')||' ON '||v_partition_name||' FROM '||v_parent_grant.grantee||' CASCADE'; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN SELECT array_agg(r) INTO v_revoke FROM ( SELECT DISTINCT grantee::text AS r FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_partition_name EXCEPT SELECT unnest(v_grantees)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE ALL ON '||v_partition_name||' FROM '||array_to_string(v_revoke, ','); END IF; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' OWNER TO '||v_parent_owner; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); PERFORM close_job(v_job_id); END IF; END LOOP; IF v_analyze THEN EXECUTE 'ANALYZE '||p_parent_table; END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_partition_name; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE TABLE: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to create a child table in a time-based partition set */ CREATE OR REPLACE FUNCTION create_time_partition (p_parent_table text, p_partition_times timestamp[]) RETURNS text LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_grantees text[]; v_hasoids boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_partition_name text; v_partition_suffix text; v_parent_tablespace text; v_part_interval interval; v_partition_timestamp_end timestamp; v_partition_timestamp_start timestamp; v_quarter text; v_revoke text[]; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_tablename text; v_trunc_value text; v_time timestamp; v_type text; v_year text; BEGIN SELECT type , control , part_interval , jobmon INTO v_type , v_control , v_part_interval , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic' OR type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_suffix := to_char(v_time, 'YYYY'); IF v_part_interval < '1 year' AND v_part_interval <> '1 week' THEN v_partition_suffix := v_partition_suffix ||'_'|| to_char(v_time, 'MM'); IF v_part_interval < '1 month' AND v_part_interval <> '1 week' THEN v_partition_suffix := v_partition_suffix ||'_'|| to_char(v_time, 'DD'); IF v_part_interval < '1 day' THEN v_partition_suffix := v_partition_suffix || '_' || to_char(v_time, 'HH24MI'); IF v_part_interval < '1 minute' THEN v_partition_suffix := v_partition_suffix || to_char(v_time, 'SS'); END IF; -- end < minute IF END IF; -- end < day IF END IF; -- end < month IF END IF; -- end < year IF v_partition_timestamp_start := v_time; BEGIN v_partition_timestamp_end := v_time + v_part_interval; 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_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_time||' skipped'); CONTINUE; END; IF v_part_interval = '1 week' THEN v_partition_suffix := to_char(v_time, 'IYYY') || 'w' || to_char(v_time, 'IW'); END IF; -- "Q" is ignored in to_timestamp, so handle special case IF v_part_interval = '3 months' AND (v_type = 'time-static' OR v_type = 'time-dynamic') THEN v_year := to_char(v_time, 'YYYY'); v_quarter := to_char(v_time, 'Q'); v_partition_suffix := v_year || 'q' || v_quarter; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_partition_suffix, TRUE); SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_tablename IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_partition_timestamp_start||' to '||(v_partition_timestamp_end-'1sec'::interval)); END IF; v_sql := 'CREATE TABLE '||v_partition_name||' (LIKE '||p_parent_table||' INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)'; SELECT relhasoids INTO v_hasoids FROM pg_class WHERE oid::regclass = p_parent_table::regclass; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_parent_tablespace IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_partition_name||' SET TABLESPACE '||v_parent_tablespace; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' ADD CONSTRAINT '||v_tablename||'_partition_check CHECK ('||v_control||'>='||quote_literal(v_partition_timestamp_start)||' AND '||v_control||'<'||quote_literal(v_partition_timestamp_end)||')'; EXECUTE 'ALTER TABLE '||v_partition_name||' INHERIT '||p_parent_table; -- If custom time, set extra config options. IF v_type = 'time-custom' THEN INSERT INTO @extschema@.custom_time_partitions (parent_table, child_table, partition_range) VALUES ( p_parent_table, v_partition_name, tstzrange(v_partition_timestamp_start, v_partition_timestamp_end, '[)') ); END IF; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP EXECUTE 'GRANT '||array_to_string(v_parent_grant.types, ',')||' ON '||v_partition_name||' TO '||v_parent_grant.grantee; SELECT array_agg(r) INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE '||array_to_string(v_revoke, ',')||' ON '||v_partition_name||' FROM '||v_parent_grant.grantee||' CASCADE'; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN SELECT array_agg(r) INTO v_revoke FROM ( SELECT DISTINCT grantee::text AS r FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_partition_name EXCEPT SELECT unnest(v_grantees)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE ALL ON '||v_partition_name||' FROM '||array_to_string(v_revoke, ','); END IF; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' OWNER TO '||v_parent_owner; 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; END LOOP; IF v_analyze THEN EXECUTE 'ANALYZE '||p_parent_table; END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_partition_name; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE TABLE: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Apply constraints managed by partman extension */ CREATE FUNCTION apply_constraints(p_parent_table text, p_child_table text DEFAULT NULL, p_analyze boolean DEFAULT TRUE, p_debug boolean DEFAULT FALSE) RETURNS void LANGUAGE plpgsql AS $$ DECLARE v_child_table text; v_child_tablename text; v_col text; v_constraint_cols text[]; v_constraint_col_type text; v_constraint_name text; v_datetime_string text; v_existing_constraint_name text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_id int; v_last_partition_timestamp timestamp; v_constraint_values record; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_part_interval text; v_partition_suffix text; v_premake int; v_sql text; v_step_id bigint; v_suffix_position int; v_type text; BEGIN SELECT type , part_interval , premake , datetime_string , last_partition , constraint_cols , jobmon INTO v_type , v_part_interval , v_premake , v_datetime_string , v_last_partition , v_constraint_cols , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_constraint_cols IS NULL THEN IF p_debug THEN RAISE NOTICE 'Given parent table (%) not set up for constraint management (constraint_cols is NULL)', p_parent_table; END IF; -- Returns silently to allow this function to be simply called by maintenance processes without having to check if config options are set. RETURN; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE CONSTRAINT: '||p_parent_table); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; -- If p_child_table is null, figure out the partition that is the one right before the premake value backwards. IF p_child_table IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Automatically determining most recent child on which to apply constraints'); END IF; v_suffix_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; IF v_type IN ('time-static', 'time-dynamic') THEN v_last_partition_timestamp := to_timestamp(substring(v_last_partition from v_suffix_position), v_datetime_string); v_partition_suffix := to_char(v_last_partition_timestamp - (v_part_interval::interval * ((v_premake * 2)+1) ), v_datetime_string); ELSIF v_type IN ('id-static', 'id-dynamic') THEN v_last_partition_id := substring(v_last_partition from v_suffix_position)::int; v_partition_suffix := (v_last_partition_id - (v_part_interval::int * ((v_premake * 2)+1) ))::text; END IF; v_child_table := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_partition_suffix, TRUE); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Target child table: '||v_child_table); END IF; ELSE v_child_table := p_child_table; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Checking if target child table exists'); END IF; SELECT tablename INTO v_child_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_child_table; IF v_child_tablename IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', 'Target child table ('||v_child_table||') does not exist. Skipping constraint creation.'); PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; IF p_debug THEN RAISE NOTICE 'Target child table (%) does not exist. Skipping constraint creation.', v_child_table; END IF; RETURN; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; FOREACH v_col IN ARRAY v_constraint_cols LOOP SELECT c.conname INTO v_existing_constraint_name FROM pg_catalog.pg_constraint c JOIN pg_catalog.pg_attribute a ON c.conrelid = a.attrelid WHERE conrelid = v_child_table::regclass AND c.conname LIKE 'partmanconstr_%' AND c.contype = 'c' AND a.attname = v_col AND ARRAY[a.attnum] <@ c.conkey AND a.attisdropped = false; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying new constraint on column: '||v_col); END IF; IF v_existing_constraint_name IS NOT NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', 'Partman managed constraint already exists on this table ('||v_child_table||') and column ('||v_col||'). Skipping creation.'); END IF; RAISE WARNING 'Partman managed constraint already exists on this table (%) and column (%). Skipping creation.', v_child_table, v_col ; CONTINUE; END IF; -- Ensure column name gets put on end of constraint name to help avoid naming conflicts v_constraint_name := @extschema@.check_name_length('partmanconstr_'||v_child_tablename, p_suffix := '_'||v_col); EXECUTE 'SELECT min('||v_col||')::text AS min, max('||v_col||')::text AS max FROM '||v_child_table INTO v_constraint_values; IF v_constraint_values IS NOT NULL THEN v_sql := concat('ALTER TABLE ', v_child_table, ' ADD CONSTRAINT ', v_constraint_name , ' CHECK (', v_col, ' >= ', quote_literal(v_constraint_values.min), ' AND ' , v_col, ' <= ', quote_literal(v_constraint_values.max), ')' ); IF p_debug THEN RAISE NOTICE 'Constraint creation query: %', v_sql; END IF; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'New constraint created: '||v_sql); END IF; ELSE IF p_debug THEN RAISE NOTICE 'Given column (%) contains all NULLs. No constraint created', v_col; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', 'Given column ('||v_col||') contains all NULLs. No constraint created'); END IF; END IF; END LOOP; IF p_analyze THEN EXECUTE 'ANALYZE '||p_parent_table; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE CONSTRAINT: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; -- Restore dropped object privileges DO $$ DECLARE v_row record; BEGIN FOR v_row IN SELECT statement FROM partman_preserve_privs_temp LOOP IF v_row.statement IS NOT NULL THEN EXECUTE v_row.statement; END IF; END LOOP; END $$; DROP TABLE partman_preserve_privs_temp; pg_partman-4.6.0/updates/pg_partman--1.7.0--1.7.1.sql000066400000000000000000001504151412761706100214370ustar00rootroot00000000000000-- Foreign keys placed on the parent table are now inherited to child tables. -- Any new partitions created after this update is installed will have the FKs applied to children. -- Existing child partitions will not have FKs applied. See the reapply_foreign_keys.py python script to reapply FKs to all child tables. -- The new apply_foreign_keys() function & reapply_foreign_keys.py script can be applied to any table inheritance set, not just the ones managed by pg_partman. -- See blog post for some more information about partitioning & foreign keys - http://www.keithf4.com/table-partitioning-and-foreign-keys -- Unlogged table property on parent table is now automatically inherited to child tables. Note this only applies to newly created child partitions after this update is installed. -- Added lockwait options to the undo partition functions (plpgsql & python) -- Added the same autovacuum feature & option to undo_partition.py that partition_data.py has. -- Made python scripts compatible with python 3 -- PgTAP tests for unlogged tables and FK inheritance CREATE TEMP TABLE partman_preserve_privs_temp (statement text); INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.undo_partition_id(text, int, bigint, boolean, numeric) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'undo_partition_id'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.undo_partition_time(text, int, interval, boolean, numeric) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'undo_partition_time'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.undo_partition(text, int, boolean, boolean, numeric) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'undo_partition'; DROP FUNCTION undo_partition_id(text, int, bigint, boolean); DROP FUNCTION undo_partition_time(text, int, interval, boolean); DROP FUNCTION undo_partition(text, int, boolean, boolean); /* * Apply foreign keys that exist on the given parent to the given child table */ CREATE FUNCTION apply_foreign_keys(p_parent_table text, p_child_table text DEFAULT NULL, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql AS $$ DECLARE v_job_id bigint; v_jobmon text; v_jobmon_schema text; v_old_search_path text; v_ref_schema text; v_ref_table text; v_row record; v_schemaname text; v_sql text; v_step_id bigint; v_tablename text; BEGIN SELECT jobmon INTO v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN APPLYING FOREIGN KEYS: '||p_parent_table); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Checking if target child table exists'); END IF; SELECT schemaname, tablename INTO v_schemaname, v_tablename FROM pg_catalog.pg_tables WHERE schemaname||'.'||tablename = p_child_table; IF v_tablename IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'CRITICAL', 'Target child table ('||v_child_table||') does not exist.'); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION 'Target child table (%.%) does not exist.', v_schemaname, v_tablename; RETURN; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; FOR v_row IN SELECT keys.conname , keys.confrelid::regclass::text AS ref_table , '"'||string_agg(att.attname, '","')||'"' AS ref_column , '"'||string_agg(att2.attname, '","')||'"' AS child_column FROM ( SELECT con.conname , unnest(con.conkey) as ref , unnest(con.confkey) as child , con.confrelid , con.conrelid FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN pg_catalog.pg_constraint con ON c.oid = con.conrelid WHERE n.nspname ||'.'|| c.relname = p_parent_table AND con.contype = 'f' ORDER BY con.conkey ) keys JOIN pg_catalog.pg_class cl ON cl.oid = keys.confrelid JOIN pg_catalog.pg_attribute att ON att.attrelid = keys.confrelid AND att.attnum = keys.child JOIN pg_catalog.pg_attribute att2 ON att2.attrelid = keys.conrelid AND att2.attnum = keys.ref GROUP BY keys.conname, keys.confrelid LOOP SELECT schemaname, tablename INTO v_ref_schema, v_ref_table FROM pg_tables WHERE schemaname||'.'||tablename = v_row.ref_table; v_sql := format('ALTER TABLE %I.%I ADD FOREIGN KEY (%s) REFERENCES %I.%I (%s)', v_schemaname, v_tablename, v_row.child_column, v_ref_schema, v_ref_table, v_row.ref_column); IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying FK: '||v_sql); END IF; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'FK applied'); END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN APPLYING FOREIGN KEYS: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to create id partitions */ CREATE OR REPLACE FUNCTION create_id_partition (p_parent_table text, p_partition_ids bigint[]) RETURNS text LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_grantees text[]; v_hasoids boolean; v_id bigint; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_parent_tablespace text; v_part_interval bigint; v_partition_name text; v_revoke text[]; v_sql text; v_step_id bigint; v_tablename text; v_unlogged char; BEGIN SELECT control , part_interval , jobmon INTO v_control , v_part_interval , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; FOREACH v_id IN ARRAY p_partition_ids LOOP v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_id::text, TRUE); -- If child table already exists, skip creation SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_tablename IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_id||' to '||(v_id + v_part_interval)-1); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class WHERE oid::regclass = p_parent_table::regclass; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; v_sql := v_sql || ' TABLE '||v_partition_name||' (LIKE '||p_parent_table||' INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)'; SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class WHERE oid::regclass = p_parent_table::regclass; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_parent_tablespace IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_partition_name||' SET TABLESPACE '||v_parent_tablespace; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' ADD CONSTRAINT '||v_tablename||'_partition_check CHECK ('||v_control||'>='||quote_literal(v_id)||' AND '||v_control||'<'||quote_literal(v_id + v_part_interval)||')'; EXECUTE 'ALTER TABLE '||v_partition_name||' INHERIT '||p_parent_table; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP EXECUTE 'GRANT '||array_to_string(v_parent_grant.types, ',')||' ON '||v_partition_name||' TO '||v_parent_grant.grantee; SELECT array_agg(r) INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE '||array_to_string(v_revoke, ',')||' ON '||v_partition_name||' FROM '||v_parent_grant.grantee||' CASCADE'; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN SELECT array_agg(r) INTO v_revoke FROM ( SELECT DISTINCT grantee::text AS r FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_partition_name EXCEPT SELECT unnest(v_grantees)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE ALL ON '||v_partition_name||' FROM '||array_to_string(v_revoke, ','); END IF; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' OWNER TO '||v_parent_owner; PERFORM @extschema@.apply_foreign_keys(quote_ident(v_parent_schema)||'.'||quote_ident(v_parent_tablename), v_partition_name); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); PERFORM close_job(v_job_id); END IF; END LOOP; IF v_analyze THEN EXECUTE 'ANALYZE '||p_parent_table; END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_partition_name; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE TABLE: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to create a child table in a time-based partition set */ CREATE OR REPLACE FUNCTION create_time_partition (p_parent_table text, p_partition_times timestamp[]) RETURNS text LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_grantees text[]; v_hasoids boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_partition_name text; v_partition_suffix text; v_parent_tablespace text; v_part_interval interval; v_partition_timestamp_end timestamp; v_partition_timestamp_start timestamp; v_quarter text; v_revoke text[]; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_tablename text; v_trunc_value text; v_time timestamp; v_type text; v_unlogged char; v_year text; BEGIN SELECT type , control , part_interval , jobmon INTO v_type , v_control , v_part_interval , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic' OR type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_suffix := to_char(v_time, 'YYYY'); IF v_part_interval < '1 year' AND v_part_interval <> '1 week' THEN v_partition_suffix := v_partition_suffix ||'_'|| to_char(v_time, 'MM'); IF v_part_interval < '1 month' AND v_part_interval <> '1 week' THEN v_partition_suffix := v_partition_suffix ||'_'|| to_char(v_time, 'DD'); IF v_part_interval < '1 day' THEN v_partition_suffix := v_partition_suffix || '_' || to_char(v_time, 'HH24MI'); IF v_part_interval < '1 minute' THEN v_partition_suffix := v_partition_suffix || to_char(v_time, 'SS'); END IF; -- end < minute IF END IF; -- end < day IF END IF; -- end < month IF END IF; -- end < year IF v_partition_timestamp_start := v_time; BEGIN v_partition_timestamp_end := v_time + v_part_interval; 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_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_time||' skipped'); CONTINUE; END; IF v_part_interval = '1 week' THEN v_partition_suffix := to_char(v_time, 'IYYY') || 'w' || to_char(v_time, 'IW'); END IF; -- "Q" is ignored in to_timestamp, so handle special case IF v_part_interval = '3 months' AND (v_type = 'time-static' OR v_type = 'time-dynamic') THEN v_year := to_char(v_time, 'YYYY'); v_quarter := to_char(v_time, 'Q'); v_partition_suffix := v_year || 'q' || v_quarter; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_partition_suffix, TRUE); SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_tablename IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_partition_timestamp_start||' to '||(v_partition_timestamp_end-'1sec'::interval)); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class WHERE oid::regclass = p_parent_table::regclass; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; v_sql := v_sql || ' TABLE '||v_partition_name||' (LIKE '||p_parent_table||' INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)'; SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class WHERE oid::regclass = p_parent_table::regclass; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_parent_tablespace IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_partition_name||' SET TABLESPACE '||v_parent_tablespace; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' ADD CONSTRAINT '||v_tablename||'_partition_check CHECK ('||v_control||'>='||quote_literal(v_partition_timestamp_start)||' AND '||v_control||'<'||quote_literal(v_partition_timestamp_end)||')'; EXECUTE 'ALTER TABLE '||v_partition_name||' INHERIT '||p_parent_table; -- If custom time, set extra config options. IF v_type = 'time-custom' THEN INSERT INTO @extschema@.custom_time_partitions (parent_table, child_table, partition_range) VALUES ( p_parent_table, v_partition_name, tstzrange(v_partition_timestamp_start, v_partition_timestamp_end, '[)') ); END IF; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP EXECUTE 'GRANT '||array_to_string(v_parent_grant.types, ',')||' ON '||v_partition_name||' TO '||v_parent_grant.grantee; SELECT array_agg(r) INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE '||array_to_string(v_revoke, ',')||' ON '||v_partition_name||' FROM '||v_parent_grant.grantee||' CASCADE'; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN SELECT array_agg(r) INTO v_revoke FROM ( SELECT DISTINCT grantee::text AS r FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_partition_name EXCEPT SELECT unnest(v_grantees)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE ALL ON '||v_partition_name||' FROM '||array_to_string(v_revoke, ','); END IF; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' OWNER TO '||v_parent_owner; PERFORM @extschema@.apply_foreign_keys(quote_ident(v_parent_schema)||'.'||quote_ident(v_parent_tablename), v_partition_name); 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; END LOOP; IF v_analyze THEN EXECUTE 'ANALYZE '||p_parent_table; END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_partition_name; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE TABLE: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to undo id-based partitioning created by this extension */ CREATE FUNCTION undo_partition_id(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval bigint DEFAULT NULL, p_keep_table boolean DEFAULT true, p_lock_wait numeric DEFAULT 0) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_batch_loop_count int := 0; v_child_loop_total bigint := 0; v_child_min bigint; v_child_table text; v_control text; v_exists int; v_function_name text; v_inner_loop_count int; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_move_sql text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_part_interval bigint; v_row record; v_rowcount bigint; v_step_id bigint; v_trig_name text; v_total bigint := 0; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_id_partition')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_id_partition already running.'; RETURN 0; END IF; SELECT part_interval::bigint , control , jobmon INTO v_part_interval , v_control , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Undoing partitioning for table '||p_parent_table); END IF; IF p_batch_interval IS NULL THEN p_batch_interval := v_part_interval; END IF; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables and stop new id partitions from being made. SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, '_part_trig_func', FALSE); SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t WHERE tgname = v_trig_name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE 'LOCK TABLE ONLY '||p_parent_table||' IN ACCESS EXCLUSIVE MODE NOWAIT'; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; RETURN -1; END IF; END IF; -- END p_lock_wait IF EXECUTE 'DROP TRIGGER IF EXISTS '||v_trig_name||' ON '||p_parent_table; END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE 'DROP FUNCTION IF EXISTS '||v_function_name||'()'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); END IF; <> WHILE v_batch_loop_count < p_batch_count LOOP SELECT n.nspname||'.'||c.relname INTO v_child_table FROM pg_inherits i JOIN pg_class c ON i.inhrelid = c.oid JOIN pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC; EXIT WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing child partition: '||v_child_table); END IF; EXECUTE 'SELECT min('||v_control||') FROM '||v_child_table INTO v_child_min; IF v_child_min IS NULL THEN -- No rows left in this child table. Remove from partition set. -- lockwait timeout for table drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE 'LOCK TABLE ONLY '||v_child_table||' IN ACCESS EXCLUSIVE MODE NOWAIT'; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; RETURN -1; END IF; END IF; -- END p_lock_wait IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF p_keep_table = false THEN EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table UNINHERITED, not DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; END IF; v_undo_count := v_undo_count + 1; CONTINUE outer_child_loop; END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP -- lockwait timeout for row batches IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE 'SELECT * FROM ' || v_child_table || ' WHERE '||v_control||' <= '||quote_literal(v_child_min + (p_batch_interval * v_inner_loop_count)) ||' FOR UPDATE NOWAIT'; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; RETURN -1; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval v_move_sql := 'WITH move_data AS (DELETE FROM '||v_child_table|| ' WHERE '||v_control||' <= '||quote_literal(v_child_min + (p_batch_interval * v_inner_loop_count))||' RETURNING *) INSERT INTO '||p_parent_table||' SELECT * FROM move_data'; EXECUTE v_move_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Moved '||v_child_loop_total||' rows to parent.'); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; IF v_batch_loop_count < p_batch_count THEN -- FOR loop never ran, so there's no child tables left. DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) to the parent. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', 'Copied '||v_total||' row(s) to the parent. Removed '||v_undo_count||' partitions.'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_total; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN UNDO PARTITIONING: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to undo time-based partitioning created by this extension */ CREATE FUNCTION undo_partition_time(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval interval DEFAULT NULL, p_keep_table boolean DEFAULT true, p_lock_wait numeric DEFAULT 0) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_batch_loop_count int := 0; v_child_min timestamptz; v_child_loop_total bigint := 0; v_child_table text; v_control text; v_function_name text; v_inner_loop_count int; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_move_sql text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_part_interval interval; v_row record; v_rowcount bigint; v_step_id bigint; v_total bigint := 0; v_trig_name text; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_time_partition')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_time_partition already running.'; RETURN 0; END IF; SELECT part_interval::interval , control , jobmon INTO v_part_interval , v_control , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic' OR type = 'time-custom'); IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Undoing partitioning for table '||p_parent_table); END IF; IF p_batch_interval IS NULL THEN p_batch_interval := v_part_interval; END IF; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables. SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, '_part_trig_func', FALSE); SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t WHERE tgname = v_trig_name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE 'LOCK TABLE ONLY '||p_parent_table||' IN ACCESS EXCLUSIVE MODE NOWAIT'; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; RETURN -1; END IF; END IF; -- END p_lock_wait IF EXECUTE 'DROP TRIGGER IF EXISTS '||v_trig_name||' ON '||p_parent_table; END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE 'DROP FUNCTION IF EXISTS '||v_function_name||'()'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); END IF; <> WHILE v_batch_loop_count < p_batch_count LOOP SELECT n.nspname||'.'||c.relname INTO v_child_table FROM pg_inherits i JOIN pg_class c ON i.inhrelid = c.oid JOIN pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC; EXIT WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing child partition: '||v_child_table); END IF; EXECUTE 'SELECT min('||v_control||') FROM '||v_child_table INTO v_child_min; IF v_child_min IS NULL THEN -- No rows left in this child table. Remove from partition set. -- lockwait timeout for table drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE 'LOCK TABLE ONLY '||v_child_table||' IN ACCESS EXCLUSIVE MODE NOWAIT'; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; RETURN -1; END IF; END IF; -- END p_lock_wait IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF p_keep_table = false THEN EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table UNINHERITED, not DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; END IF; v_undo_count := v_undo_count + 1; CONTINUE outer_child_loop; END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE 'SELECT * FROM ' || v_child_table || ' WHERE '||v_control||' <= '||quote_literal(v_child_min + (p_batch_interval * v_inner_loop_count)) ||' FOR UPDATE NOWAIT'; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; RETURN -1; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval v_move_sql := 'WITH move_data AS (DELETE FROM '||v_child_table|| ' WHERE '||v_control||' <= '||quote_literal(v_child_min + (p_batch_interval * v_inner_loop_count))||' RETURNING *) INSERT INTO '||p_parent_table||' SELECT * FROM move_data'; EXECUTE v_move_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Moved '||v_child_loop_total||' rows to parent.'); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; IF v_batch_loop_count < p_batch_count THEN -- FOR loop never ran, so there's no child tables left. DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) to the parent. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', 'Copied '||v_total||' row(s) to the parent. Removed '||v_undo_count||' partitions.'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_total; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN UNDO PARTITIONING: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to undo partitioning. * Will actually work on any parent/child table set, not just ones created by pg_partman. */ CREATE FUNCTION undo_partition(p_parent_table text, p_batch_count int DEFAULT 1, p_keep_table boolean DEFAULT true, p_jobmon boolean DEFAULT true, p_lock_wait numeric DEFAULT 0) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_batch_loop_count bigint := 0; v_child_count bigint; v_child_table text; v_copy_sql text; v_function_name text; v_job_id bigint; v_jobmon_schema text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_part_interval interval; v_rowcount bigint; v_step_id bigint; v_total bigint := 0; v_trig_name text; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition already running.'; RETURN 0; END IF; IF p_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Undoing partitioning for table '||p_parent_table); END IF; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables and stop new id partitions from being made. SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, '_part_trig_func', FALSE); SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t WHERE tgname = v_trig_name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE 'LOCK TABLE ONLY '||p_parent_table||' IN ACCESS EXCLUSIVE MODE NOWAIT'; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; RETURN -1; END IF; END IF; -- END p_lock_wait IF EXECUTE 'DROP TRIGGER IF EXISTS '||v_trig_name||' ON '||p_parent_table; END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE 'DROP FUNCTION IF EXISTS '||v_function_name||'()'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); END IF; WHILE v_batch_loop_count < p_batch_count LOOP SELECT n.nspname||'.'||c.relname INTO v_child_table FROM pg_inherits i JOIN pg_class c ON i.inhrelid = c.oid JOIN pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC; EXIT WHEN v_child_table IS NULL; EXECUTE 'SELECT count(*) FROM '||v_child_table INTO v_child_count; IF v_child_count = 0 THEN -- No rows left in this child table. Remove from partition set. -- lockwait timeout for table drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE 'LOCK TABLE ONLY '||v_child_table||' IN ACCESS EXCLUSIVE MODE NOWAIT'; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; RETURN -1; END IF; END IF; -- END p_lock_wait IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF p_keep_table = false THEN EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table DROPPED. Moved '||coalesce(v_rowcount, 0)||' rows to parent'); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table UNINHERITED, not DROPPED. Copied '||coalesce(v_rowcount, 0)||' rows to parent'); END IF; END IF; v_undo_count := v_undo_count + 1; CONTINUE; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing child partition: '||v_child_table); END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE 'SELECT * FROM '|| v_child_table ||' FOR UPDATE NOWAIT'; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; RETURN -1; END IF; END IF; v_copy_sql := 'INSERT INTO '||p_parent_table||' SELECT * FROM '||v_child_table; EXECUTE v_copy_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF p_keep_table = false THEN EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table DROPPED. Moved '||v_rowcount||' rows to parent'); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table UNINHERITED, not DROPPED. Copied '||v_rowcount||' rows to parent'); END IF; END IF; v_batch_loop_count := v_batch_loop_count + 1; v_undo_count := v_undo_count + 1; END LOOP; IF v_undo_count = 0 THEN -- FOR loop never ran, so there's no child tables left. DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman (if it existed)'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) from % child table(s) to the parent: %', v_total, v_undo_count, p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', 'Copied '||v_total||' row(s) from '||v_undo_count||' child table(s) to the parent'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_total; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN UNDO PARTITIONING: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''Partition function maintenance for table '||p_parent_table||' failed'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; -- Restore dropped object privileges DO $$ DECLARE v_row record; BEGIN FOR v_row IN SELECT statement FROM partman_preserve_privs_temp LOOP IF v_row.statement IS NOT NULL THEN EXECUTE v_row.statement; END IF; END LOOP; END $$; DROP TABLE IF EXISTS partman_preserve_privs_temp; pg_partman-4.6.0/updates/pg_partman--1.7.1--1.7.2.sql000066400000000000000000001052741412761706100214440ustar00rootroot00000000000000-- Fixed bug in apply_foreign_keys() where new partition creation would fail when the partition set's schema is in the current search_path. Most commonly happened when partition sets with foreig keys were in public, but any schema in current search_path would cause this to manifest. Reported by Isaƭas SƔnchez via my blog. (Github Issue #27) -- Foreign key inheritance is now optional since more complex FK relationships may not work ideally with pg_partman's default method. New configuration option in part_config table and parameter to create_parent(). ALTER TABLE @extschema@.part_config ADD inherit_fk boolean NOT NULL DEFAULT true; CREATE TEMP TABLE partman_preserve_privs_temp (statement text); INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_parent(text, text, text, text, text[], int, boolean, text, boolean, boolean, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_parent'; DROP FUNCTION create_parent(text, text, text, text, text[], int, boolean, text, boolean, boolean); /* * Apply foreign keys that exist on the given parent to the given child table */ CREATE OR REPLACE FUNCTION apply_foreign_keys(p_parent_table text, p_child_table text DEFAULT NULL, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql AS $$ DECLARE v_job_id bigint; v_jobmon text; v_jobmon_schema text; v_old_search_path text; v_ref_schema text; v_ref_table text; v_row record; v_schemaname text; v_sql text; v_step_id bigint; v_tablename text; BEGIN SELECT jobmon INTO v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN APPLYING FOREIGN KEYS: '||p_parent_table); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Checking if target child table exists'); END IF; SELECT schemaname, tablename INTO v_schemaname, v_tablename FROM pg_catalog.pg_tables WHERE schemaname||'.'||tablename = p_child_table; IF v_tablename IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'CRITICAL', 'Target child table ('||v_child_table||') does not exist.'); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION 'Target child table (%.%) does not exist.', v_schemaname, v_tablename; RETURN; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; FOR v_row IN SELECT n.nspname||'.'||cl.relname AS ref_table , '"'||string_agg(att.attname, '","')||'"' AS ref_column , '"'||string_agg(att2.attname, '","')||'"' AS child_column FROM ( SELECT con.conname , unnest(con.conkey) as ref , unnest(con.confkey) as child , con.confrelid , con.conrelid FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN pg_catalog.pg_constraint con ON c.oid = con.conrelid WHERE n.nspname ||'.'|| c.relname = p_parent_table AND con.contype = 'f' ORDER BY con.conkey ) keys JOIN pg_catalog.pg_class cl ON cl.oid = keys.confrelid JOIN pg_catalog.pg_namespace n ON cl.relnamespace = n.oid JOIN pg_catalog.pg_attribute att ON att.attrelid = keys.confrelid AND att.attnum = keys.child JOIN pg_catalog.pg_attribute att2 ON att2.attrelid = keys.conrelid AND att2.attnum = keys.ref GROUP BY keys.conname, n.nspname, cl.relname LOOP SELECT schemaname, tablename INTO v_ref_schema, v_ref_table FROM pg_tables WHERE schemaname||'.'||tablename = v_row.ref_table; v_sql := format('ALTER TABLE %I.%I ADD FOREIGN KEY (%s) REFERENCES %I.%I (%s)', v_schemaname, v_tablename, v_row.child_column, v_ref_schema, v_ref_table, v_row.ref_column); IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying FK: '||v_sql); END IF; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'FK applied'); END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN APPLYING FOREIGN KEYS: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to turn a table into the parent of a partition set */ CREATE FUNCTION 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_use_run_maintenance boolean DEFAULT NULL , p_start_partition text DEFAULT NULL , p_inherit_fk boolean DEFAULT true , p_jobmon boolean DEFAULT true , p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_base_timestamp timestamp; v_count int := 1; v_datetime_string text; v_id_interval bigint; v_job_id bigint; v_jobmon_schema text; v_last_partition_name text; v_old_search_path text; v_partition_time timestamp; v_partition_time_array timestamp[]; v_partition_id bigint[]; v_max bigint; v_notnull boolean; v_run_maint boolean; v_start_time timestamp; v_starting_partition_id bigint; v_step_id bigint; v_step_overflow_id bigint; v_tablename text; v_time_interval interval; BEGIN IF position('.' in p_parent_table) = 0 THEN RAISE EXCEPTION 'Parent table must be schema qualified'; END IF; SELECT tablename INTO v_tablename FROM pg_tables WHERE schemaname || '.' || tablename = p_parent_table; IF v_tablename IS NULL THEN RAISE EXCEPTION 'Please create given parent table first: %', p_parent_table; END IF; SELECT attnotnull INTO v_notnull FROM pg_attribute WHERE attrelid = p_parent_table::regclass AND attname = p_control; IF v_notnull = false OR v_notnull IS NULL THEN RAISE EXCEPTION 'Control column (%) for parent table (%) must be NOT NULL', p_control, p_parent_table; END IF; IF NOT @extschema@.check_partition_type(p_type) THEN RAISE EXCEPTION '% is not a valid partitioning type', p_type; END IF; IF p_type = 'time-custom' AND @extschema@.check_version('9.2.0') IS FALSE THEN RAISE EXCEPTION 'The "time-custom" type requires a minimum PostgreSQL version of 9.2.0'; END IF; EXECUTE 'LOCK TABLE '||p_parent_table||' IN ACCESS EXCLUSIVE MODE'; IF p_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF p_use_run_maintenance IS NOT NULL THEN IF p_use_run_maintenance IS FALSE AND (p_type = 'time-static' OR p_type = 'time-dynamic' OR p_type = 'time-custom') THEN RAISE EXCEPTION 'p_run_maintenance cannot be set to false for time based partitioning'; END IF; v_run_maint := p_use_run_maintenance; ELSIF p_type = 'time-static' OR p_type = 'time-dynamic' OR p_type = 'time-custom' THEN v_run_maint := TRUE; ELSIF p_type = 'id-static' OR p_type ='id-dynamic' THEN v_run_maint := FALSE; ELSE RAISE EXCEPTION 'use_run_maintenance value cannot be set NULL'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN SETUP PARENT: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating initial partitions on new parent table: '||p_parent_table); END IF; IF p_type = 'time-static' OR p_type = 'time-dynamic' OR p_type = 'time-custom' 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 <> 'time-custom' THEN RAISE EXCEPTION 'Must use a predefined time interval if not using type "time-custom". See documentation.'; 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::timestamp, 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))::timestamp; 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 , type , part_interval , control , premake , constraint_cols , datetime_string , use_run_maintenance , inherit_fk , jobmon) VALUES ( p_parent_table , p_type , v_time_interval , p_control , p_premake , p_constraint_cols , v_datetime_string , v_run_maint , p_inherit_fk , p_jobmon); v_last_partition_name := @extschema@.create_time_partition(p_parent_table, v_partition_time_array); -- Doing separate update because create function requires in config table last_partition to be set UPDATE @extschema@.part_config SET last_partition = v_last_partition_name WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Time partitions premade: '||p_premake); END IF; END IF; IF p_type = 'id-static' OR p_type = 'id-dynamic' THEN v_id_interval := p_interval::bigint; IF v_id_interval <= 1 THEN RAISE EXCEPTION 'Interval for serial partitioning must be greater than 1'; END IF; -- 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 EXECUTE 'SELECT COALESCE('||quote_nullable(p_start_partition::bigint)||', max('||p_control||')::bigint, 0) FROM '||p_parent_table||' LIMIT 1' INTO v_max; 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_append(v_partition_id, (v_starting_partition_id - v_id_interval*i)); END IF; v_partition_id = array_append(v_partition_id, (v_id_interval*i) + v_starting_partition_id); END LOOP; INSERT INTO @extschema@.part_config ( parent_table , type , part_interval , control , premake , constraint_cols , use_run_maintenance , inherit_fk , jobmon) VALUES ( p_parent_table , p_type , v_id_interval , p_control , p_premake , p_constraint_cols , v_run_maint , p_inherit_fk , p_jobmon); v_last_partition_name := @extschema@.create_id_partition(p_parent_table, v_partition_id); -- Doing separate update because create function needs parent table in config table for apply_grants() UPDATE @extschema@.part_config SET last_partition = v_last_partition_name WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'ID partitions premade: '||p_premake); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating partition function'); END IF; IF p_type = 'time-static' OR p_type = 'time-dynamic' OR p_type = 'time-custom' THEN PERFORM @extschema@.create_time_function(p_parent_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Time function created'); END IF; ELSIF p_type = 'id-static' OR p_type = 'id-dynamic' THEN PERFORM @extschema@.create_id_function(p_parent_table); 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); 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; EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE PARENT: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''Partition creation for table '||p_parent_table||' failed'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to create id partitions */ CREATE OR REPLACE FUNCTION create_id_partition (p_parent_table text, p_partition_ids bigint[]) RETURNS text LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_grantees text[]; v_hasoids boolean; v_id bigint; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_parent_tablespace text; v_part_interval bigint; v_partition_name text; v_revoke text[]; v_sql text; v_step_id bigint; v_tablename text; v_unlogged char; BEGIN SELECT control , part_interval , inherit_fk , jobmon INTO v_control , v_part_interval , v_inherit_fk , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; FOREACH v_id IN ARRAY p_partition_ids LOOP v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_id::text, TRUE); -- If child table already exists, skip creation SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_tablename IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_id||' to '||(v_id + v_part_interval)-1); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class WHERE oid::regclass = p_parent_table::regclass; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; v_sql := v_sql || ' TABLE '||v_partition_name||' (LIKE '||p_parent_table||' INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)'; SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class WHERE oid::regclass = p_parent_table::regclass; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_parent_tablespace IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_partition_name||' SET TABLESPACE '||v_parent_tablespace; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' ADD CONSTRAINT '||v_tablename||'_partition_check CHECK ('||v_control||'>='||quote_literal(v_id)||' AND '||v_control||'<'||quote_literal(v_id + v_part_interval)||')'; EXECUTE 'ALTER TABLE '||v_partition_name||' INHERIT '||p_parent_table; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP EXECUTE 'GRANT '||array_to_string(v_parent_grant.types, ',')||' ON '||v_partition_name||' TO '||v_parent_grant.grantee; SELECT array_agg(r) INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE '||array_to_string(v_revoke, ',')||' ON '||v_partition_name||' FROM '||v_parent_grant.grantee||' CASCADE'; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN SELECT array_agg(r) INTO v_revoke FROM ( SELECT DISTINCT grantee::text AS r FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_partition_name EXCEPT SELECT unnest(v_grantees)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE ALL ON '||v_partition_name||' FROM '||array_to_string(v_revoke, ','); END IF; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' OWNER TO '||v_parent_owner; IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(quote_ident(v_parent_schema)||'.'||quote_ident(v_parent_tablename), v_partition_name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); PERFORM close_job(v_job_id); END IF; END LOOP; IF v_analyze THEN EXECUTE 'ANALYZE '||p_parent_table; END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_partition_name; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE TABLE: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to create a child table in a time-based partition set */ CREATE OR REPLACE FUNCTION create_time_partition (p_parent_table text, p_partition_times timestamp[]) RETURNS text LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_grantees text[]; v_hasoids boolean; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_partition_name text; v_partition_suffix text; v_parent_tablespace text; v_part_interval interval; v_partition_timestamp_end timestamp; v_partition_timestamp_start timestamp; v_quarter text; v_revoke text[]; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_tablename text; v_trunc_value text; v_time timestamp; v_type text; v_unlogged char; v_year text; BEGIN SELECT type , control , part_interval , inherit_fk , jobmon INTO v_type , v_control , v_part_interval , v_inherit_fk , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic' OR type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_suffix := to_char(v_time, 'YYYY'); IF v_part_interval < '1 year' AND v_part_interval <> '1 week' THEN v_partition_suffix := v_partition_suffix ||'_'|| to_char(v_time, 'MM'); IF v_part_interval < '1 month' AND v_part_interval <> '1 week' THEN v_partition_suffix := v_partition_suffix ||'_'|| to_char(v_time, 'DD'); IF v_part_interval < '1 day' THEN v_partition_suffix := v_partition_suffix || '_' || to_char(v_time, 'HH24MI'); IF v_part_interval < '1 minute' THEN v_partition_suffix := v_partition_suffix || to_char(v_time, 'SS'); END IF; -- end < minute IF END IF; -- end < day IF END IF; -- end < month IF END IF; -- end < year IF v_partition_timestamp_start := v_time; BEGIN v_partition_timestamp_end := v_time + v_part_interval; 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_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_time||' skipped'); CONTINUE; END; IF v_part_interval = '1 week' THEN v_partition_suffix := to_char(v_time, 'IYYY') || 'w' || to_char(v_time, 'IW'); END IF; -- "Q" is ignored in to_timestamp, so handle special case IF v_part_interval = '3 months' AND (v_type = 'time-static' OR v_type = 'time-dynamic') THEN v_year := to_char(v_time, 'YYYY'); v_quarter := to_char(v_time, 'Q'); v_partition_suffix := v_year || 'q' || v_quarter; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_partition_suffix, TRUE); SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_tablename IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_partition_timestamp_start||' to '||(v_partition_timestamp_end-'1sec'::interval)); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class WHERE oid::regclass = p_parent_table::regclass; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; v_sql := v_sql || ' TABLE '||v_partition_name||' (LIKE '||p_parent_table||' INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)'; SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class WHERE oid::regclass = p_parent_table::regclass; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_parent_tablespace IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_partition_name||' SET TABLESPACE '||v_parent_tablespace; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' ADD CONSTRAINT '||v_tablename||'_partition_check CHECK ('||v_control||'>='||quote_literal(v_partition_timestamp_start)||' AND '||v_control||'<'||quote_literal(v_partition_timestamp_end)||')'; EXECUTE 'ALTER TABLE '||v_partition_name||' INHERIT '||p_parent_table; -- If custom time, set extra config options. IF v_type = 'time-custom' THEN INSERT INTO @extschema@.custom_time_partitions (parent_table, child_table, partition_range) VALUES ( p_parent_table, v_partition_name, tstzrange(v_partition_timestamp_start, v_partition_timestamp_end, '[)') ); END IF; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP EXECUTE 'GRANT '||array_to_string(v_parent_grant.types, ',')||' ON '||v_partition_name||' TO '||v_parent_grant.grantee; SELECT array_agg(r) INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE '||array_to_string(v_revoke, ',')||' ON '||v_partition_name||' FROM '||v_parent_grant.grantee||' CASCADE'; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN SELECT array_agg(r) INTO v_revoke FROM ( SELECT DISTINCT grantee::text AS r FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_partition_name EXCEPT SELECT unnest(v_grantees)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE ALL ON '||v_partition_name||' FROM '||array_to_string(v_revoke, ','); END IF; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' OWNER TO '||v_parent_owner; IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(quote_ident(v_parent_schema)||'.'||quote_ident(v_parent_tablename), v_partition_name); END IF; 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; END LOOP; IF v_analyze THEN EXECUTE 'ANALYZE '||p_parent_table; END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_partition_name; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE TABLE: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; -- Restore dropped object privileges DO $$ DECLARE v_row record; BEGIN FOR v_row IN SELECT statement FROM partman_preserve_privs_temp LOOP IF v_row.statement IS NOT NULL THEN EXECUTE v_row.statement; END IF; END LOOP; END $$; DROP TABLE IF EXISTS partman_preserve_privs_temp; pg_partman-4.6.0/updates/pg_partman--1.7.2--1.8.0.sql000066400000000000000000005020041412761706100214340ustar00rootroot00000000000000-- PG Partman now supports sub-partitioning. This allows automatic configuration to turn the child tables of an existing partition set into parent tables of their own partition sets. (Github Issue #26) -- New function "create_sub_parent()" works exactly like "create_parent()", even taking similar parameters. Instead, the parent_table you're giving it as a parameter is telling it which parent's child tables to partition and how to partition them. -- This can be chained down as many levels as desired. Just recall the 63 character limit on object names since this will be adding a new partition suffix every level down. The final suffix is always guarenteed to be added on in full, but the parent suffix name may get truncated off. -- Due to logical complexity (and possible contention issues at larger data sizes), when using subpartitioning, all parent tables at all partition levels are set to use run_maintenance() by default. This includes serial partitioning which normally by default can use a trigger based method to create future partitions. You can still set it to false so you can force maintenance to run at specific times (see new run_maintenance() feature below), but you MUST force it to run at some point otherwise new partitions will never be made. -- Note that there will ALWAYS be at least one child partition created, even for subpartition parents that are outside the current trigger range. Data outside the currently covered trigger range will still be inserted the the relevant parent. -- Note that for retention policies, whatever retention period is set on the highest level will be honored and ALL child tables will be dropped, cascading all the way down to the bottom. Use this option even more carefully! -- New parent table name parameter to run_maintenance(). If set, skips all other tables for that maintenance run and only does the one given. (Github Issue #32) -- This is an optional parameter, so should not affect any existing use of the function. When not given, maintenance is run for all partition sets set to use it in the part_config table. -- The already existing configuration option in part_config (use_run_maintenance) can be used to tell run_maintenance() to skip any partition sets for which you do not want it to run when no table name parameter is given. You can then schedule partition maintenance for specific tables to run at specific times using the new argument to run_maintenance(). Note that if a parent table argument is explicitely given to run_maintenance, it will always run the maintenance for it no matter what the configuration table has set. -- Note that when a table argument is given to run_maintenance(), retention settings will only be run for that one specific table given (if configured). -- Be aware that the "use_run_maintenance" configuration option is always set to true for time-based partitioning & subpartition sets and set false for serial based partitioning (when not subpartitioned) when calling create_parent() or create_sub_parent(). Adjust this configuration setting accordingly so run_maintenance() does what you require after you create your partition sets. -- The trigger constraint on the **part_config** table that would not allow "use_run_maintenance" to be set to false for time based partitioning has been removed. -- New analyze parameter to run_maintenance(). -- Defaults to true so that if any partition set has a new child table created, an analyze is run on that whole partition set. This is to ensure constraint exclusion works properly. -- Large partition sets were causing run_maintenance() to take a long time to run since the analyze would hold it up. This could cause some contention. -- Setting p_analyze to false will cause the analyze to not run for ALL partition sets that are eligible for new partition creation or retention management at the time it is called. -- If you set this to false, it is advised that you have some other means to ensure a regular analyze is being run on your partition sets. -- NOTE this parameter is set as the second argument since it's likely to be more commonly used, so make sure to check any current run_maintenence() calls to account for this (previously p_jobmon was the second parameter). -- Analyze is no longer automatically run on the parent table after create_parent() is run. Since create_parent() takes an exclusive lock on the parent table during setup, tables that already had a lot of existing data where being locked for the length of the analyze run, which could be quite long. When data is partitioned out later, analyze is automatically run. Also, whenever new partitions are created in the future, an analyze will be run as well (if the p_analyze argument to run_maintenance() is true which it is by default). Both those cases should take care of updating the planner statistics when it begins to matter. Run an analyze on the parent table after setup if you want to be sure. -- Fixed bug in show_partitions() that caused an error when the values in the control column of a serial partition set were larger than the max int value. This would also cause errors when partitioning existing data with values that high since the partitioning functions use show_partitions() internally. (Reported by S. Kristensen) -- create_parent() and new create_sub_parent() now return a boolean value to determine whether they succeeded. -- For all pythons scripts, changed the --connection default to "host=" instead of "host=localhost". This makes the default connection to the database use the local socket instead of TCP. Makes it act more predictibly like all other postgres executables (psql, pg_dump, etc). Please check any that you many have scheduled to run to ensure they are still working properly. -- Added a --version argument to all python scripts. This tells you the minimum version of pg_partman this script is meant to work with. -- Made sure all scripts in bin folder are added to Makefile for installation. -- Make sure autovacuum is reset if SIGINT (Ctrl+C) is fired when using partition_data.py or undo_partition.py. -- Added howto.md file to doc folder with some more extensive examples. -- last_partition column in part_config table no longer in use. Dropped it. -- Renamed internal functions create_id_partition(), create_id_function(), create_time_partition() & create_time_function() to create_partition_id(), create_function_id(), create_partition_time() & create_function_time() respectively. This gives all functions a consistent naming pattern. CREATE TEMP TABLE partman_preserve_privs_temp (statement text); INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_parent(text, text, text, text, text[], int, boolean, text, boolean, boolean, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_parent'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_partition_id(text, bigint[], boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_id_partition'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_partition_time(text, timestamp[], boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_time_partition'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_function_time(text) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_time_function'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_function_id(text) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_id_function'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.run_maintenance(text, boolean, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'run_maintenance'; DROP TRIGGER time_partition_maintenance_true_trig ON @extschema@.part_config; DROP FUNCTION @extschema@.time_partition_maintenance_true_trig(); DROP FUNCTION @extschema@.create_parent(text, text, text, text, text[], int, boolean, text, boolean, boolean, boolean); DROP FUNCTION @extschema@.create_id_partition (text, bigint[]); DROP FUNCTION @extschema@.create_time_partition (text, timestamp[]); DROP FUNCTION @extschema@.create_time_function(text); DROP FUNCTION @extschema@.create_id_function(text); DROP FUNCTION @extschema@.run_maintenance(boolean); -- FK set deferrable because create_parent() inserts to this table before part_config CREATE TABLE @extschema@.part_config_sub ( sub_parent text PRIMARY KEY REFERENCES @extschema@.part_config (parent_table) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED , sub_type text NOT NULL , sub_control text NOT NULL , sub_part_interval text NOT NULL , sub_constraint_cols text[] , sub_premake int NOT NULL DEFAULT 4 , sub_inherit_fk boolean NOT NULL DEFAULT true , sub_retention text , sub_retention_schema text , sub_retention_keep_table boolean NOT NULL DEFAULT true , sub_retention_keep_index boolean NOT NULL DEFAULT true , sub_use_run_maintenance BOOLEAN NOT NULL DEFAULT true , sub_jobmon boolean NOT NULL DEFAULT true ); ALTER TABLE @extschema@.part_config DROP COLUMN last_partition; /* * Ensure that sub-partitioned tables that are themselves sub-partitions have the same configuration options set when they are part of the same inheritance tree */ CREATE FUNCTION check_subpart_sameconfig(text) RETURNS boolean LANGUAGE sql STABLE AS $$ WITH child_tables AS ( SELECT n.nspname||'.'||c.relname AS tablename FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent::regclass = $1::regclass ) SELECT CASE WHEN count(*) <= 1 THEN true WHEN count(*) > 1 THEN false END FROM ( SELECT DISTINCT sub_type , sub_control , sub_part_interval , sub_constraint_cols , sub_premake , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_use_run_maintenance , sub_jobmon FROM @extschema@.part_config_sub a JOIN child_tables b on a.sub_parent = b.tablename) x; $$; ALTER TABLE @extschema@.part_config_sub ADD CONSTRAINT subpart_sameconfig_chk CHECK (check_subpart_sameconfig(sub_parent)); /* * 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. * To avoid logical complications and contention issues, ALL subpartitions must be maintained using run_maintenance(). * This means the automatic, trigger based partition creation for serial partitioning will not work if it is a subpartition. */ CREATE FUNCTION create_sub_parent( p_top_parent text , p_control text , p_type text , p_interval text , p_constraint_cols text[] DEFAULT NULL , p_premake int DEFAULT 4 , p_start_partition text DEFAULT NULL , p_inherit_fk boolean DEFAULT true , p_jobmon boolean DEFAULT true , p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_last_partition text; v_row record; v_row_last_part record; v_run_maint boolean; v_sql text; v_success boolean := false; v_top_type text; BEGIN SELECT use_run_maintenance INTO v_run_maint FROM @extschema@.part_config WHERE parent_table = p_top_parent; IF v_run_maint 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; ELSIF v_run_maint = false THEN RAISE EXCEPTION 'Any parent table that will be part of a sub-partitioned set (on any level) must have use_run_maintenance set to true in part_config table, even for serial partitioning. See documentation for more info.'; END IF; FOR v_row IN -- Loop through all current children to turn them into partitioned tables SELECT show_partitions AS child_table FROM @extschema@.show_partitions(p_top_parent) LOOP -- Just call existing create_parent() function but add the given parameters to the part_config_sub table as well 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_use_run_maintenance := %L , p_start_partition := %L , p_inherit_fk := %L , p_jobmon := %L , p_debug := %L )' , v_row.child_table , p_control , p_type , p_interval , p_constraint_cols , p_premake , true , p_start_partition , p_inherit_fk , p_jobmon , p_debug); EXECUTE v_sql; END LOOP; INSERT INTO @extschema@.part_config_sub ( sub_parent , sub_control , sub_type , sub_part_interval , sub_constraint_cols , sub_premake , sub_inherit_fk , sub_use_run_maintenance , sub_jobmon) VALUES ( p_top_parent , p_control , p_type , p_interval , p_constraint_cols , p_premake , p_inherit_fk , true , p_jobmon); v_success := true; RETURN v_success; END $$; /* * Function to create id partitions */ CREATE FUNCTION create_partition_id(p_parent_table text, p_partition_ids bigint[], p_analyze boolean DEFAULT true) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_grantees text[]; v_hasoids boolean; v_id bigint; v_id_position int; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_parent_tablespace text; v_part_interval bigint; v_partition_created boolean := false; v_partition_name text; v_revoke text[]; v_row record; v_sql text; v_step_id bigint; v_sub_id_max bigint; v_sub_id_min bigint; v_tablename text; v_top_interval bigint; v_top_parent text; v_unlogged char; BEGIN SELECT control , part_interval , inherit_fk , jobmon INTO v_control , v_part_interval , v_inherit_fk , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; -- Check if parent table is a subpartition of an already existing id based partition set managed by pg_partman -- If so, limit what child tables can be created based on parent suffix WITH top_oid AS ( SELECT i.inhparent AS top_parent_oid FROM pg_catalog.pg_class c JOIN pg_catalog.pg_inherits i ON c.oid = i.inhrelid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname||'.'||c.relname = p_parent_table ) SELECT n.nspname||'.'||c.relname INTO v_top_parent FROM pg_catalog.pg_class c JOIN top_oid t ON c.oid = t.top_parent_oid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace JOIN @extschema@.part_config p ON p.parent_table = n.nspname||'.'||c.relname WHERE c.oid = t.top_parent_oid AND p.type = 'id-static' OR p.type = 'id-dynamic'; IF v_top_parent IS NOT NULL THEN SELECT part_interval::bigint INTO v_top_interval FROM @extschema@.part_config WHERE parent_table = v_top_parent; v_id_position := (length(p_parent_table) - position('p_' in reverse(p_parent_table))) + 2; v_sub_id_min = substring(p_parent_table from v_id_position)::bigint; v_sub_id_max = (v_sub_id_min + v_top_interval) - 1; END IF; SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; FOREACH v_id IN ARRAY p_partition_ids LOOP -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_id_min IS NOT NULL THEN IF v_id < v_sub_id_min OR v_id > v_sub_id_max THEN CONTINUE; END IF; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_id::text, TRUE); -- If child table already exists, skip creation SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_tablename IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_id||' to '||(v_id + v_part_interval)-1); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class WHERE oid::regclass = p_parent_table::regclass; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; v_sql := v_sql || ' TABLE '||v_partition_name||' (LIKE '||p_parent_table||' INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)'; SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class WHERE oid::regclass = p_parent_table::regclass; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_parent_tablespace IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_partition_name||' SET TABLESPACE '||v_parent_tablespace; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' ADD CONSTRAINT '||v_tablename||'_partition_check CHECK ('||v_control||'>='||quote_literal(v_id)||' AND '||v_control||'<'||quote_literal(v_id + v_part_interval)||')'; EXECUTE 'ALTER TABLE '||v_partition_name||' INHERIT '||p_parent_table; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP EXECUTE 'GRANT '||array_to_string(v_parent_grant.types, ',')||' ON '||v_partition_name||' TO '||v_parent_grant.grantee; SELECT array_agg(r) INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE '||array_to_string(v_revoke, ',')||' ON '||v_partition_name||' FROM '||v_parent_grant.grantee||' CASCADE'; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN SELECT array_agg(r) INTO v_revoke FROM ( SELECT DISTINCT grantee::text AS r FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_partition_name EXCEPT SELECT unnest(v_grantees)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE ALL ON '||v_partition_name||' FROM '||array_to_string(v_revoke, ','); END IF; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' OWNER TO '||v_parent_owner; IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(quote_ident(v_parent_schema)||'.'||quote_ident(v_parent_tablename), v_partition_name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_control , sub_type , sub_part_interval , sub_constraint_cols , sub_premake , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_use_run_maintenance , sub_jobmon FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Subpartitioning '||v_partition_name); END IF; 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_use_run_maintenance := %L , p_inherit_fk := %L , p_jobmon := %L )' , v_partition_name , v_row.sub_control , v_row.sub_type , v_row.sub_part_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_inherit_fk , v_row.sub_use_run_maintenance , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index WHERE parent_table = v_partition_name; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; -- end sub partitioning LOOP IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN EXECUTE 'ANALYZE '||p_parent_table; END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_partition_created; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE TABLE: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to create a child table in a time-based partition set */ CREATE FUNCTION create_partition_time (p_parent_table text, p_partition_times timestamp[], p_analyze boolean DEFAULT true) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_datetime_string text; v_grantees text[]; v_hasoids boolean; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_partition_created boolean := false; v_partition_name text; v_partition_suffix text; v_parent_tablespace text; v_part_interval interval; v_partition_timestamp_end timestamp; v_partition_timestamp_start timestamp; v_quarter text; v_revoke text[]; v_row record; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_sub_timestamp_max timestamp; v_sub_timestamp_min timestamp; v_tablename text; v_time_position int; v_top_interval interval; v_top_parent text; v_trunc_value text; v_time timestamp; v_type text; v_unlogged char; v_year text; BEGIN SELECT type , control , part_interval , inherit_fk , jobmon , datetime_string INTO v_type , v_control , v_part_interval , v_inherit_fk , v_jobmon , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic' OR type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; -- Check if parent table is a subpartition of an already existing time-based partition set managed by pg_partman -- If so, limit what child tables can be created based on parent suffix WITH top_oid AS ( SELECT i.inhparent AS top_parent_oid FROM pg_catalog.pg_class c JOIN pg_catalog.pg_inherits i ON c.oid = i.inhrelid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname||'.'||c.relname = p_parent_table ) SELECT n.nspname||'.'||c.relname INTO v_top_parent FROM pg_catalog.pg_class c JOIN top_oid t ON c.oid = t.top_parent_oid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace JOIN @extschema@.part_config p ON p.parent_table = n.nspname||'.'||c.relname WHERE c.oid = t.top_parent_oid AND p.type = 'time-static' OR p.type = 'time-dynamic'; IF v_top_parent IS NOT NULL THEN SELECT part_interval::interval INTO v_top_interval FROM @extschema@.part_config WHERE parent_table = v_top_parent; v_time_position := (length(p_parent_table) - position('p_' in reverse(p_parent_table))) + 2; IF v_part_interval::interval <> '3 months' OR (v_part_interval::interval = '3 months' AND v_type = 'time-custom') THEN v_sub_timestamp_min := to_timestamp(substring(p_parent_table from v_time_position), v_datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(p_parent_table from v_time_position), 'q', 1); v_quarter := split_part(substring(p_parent_table from v_time_position), 'q', 2); CASE WHEN v_quarter = '1' THEN v_sub_timestamp_min := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_sub_timestamp_min := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_sub_timestamp_min := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_sub_timestamp_min := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; v_sub_timestamp_max = (v_sub_timestamp_min + v_top_interval::interval) - '1 sec'::interval; END IF; SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_timestamp_start := v_time; BEGIN v_partition_timestamp_end := v_time + v_part_interval; 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_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_time||' skipped'); CONTINUE; END; -- This suffix generation code is in partition_data_time() as well v_partition_suffix := to_char(v_time, 'YYYY'); IF v_part_interval < '1 year' AND v_part_interval <> '1 week' THEN v_partition_suffix := v_partition_suffix ||'_'|| to_char(v_time, 'MM'); IF v_part_interval < '1 month' AND v_part_interval <> '1 week' THEN v_partition_suffix := v_partition_suffix ||'_'|| to_char(v_time, 'DD'); IF v_part_interval < '1 day' THEN v_partition_suffix := v_partition_suffix || '_' || to_char(v_time, 'HH24MI'); IF v_part_interval < '1 minute' THEN v_partition_suffix := v_partition_suffix || to_char(v_time, 'SS'); END IF; -- end < minute IF END IF; -- end < day IF END IF; -- end < month IF END IF; -- end < year IF IF v_part_interval = '1 week' THEN v_partition_suffix := to_char(v_time, 'IYYY') || 'w' || to_char(v_time, 'IW'); END IF; -- "Q" is ignored in to_timestamp, so handle special case IF v_part_interval = '3 months' AND (v_type = 'time-static' OR v_type = 'time-dynamic') THEN v_year := to_char(v_time, 'YYYY'); v_quarter := to_char(v_time, 'Q'); v_partition_suffix := v_year || 'q' || v_quarter; END IF; -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_timestamp_min IS NOT NULL THEN IF v_time < v_sub_timestamp_min OR v_time > v_sub_timestamp_max THEN CONTINUE; END IF; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_partition_suffix, TRUE); SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_tablename IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_partition_timestamp_start||' to '||(v_partition_timestamp_end-'1sec'::interval)); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class WHERE oid::regclass = p_parent_table::regclass; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; v_sql := v_sql || ' TABLE '||v_partition_name||' (LIKE '||p_parent_table||' INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)'; SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class WHERE oid::regclass = p_parent_table::regclass; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_parent_tablespace IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_partition_name||' SET TABLESPACE '||v_parent_tablespace; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' ADD CONSTRAINT '||v_tablename||'_partition_check CHECK ('||v_control||'>='||quote_literal(v_partition_timestamp_start)||' AND '||v_control||'<'||quote_literal(v_partition_timestamp_end)||')'; EXECUTE 'ALTER TABLE '||v_partition_name||' INHERIT '||p_parent_table; -- If custom time, set extra config options. IF v_type = 'time-custom' THEN INSERT INTO @extschema@.custom_time_partitions (parent_table, child_table, partition_range) VALUES ( p_parent_table, v_partition_name, tstzrange(v_partition_timestamp_start, v_partition_timestamp_end, '[)') ); END IF; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP EXECUTE 'GRANT '||array_to_string(v_parent_grant.types, ',')||' ON '||v_partition_name||' TO '||v_parent_grant.grantee; SELECT array_agg(r) INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE '||array_to_string(v_revoke, ',')||' ON '||v_partition_name||' FROM '||v_parent_grant.grantee||' CASCADE'; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN SELECT array_agg(r) INTO v_revoke FROM ( SELECT DISTINCT grantee::text AS r FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_partition_name EXCEPT SELECT unnest(v_grantees)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE ALL ON '||v_partition_name||' FROM '||array_to_string(v_revoke, ','); END IF; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' OWNER TO '||v_parent_owner; IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(quote_ident(v_parent_schema)||'.'||quote_ident(v_parent_tablename), v_partition_name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_control , sub_type , sub_part_interval , sub_constraint_cols , sub_premake , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_use_run_maintenance , sub_jobmon FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Subpartitioning '||v_partition_name); END IF; 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_use_run_maintenance := %L , p_inherit_fk := %L , p_jobmon := %L )' , v_partition_name , v_row.sub_control , v_row.sub_type , v_row.sub_part_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_inherit_fk , v_row.sub_use_run_maintenance , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index WHERE parent_table = v_partition_name; END LOOP; -- end sub partitioning LOOP IF v_jobmon_schema IS NOT NULL THEN 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; v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN EXECUTE 'ANALYZE '||p_parent_table; END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_partition_created; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE TABLE: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to turn a table into the parent of a partition set */ CREATE FUNCTION 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_use_run_maintenance boolean DEFAULT NULL , p_start_partition text DEFAULT NULL , p_inherit_fk boolean DEFAULT true , p_jobmon boolean DEFAULT true , p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_base_timestamp timestamp; v_count int := 1; v_datetime_string text; v_higher_parent text := p_parent_table; v_id_interval bigint; v_id_position int; v_job_id bigint; v_jobmon_schema text; v_last_partition_created boolean; v_max bigint; v_notnull boolean; v_old_search_path text; v_parent_partition_id bigint; v_parent_partition_timestamp timestamp; v_partition_time timestamp; v_partition_time_array timestamp[]; v_partition_id_array bigint[]; v_row record; v_run_maint boolean; v_sql text; v_start_time timestamp; v_starting_partition_id bigint; v_step_id bigint; v_step_overflow_id bigint; v_sub_parent text; v_success boolean := false; v_tablename text; v_time_interval interval; v_time_position int; v_top_parent text := p_parent_table; BEGIN IF position('.' in p_parent_table) = 0 THEN RAISE EXCEPTION 'Parent table must be schema qualified'; END IF; SELECT tablename INTO v_tablename FROM pg_tables WHERE schemaname || '.' || tablename = p_parent_table; IF v_tablename IS NULL THEN RAISE EXCEPTION 'Please create given parent table first: %', p_parent_table; END IF; SELECT attnotnull INTO v_notnull FROM pg_attribute WHERE attrelid = p_parent_table::regclass AND attname = p_control; IF v_notnull = false OR v_notnull IS NULL THEN RAISE EXCEPTION 'Control column (%) for parent table (%) must be NOT NULL', p_control, p_parent_table; END IF; IF NOT @extschema@.check_partition_type(p_type) THEN RAISE EXCEPTION '% is not a valid partitioning type', p_type; END IF; IF p_type = 'time-custom' AND @extschema@.check_version('9.2.0') IS FALSE THEN RAISE EXCEPTION 'The "time-custom" type requires a minimum PostgreSQL version of 9.2.0'; END IF; EXECUTE 'LOCK TABLE '||p_parent_table||' IN ACCESS EXCLUSIVE MODE'; IF p_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF p_use_run_maintenance IS NOT NULL THEN IF p_use_run_maintenance IS FALSE AND (p_type = 'time-static' OR p_type = 'time-dynamic' OR p_type = 'time-custom') THEN RAISE EXCEPTION 'p_run_maintenance cannot be set to false for time based partitioning'; END IF; v_run_maint := p_use_run_maintenance; ELSIF p_type = 'time-static' OR p_type = 'time-dynamic' OR p_type = 'time-custom' THEN v_run_maint := TRUE; ELSIF p_type = 'id-static' OR p_type ='id-dynamic' THEN v_run_maint := FALSE; ELSE RAISE EXCEPTION 'use_run_maintenance value cannot be set NULL'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN SETUP PARENT: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating initial partitions on new parent table: '||p_parent_table); END IF; -- If this parent table has siblings that are also partitioned (subpartitions), ensure it 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_inherits h where h.inhrelid::regclass = p_parent_table::regclass ), sibling_children as ( select i.inhrelid::regclass::text as tablename from pg_inherits i join parent_table p on i.inhparent = p.parent_oid ) SELECT DISTINCT sub_type , sub_control , sub_part_interval , sub_constraint_cols , sub_premake , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_use_run_maintenance , sub_jobmon 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_type , sub_control , sub_part_interval , sub_constraint_cols , sub_premake , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_use_run_maintenance , sub_jobmon) VALUES ( p_parent_table , v_row.sub_type , v_row.sub_control , v_row.sub_part_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_use_run_maintenance , v_row.sub_jobmon); END LOOP; IF p_type = 'time-static' OR p_type = 'time-dynamic' OR p_type = 'time-custom' 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 <> 'time-custom' THEN RAISE EXCEPTION 'Must use a predefined time interval if not using type "time-custom". See documentation.'; 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::timestamp, 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))::timestamp; 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 , type , part_interval , control , premake , constraint_cols , datetime_string , use_run_maintenance , inherit_fk , jobmon) VALUES ( p_parent_table , p_type , v_time_interval , p_control , p_premake , p_constraint_cols , v_datetime_string , v_run_maint , p_inherit_fk , p_jobmon); 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 n.nspname||'.'||c.relname = p_parent_table ) SELECT n.nspname||'.'||c.relname INTO v_top_parent 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 IS NOT NULL THEN -- If so create the lowest possible partition that is within the boundary of the parent v_time_position := (length(p_parent_table) - position('p_' in reverse(p_parent_table))) + 2; v_parent_partition_timestamp := to_timestamp(substring(p_parent_table from v_time_position), v_datetime_string); 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 -- Currently unknown edge case if code gets here RAISE EXCEPTION 'No child tables created. Unexpected edge case encountered. Please report this error to author with conditions that led to it.'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Time partitions premade: '||p_premake); END IF; END IF; IF p_type = 'id-static' OR p_type = 'id-dynamic' THEN v_id_interval := p_interval::bigint; IF v_id_interval <= 1 THEN RAISE EXCEPTION 'Interval for serial partitioning must be greater than 1'; END IF; -- Check if parent table is a subpartition of an already existing id partition set managed by pg_partman. WHILE v_higher_parent 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||'.'||c.relname = v_higher_parent ) SELECT n.nspname||'.'||c.relname INTO v_higher_parent 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 WHERE p.type = 'id-static' OR p.type = 'id-dynamic'; IF v_higher_parent IS NOT NULL THEN -- v_top_parent initially set in DECLARE v_top_parent := v_higher_parent; 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 v_sql := 'SELECT COALESCE('||quote_nullable(p_start_partition::bigint)||', max('||p_control||')::bigint, 0) FROM '||v_top_parent||' LIMIT 1'; EXECUTE v_sql INTO v_max; 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 , type , part_interval , control , premake , constraint_cols , use_run_maintenance , inherit_fk , jobmon) VALUES ( p_parent_table , p_type , v_id_interval , p_control , p_premake , p_constraint_cols , v_run_maint , p_inherit_fk , p_jobmon); 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 n.nspname||'.'||c.relname = p_parent_table ) SELECT n.nspname||'.'||c.relname INTO v_top_parent 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 WHERE p.type = 'id-static' OR p.type = 'id-dynamic'; IF v_top_parent IS NOT NULL THEN -- Create the lowest possible partition that is within the boundary of the parent v_id_position := (length(p_parent_table) - position('p_' in reverse(p_parent_table))) + 2; v_parent_partition_id = substring(p_parent_table from v_id_position)::bigint; 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 EXCEPTION 'No child tables created. Unexpected edge case encountered. Please report this error to author with conditions that led to it.'; END IF; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating partition function'); END IF; IF p_type = 'time-static' OR p_type = 'time-dynamic' OR p_type = 'time-custom' THEN PERFORM @extschema@.create_function_time(p_parent_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Time function created'); END IF; ELSIF p_type = 'id-static' OR p_type = 'id-dynamic' THEN PERFORM @extschema@.create_function_id(p_parent_table); 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); 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; EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; v_success := true; RETURN v_success; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE PARENT: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''Partition creation for table '||p_parent_table||' failed'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Create the trigger function for the parent table of a time-based partition set */ CREATE FUNCTION create_function_time(p_parent_table text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_count int; v_current_partition_name text; v_current_partition_timestamp timestamptz; v_datetime_string text; v_final_partition_timestamp timestamptz; v_function_name text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_new_length int; v_next_partition_name text; v_next_partition_timestamp timestamptz; v_parent_schema text; v_parent_tablename text; v_part_interval interval; v_premake int; v_prev_partition_name text; v_prev_partition_timestamp timestamptz; v_step_id bigint; v_trig_func text; v_type text; BEGIN SELECT type , part_interval::interval , control , premake , datetime_string , jobmon INTO v_type , v_part_interval , v_control , v_premake , v_datetime_string , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic' OR type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE FUNCTION: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating partition function for table '||p_parent_table); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_function_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, '_part_trig_func', FALSE); IF v_type = 'time-static' THEN CASE WHEN v_part_interval = '15 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_part_interval = '30 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_part_interval = '1 hour' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_part_interval = '1 day' THEN v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_part_interval = '1 week' THEN v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_part_interval = '1 month' THEN v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); -- Type time-static plus this interval is the special quarterly interval WHEN v_part_interval = '3 months' THEN v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_part_interval = '1 year' THEN v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, to_char(v_current_partition_timestamp, v_datetime_string), TRUE); v_next_partition_timestamp := v_current_partition_timestamp + v_part_interval::interval; v_trig_func := 'CREATE OR REPLACE FUNCTION '||v_function_name||'() RETURNS trigger LANGUAGE plpgsql AS $t$ BEGIN IF TG_OP = ''INSERT'' THEN IF NEW.'||v_control||' >= '||quote_literal(v_current_partition_timestamp)||' AND NEW.'||v_control||' < '||quote_literal(v_next_partition_timestamp)|| ' THEN '; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_current_partition_name; IF v_count > 0 THEN v_trig_func := v_trig_func || ' INSERT INTO '||v_current_partition_name||' VALUES (NEW.*); '; ELSE v_trig_func := v_trig_func || ' -- Child table for current values does not exist in this partition set, so write to parent RETURN NEW;'; END IF; FOR i IN 1..v_premake LOOP v_prev_partition_timestamp := v_current_partition_timestamp - (v_part_interval::interval * i); v_next_partition_timestamp := v_current_partition_timestamp + (v_part_interval::interval * i); v_final_partition_timestamp := v_next_partition_timestamp + (v_part_interval::interval); v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, to_char(v_prev_partition_timestamp, v_datetime_string), TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, to_char(v_next_partition_timestamp, v_datetime_string), TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles edge case of changing premake immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_prev_partition_name; IF v_count > 0 THEN v_trig_func := v_trig_func ||' ELSIF NEW.'||v_control||' >= '||quote_literal(v_prev_partition_timestamp)||' AND NEW.'||v_control||' < '|| quote_literal(v_prev_partition_timestamp + v_part_interval::interval)|| ' THEN INSERT INTO '||v_prev_partition_name||' VALUES (NEW.*);'; END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_next_partition_name; IF v_count > 0 THEN v_trig_func := v_trig_func ||' ELSIF NEW.'||v_control||' >= '||quote_literal(v_next_partition_timestamp)||' AND NEW.'||v_control||' < '|| quote_literal(v_final_partition_timestamp)|| ' THEN INSERT INTO '||v_next_partition_name||' VALUES (NEW.*);'; END IF; END LOOP; v_trig_func := v_trig_func ||' ELSE RETURN NEW; END IF; END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for current time interval: '|| v_current_partition_timestamp||' to '||(v_final_partition_timestamp-'1sec'::interval)); END IF; ELSIF v_type = 'time-dynamic' THEN v_trig_func := 'CREATE OR REPLACE FUNCTION '||v_function_name||'() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_partition_name text; v_partition_timestamp timestamptz; BEGIN IF TG_OP = ''INSERT'' THEN '; CASE WHEN v_part_interval = '15 mins' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''hour'', NEW.'||v_control||') + ''15min''::interval * floor(date_part(''minute'', NEW.'||v_control||') / 15.0);'; WHEN v_part_interval = '30 mins' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''hour'', NEW.'||v_control||') + ''30min''::interval * floor(date_part(''minute'', NEW.'||v_control||') / 30.0);'; WHEN v_part_interval = '1 hour' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''hour'', NEW.'||v_control||');'; WHEN v_part_interval = '1 day' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''day'', NEW.'||v_control||');'; WHEN v_part_interval = '1 week' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''week'', NEW.'||v_control||');'; WHEN v_part_interval = '1 month' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''month'', NEW.'||v_control||');'; WHEN v_part_interval = '3 months' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''quarter'', NEW.'||v_control||');'; WHEN v_part_interval = '1 year' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''year'', NEW.'||v_control||');'; END CASE; v_trig_func := v_trig_func||' v_partition_name := @extschema@.check_name_length('''||v_parent_tablename||''', '''||v_parent_schema||''', to_char(v_partition_timestamp, '||quote_literal(v_datetime_string)||'), TRUE); SELECT count(*) INTO v_count FROM pg_tables WHERE schemaname ||''.''|| tablename = v_partition_name; IF v_count > 0 THEN EXECUTE ''INSERT INTO ''||v_partition_name||'' VALUES($1.*)'' USING NEW; ELSE RETURN NEW; END IF; END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for dynamic time table: '||p_parent_table); END IF; ELSIF v_type = 'time-custom' THEN v_trig_func := 'CREATE OR REPLACE FUNCTION '||v_function_name||'() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_child_table text; v_count int; BEGIN SELECT child_table INTO v_child_table FROM @extschema@.custom_time_partitions WHERE partition_range @> NEW.'||v_control||' AND parent_table = '||quote_literal(p_parent_table)||'; SELECT count(*) INTO v_count FROM pg_tables WHERE schemaname ||''.''|| tablename = v_child_table; IF v_count > 0 THEN EXECUTE ''INSERT INTO ''||v_child_table||'' VALUES ($1.*)'' USING NEW; ELSE RETURN NEW; END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for custom time table: '||p_parent_table); END IF; ELSE RAISE EXCEPTION 'ERROR: Invalid time partitioning type given: %', v_type; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE FUNCTION: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Create the trigger function for the parent table of an id-based partition set */ CREATE FUNCTION create_function_id(p_parent_table text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_count int; v_current_partition_name text; v_current_partition_id bigint; v_datetime_string text; v_final_partition_id bigint; v_function_name text; v_higher_parent text := p_parent_table; v_id_position int; v_job_id bigint; v_jobmon text; v_jobmon_schema text; v_last_partition text; v_max bigint; v_next_partition_id bigint; v_next_partition_name text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_part_interval bigint; v_premake int; v_prev_partition_id bigint; v_prev_partition_name text; v_run_maint boolean; v_step_id bigint; v_top_parent text := p_parent_table; v_trig_func text; v_type text; BEGIN SELECT type , part_interval::bigint , control , premake , use_run_maintenance , jobmon INTO v_type , v_part_interval , v_control , v_premake , v_run_maint , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT show_partitions INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE FUNCTION: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating partition function for table '||p_parent_table); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_function_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, '_part_trig_func', FALSE); IF v_type = 'id-static' THEN -- Get the highest level top parent if multi-level partitioned in order to get proper max() value below WHILE v_higher_parent 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||'.'||c.relname = v_higher_parent ) SELECT n.nspname||'.'||c.relname INTO v_higher_parent 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 WHERE p.type = 'id-static' OR p.type = 'id-dynamic'; IF v_higher_parent IS NOT NULL THEN -- initially set in DECLARE v_top_parent := v_higher_parent; END IF; END LOOP; EXECUTE 'SELECT COALESCE(max('||v_control||'), 0) FROM '||v_top_parent INTO v_max; v_current_partition_id = v_max - (v_max % v_part_interval); v_next_partition_id := v_current_partition_id + v_part_interval; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_current_partition_id::text, TRUE); v_trig_func := 'CREATE OR REPLACE FUNCTION '||v_function_name||'() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_current_partition_id bigint; v_last_partition text := '||quote_literal(v_last_partition)||'; v_id_position int; v_next_partition_id bigint; v_next_partition_name text; v_partition_created boolean; BEGIN IF TG_OP = ''INSERT'' THEN IF NEW.'||v_control||' >= '||v_current_partition_id||' AND NEW.'||v_control||' < '||v_next_partition_id|| ' THEN '; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_current_partition_name; IF v_count > 0 THEN v_trig_func := v_trig_func || ' INSERT INTO '||v_current_partition_name||' VALUES (NEW.*); '; ELSE v_trig_func := v_trig_func || ' -- Child table for current values does not exist in this partition set, so write to parent RETURN NEW;'; END IF; FOR i IN 1..v_premake LOOP v_prev_partition_id := v_current_partition_id - (v_part_interval * i); v_next_partition_id := v_current_partition_id + (v_part_interval * i); v_final_partition_id := v_next_partition_id + v_part_interval; v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_prev_partition_id::text, TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_next_partition_id::text, TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles edge case of changing premake immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_prev_partition_name; IF v_count > 0 THEN -- Only handle previous partitions if they're starting above zero IF v_prev_partition_id >= 0 THEN v_trig_func := v_trig_func ||' ELSIF NEW.'||v_control||' >= '||v_prev_partition_id||' AND NEW.'||v_control||' < '||v_prev_partition_id + v_part_interval|| ' THEN INSERT INTO '||v_prev_partition_name||' VALUES (NEW.*); '; END IF; END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_next_partition_name; IF v_count > 0 THEN v_trig_func := v_trig_func ||' ELSIF NEW.'||v_control||' >= '||v_next_partition_id||' AND NEW.'||v_control||' < '||v_final_partition_id|| ' THEN INSERT INTO '||v_next_partition_name||' VALUES (NEW.*);'; END IF; END LOOP; v_trig_func := v_trig_func ||' ELSE RETURN NEW; END IF;'; IF v_run_maint IS FALSE THEN v_trig_func := v_trig_func ||' v_current_partition_id := NEW.'||v_control||' - (NEW.'||v_control||' % '||v_part_interval||'); IF (NEW.'||v_control||' % '||v_part_interval||') > ('||v_part_interval||' / 2) THEN v_id_position := (length(v_last_partition) - position(''p_'' in reverse(v_last_partition))) + 2; v_next_partition_id := (substring(v_last_partition from v_id_position)::bigint) + '||v_part_interval||'; WHILE ((v_next_partition_id - v_current_partition_id) / '||v_part_interval||') <= '||v_premake||' LOOP v_partition_created := @extschema@.create_partition_id('||quote_literal(p_parent_table)||', ARRAY[v_next_partition_id]); IF v_partition_created THEN PERFORM @extschema@.create_function_id('||quote_literal(p_parent_table)||'); PERFORM @extschema@.apply_constraints('||quote_literal(p_parent_table)||'); END IF; v_next_partition_id := v_next_partition_id + '||v_part_interval||'; END LOOP; END IF;'; END IF; v_trig_func := v_trig_func ||' END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for current id interval: '||v_current_partition_id||' to '||v_final_partition_id-1); END IF; ELSIF v_type = 'id-dynamic' THEN -- The return inside the partition creation check is there to keep really high ID values from creating new partitions. v_trig_func := 'CREATE OR REPLACE FUNCTION '||v_function_name||'() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_current_partition_id bigint; v_current_partition_name text; v_id_position int; v_last_partition text := '||quote_literal(v_last_partition)||'; v_last_partition_id bigint; v_next_partition_id bigint; v_next_partition_name text; v_partition_created boolean; BEGIN IF TG_OP = ''INSERT'' THEN v_current_partition_id := NEW.'||v_control||' - (NEW.'||v_control||' % '||v_part_interval||'); v_current_partition_name := @extschema@.check_name_length('''||v_parent_tablename||''', '''||v_parent_schema||''', v_current_partition_id::text, TRUE); SELECT count(*) INTO v_count FROM pg_tables WHERE schemaname ||''.''|| tablename = v_current_partition_name; IF v_count > 0 THEN EXECUTE ''INSERT INTO ''||v_current_partition_name||'' VALUES($1.*)'' USING NEW; ELSE RETURN NEW; END IF;'; IF v_run_maint IS FALSE THEN v_trig_func := v_trig_func ||' IF (NEW.'||v_control||' % '||v_part_interval||') > ('||v_part_interval||' / 2) THEN v_id_position := (length(v_last_partition) - position(''p_'' in reverse(v_last_partition))) + 2; v_last_partition_id = substring(v_last_partition from v_id_position)::bigint; v_next_partition_id := v_last_partition_id + '||v_part_interval||'; IF NEW.'||v_control||' >= v_next_partition_id THEN RETURN NEW; END IF; WHILE ((v_next_partition_id - v_current_partition_id) / '||v_part_interval||') <= '||v_premake||' LOOP v_partition_created := @extschema@.create_partition_id('||quote_literal(p_parent_table)||', ARRAY[v_next_partition_id]); IF v_partition_created THEN PERFORM @extschema@.create_function_id('||quote_literal(p_parent_table)||'); PERFORM @extschema@.apply_constraints('||quote_literal(p_parent_table)||'); END IF; v_next_partition_id := v_next_partition_id + '||v_part_interval||'; END LOOP; END IF;'; END IF; v_trig_func := v_trig_func ||' END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for dynamic id table: '||p_parent_table); END IF; ELSE RAISE EXCEPTION 'ERROR: Invalid id partitioning type given: %', v_type; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE FUNCTION: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''Partition function maintenance for table '||p_parent_table||' failed'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Populate the child table(s) of a time-based partition set with old data from the original parent */ CREATE OR REPLACE FUNCTION partition_data_time(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval interval DEFAULT NULL, p_lock_wait numeric DEFAULT 0, p_order text DEFAULT 'ASC') RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_datetime_string text; v_current_partition_name text; v_max_partition_timestamp timestamp; v_last_partition text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_min_partition_timestamp timestamp; v_parent_schema text; v_parent_tablename text; v_part_interval interval; v_partition_suffix text; v_partition_timestamp timestamp[]; v_quarter text; v_rowcount bigint; v_sql text; v_start_control timestamp; v_time_position int; v_total_rows bigint := 0; v_type text; v_year text; BEGIN SELECT type , part_interval::interval , control , datetime_string INTO v_type , v_part_interval , v_control , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic' OR type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF p_batch_interval IS NULL OR p_batch_interval > v_part_interval THEN p_batch_interval := v_part_interval; END IF; SELECT show_partitions INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; FOR i IN 1..p_batch_count LOOP IF p_order = 'ASC' THEN EXECUTE 'SELECT min('||v_control||') FROM ONLY '||p_parent_table INTO v_start_control; ELSIF p_order = 'DESC' THEN EXECUTE 'SELECT max('||v_control||') FROM ONLY '||p_parent_table INTO v_start_control; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; IF v_start_control IS NULL THEN EXIT; END IF; IF v_type = 'time-static' OR v_type = 'time-dynamic' THEN CASE WHEN v_part_interval = '15 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '15min'::interval * floor(date_part('minute', v_start_control) / 15.0); WHEN v_part_interval = '30 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '30min'::interval * floor(date_part('minute', v_start_control) / 30.0); WHEN v_part_interval = '1 hour' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control); WHEN v_part_interval = '1 day' THEN v_min_partition_timestamp := date_trunc('day', v_start_control); WHEN v_part_interval = '1 week' THEN v_min_partition_timestamp := date_trunc('week', v_start_control); WHEN v_part_interval = '1 month' THEN v_min_partition_timestamp := date_trunc('month', v_start_control); WHEN v_part_interval = '3 months' THEN v_min_partition_timestamp := date_trunc('quarter', v_start_control); WHEN v_part_interval = '1 year' THEN v_min_partition_timestamp := date_trunc('year', v_start_control); END CASE; ELSIF v_type = 'time-custom' THEN -- Keep going backwards, checking if the time interval encompases the current v_start_control value v_time_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; v_min_partition_timestamp := to_timestamp(substring(v_last_partition from v_time_position), v_datetime_string); v_max_partition_timestamp := v_min_partition_timestamp + v_part_interval; LOOP IF v_start_control >= v_min_partition_timestamp AND v_start_control < v_max_partition_timestamp THEN EXIT; ELSE v_max_partition_timestamp := v_min_partition_timestamp; BEGIN v_min_partition_timestamp := v_min_partition_timestamp - v_part_interval; EXCEPTION WHEN datetime_field_overflow THEN RAISE EXCEPTION 'Attempted partition time interval is outside PostgreSQL''s supported time range. Unable to create partition with interval before timestamp % ', v_min_partition_interval; END; END IF; END LOOP; END IF; v_partition_timestamp := ARRAY[v_min_partition_timestamp]; IF p_order = 'ASC' THEN IF (v_start_control + p_batch_interval) >= (v_min_partition_timestamp + v_part_interval) THEN v_max_partition_timestamp := v_min_partition_timestamp + v_part_interval; ELSE v_max_partition_timestamp := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_timestamp := v_min_partition_timestamp + v_part_interval; -- Make sure minimum doesn't underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_timestamp THEN v_min_partition_timestamp = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN v_sql := 'SELECT * FROM ONLY ' || p_parent_table || ' WHERE '||v_control||' >= '||quote_literal(v_min_partition_timestamp)|| ' AND '||v_control||' < '||quote_literal(v_max_partition_timestamp) ||' FOR UPDATE NOWAIT'; EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; PERFORM @extschema@.create_partition_time(p_parent_table, v_partition_timestamp); -- This suffix generation code is in create_partition_time() as well v_partition_suffix := to_char(v_min_partition_timestamp, 'YYYY'); IF v_part_interval < '1 year' AND v_part_interval <> '1 week' THEN v_partition_suffix := v_partition_suffix ||'_'|| to_char(v_min_partition_timestamp, 'MM'); IF v_part_interval < '1 month' AND v_part_interval <> '1 week' THEN v_partition_suffix := v_partition_suffix ||'_'|| to_char(v_min_partition_timestamp, 'DD'); IF v_part_interval < '1 day' THEN v_partition_suffix := v_partition_suffix || '_' || to_char(v_min_partition_timestamp, 'HH24MI'); IF v_part_interval < '1 minute' THEN v_partition_suffix := v_partition_suffix || to_char(v_min_partition_timestamp, 'SS'); END IF; -- end < minute IF END IF; -- end < day IF END IF; -- end < month IF END IF; -- end < year IF IF v_part_interval = '1 week' THEN v_partition_suffix := to_char(v_min_partition_timestamp, 'IYYY') || 'w' || to_char(v_min_partition_timestamp, 'IW'); END IF; -- "Q" is ignored in to_timestamp, so handle special case IF v_part_interval = '3 months' AND (v_type = 'time-static' OR v_type = 'time-dynamic') THEN v_year := to_char(v_min_partition_timestamp, 'YYYY'); v_quarter := to_char(v_min_partition_timestamp, 'Q'); v_partition_suffix := v_year || 'q' || v_quarter; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname||'.'||tablename = p_parent_table; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_partition_suffix, TRUE); EXECUTE 'WITH partition_data AS ( DELETE FROM ONLY '||p_parent_table||' WHERE '||v_control||' >= '||quote_literal(v_min_partition_timestamp)|| ' AND '||v_control||' < '||quote_literal(v_max_partition_timestamp)||' RETURNING *) INSERT INTO '||v_current_partition_name||' SELECT * FROM partition_data'; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; IF v_type = 'time-static' THEN PERFORM @extschema@.create_function_time(p_parent_table); END IF; RETURN v_total_rows; END $$; /* * Populate the child table(s) of an id-based partition set with old data from the original parent */ CREATE OR REPLACE FUNCTION partition_data_id(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval int DEFAULT NULL, p_lock_wait numeric DEFAULT 0, p_order text DEFAULT 'ASC') RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_current_partition_name text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_id bigint; v_min_partition_id bigint; v_parent_schema text; v_parent_tablename text; v_part_interval bigint; v_partition_id bigint[]; v_rowcount bigint; v_sql text; v_start_control bigint; v_total_rows bigint := 0; v_type text; BEGIN SELECT type , part_interval::bigint , control INTO v_type , v_part_interval , v_control FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF p_batch_interval IS NULL OR p_batch_interval > v_part_interval THEN p_batch_interval := v_part_interval; END IF; FOR i IN 1..p_batch_count LOOP IF p_order = 'ASC' THEN EXECUTE 'SELECT min('||v_control||') FROM ONLY '||p_parent_table INTO v_start_control; IF v_start_control IS NULL THEN EXIT; END IF; v_min_partition_id = v_start_control - (v_start_control % v_part_interval); v_partition_id := ARRAY[v_min_partition_id]; -- Check if custom batch interval overflows current partition maximum IF (v_start_control + p_batch_interval) >= (v_min_partition_id + v_part_interval) THEN v_max_partition_id := v_min_partition_id + v_part_interval; ELSE v_max_partition_id := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN EXECUTE 'SELECT max('||v_control||') FROM ONLY '||p_parent_table INTO v_start_control; IF v_start_control IS NULL THEN EXIT; END IF; v_min_partition_id = v_start_control - (v_start_control % v_part_interval); -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_id := v_min_partition_id + v_part_interval; v_partition_id := ARRAY[v_min_partition_id]; -- Make sure minimum doesn't underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_id THEN v_min_partition_id = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN v_sql := 'SELECT * FROM ONLY ' || p_parent_table || ' WHERE '||v_control||' >= '||quote_literal(v_min_partition_id)|| ' AND '||v_control||' < '||quote_literal(v_max_partition_id) ||' FOR UPDATE NOWAIT'; EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; PERFORM @extschema@.create_partition_id(p_parent_table, v_partition_id); SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname||'.'||tablename = p_parent_table; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_min_partition_id::text, TRUE); EXECUTE 'WITH partition_data AS ( DELETE FROM ONLY '||p_parent_table||' WHERE '||v_control||' >= '||v_min_partition_id|| ' AND '||v_control||' < '||v_max_partition_id||' RETURNING *) INSERT INTO '||v_current_partition_name||' SELECT * FROM partition_data'; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; IF v_type = 'id-static' THEN PERFORM @extschema@.create_function_id(p_parent_table); END IF; RETURN v_total_rows; END $$; /* * Function to manage pre-creation of the next partitions in a set. * Also manages dropping old partitions if the retention option is set. * If p_parent_table is passed, will only run run_maintenance() on that one table (no matter what the configuration table may have set for it) * Otherwise, will run on all tables in the config table with p_run_maintenance() set to true. * For large partition sets, running analyze can cause maintenance to take longer than expected. Can set p_analyze to false to avoid a forced analyze run. * Be aware that constraint exclusion may not work properly until an analyze on the partition set is run. */ CREATE FUNCTION run_maintenance(p_parent_table text DEFAULT NULL, p_analyze boolean DEFAULT true, p_jobmon boolean DEFAULT true) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_create_count int := 0; v_current_partition text; v_current_partition_id bigint; v_current_partition_timestamp timestamp; v_datetime_string text; v_drop_count int := 0; v_id_position int; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_created boolean; v_last_partition_id bigint; v_last_partition_timestamp timestamp; v_next_partition_id bigint; v_next_partition_timestamp timestamp; v_old_search_path text; v_premade_count int; v_quarter text; v_step_id bigint; v_step_overflow_id bigint; v_step_serial_id bigint; v_sub_parent text; v_row record; v_row_sub record; v_tablename text; v_tables_list_sql text; v_time_position int; v_year text; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman maintenance already running.'; RETURN; END IF; 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' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN RUN MAINTENANCE'); v_step_id := add_step(v_job_id, 'Running maintenance loop'); END IF; v_tables_list_sql := 'SELECT parent_table , type , part_interval , control , premake , datetime_string , undo_in_progress FROM @extschema@.part_config'; IF p_parent_table IS NULL THEN v_tables_list_sql := v_tables_list_sql || ' WHERE use_run_maintenance = true'; ELSE v_tables_list_sql := v_tables_list_sql || format(' WHERE parent_table = %L', p_parent_table); END IF; FOR v_row IN EXECUTE v_tables_list_sql LOOP CONTINUE WHEN v_row.undo_in_progress; SELECT show_partitions INTO v_last_partition FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LIMIT 1; IF v_row.type = 'time-static' OR v_row.type = 'time-dynamic' OR v_row.type = 'time-custom' THEN IF v_row.type = 'time-static' OR v_row.type = 'time-dynamic' THEN CASE WHEN v_row.part_interval::interval = '15 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_row.part_interval::interval = '30 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_row.part_interval::interval = '1 hour' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_row.part_interval::interval = '1 day' THEN v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_row.part_interval::interval = '1 week' THEN v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_row.part_interval::interval = '1 month' THEN v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_row.part_interval::interval = '3 months' THEN v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_row.part_interval::interval = '1 year' THEN v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; ELSIF v_row.type = 'time-custom' THEN SELECT child_table INTO v_current_partition FROM @extschema@.custom_time_partitions WHERE parent_table = v_row.parent_table AND partition_range @> CURRENT_TIMESTAMP; IF v_current_partition IS NULL THEN RAISE EXCEPTION 'Current time partition missing from custom_time_partitions config table for table % and timestamp %', CURRENT_TIMESTAMP, v_row.parent_table; END IF; v_time_position := (length(v_current_partition) - position('p_' in reverse(v_current_partition))) + 2; v_current_partition_timestamp := to_timestamp(substring(v_current_partition from v_time_position), v_row.datetime_string); END IF; v_time_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; IF v_row.part_interval::interval <> '3 months' OR (v_row.part_interval::interval = '3 months' AND v_row.type = 'time-custom') THEN v_last_partition_timestamp := to_timestamp(substring(v_last_partition from v_time_position), v_row.datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(v_last_partition from v_time_position), 'q', 1); v_quarter := split_part(substring(v_last_partition from v_time_position), 'q', 2); CASE WHEN v_quarter = '1' THEN v_last_partition_timestamp := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_last_partition_timestamp := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_last_partition_timestamp := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_last_partition_timestamp := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; -- Check and see how many premade partitions there are. -- Can be negative when subpartitioning and there are parent partitions in the past compared to current timestamp value. -- abs() prevents run_maintenence from running on those old parent tables v_premade_count = abs(round(EXTRACT('epoch' FROM age(v_last_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.part_interval::interval))); v_next_partition_timestamp := v_last_partition_timestamp; -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed. WHILE v_premade_count < v_row.premake LOOP BEGIN v_next_partition_timestamp := v_next_partition_timestamp + v_row.part_interval::interval; EXCEPTION WHEN datetime_field_overflow THEN v_premade_count := v_row.premake; -- do this so it can exit the premake check loop and continue in the outer for loop IF v_jobmon_schema IS NOT NULL THEN 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 skippd for parent table '||v_partition_time); END IF; RAISE WARNING 'Attempted partition time interval is outside PostgreSQL''s supported time range. Child partition creation skipped for parent table %', v_row.parent_table; CONTINUE; END; v_last_partition_created := @extschema@.create_partition_time(v_row.parent_table, ARRAY[v_next_partition_timestamp], p_analyze); v_create_count := v_create_count + 1; IF v_row.type = 'time-static' AND v_last_partition_created THEN PERFORM @extschema@.create_function_time(v_row.parent_table); END IF; -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(v_row.parent_table); -- Can be negative when subpartitioning and there are parent partitions in the past compared to current timestamp value. -- abs() prevents run_maintenence from running on those old parent tables v_premade_count = abs(round(EXTRACT('epoch' FROM age(v_next_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.part_interval::interval))); END LOOP; ELSIF v_row.type = 'id-static' OR v_row.type ='id-dynamic' THEN -- This doesn't need the overall max of a full subpartition set, just the max of the current partition set EXECUTE 'SELECT '||v_row.control||' - ('||v_row.control||' % '||v_row.part_interval::int||') FROM '||v_row.parent_table||' WHERE '||v_row.control||' = (SELECT max('||v_row.control||') FROM '||v_row.parent_table||')' INTO v_current_partition_id; v_id_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; v_last_partition_id = substring(v_last_partition from v_id_position)::bigint; -- This catches if there's invalid data in a parent table set that's outside all child table ranges. IF v_last_partition_id < v_current_partition_id THEN IF v_jobmon_schema IS NOT NULL THEN v_step_serial_id := add_step(v_job_id, 'Found inconsistent data in serial partition set.'); PERFORM update_step(v_step_serial_id, 'CRITICAL', 'Child partition creation skipped for parent table '||v_row.parent_table||'. Current max serial id value ('||v_current_partition_id||') is greater than the id range covered by the last partition created ('||v_last_partition||'). Run check_parent() to find possible cause.'); END IF; RAISE WARNING 'Child partition creation skipped for parent table %. Found inconsistent data in serial partition set. Current max serial id value (%) is greater than the id range covered by the last partition created (%). Run check_parent() to find possible cause.', v_row.parent_table, v_current_partition_id, v_last_partition; CONTINUE; END IF; v_next_partition_id := v_last_partition_id + v_row.part_interval::bigint; -- Can be negative when subpartitioning and there are parent partitions with lower values compared to current id value. -- abs() prevents run_maintenence from running on those old parent tables WHILE (abs((v_next_partition_id - v_current_partition_id) / v_row.part_interval::bigint)) <= v_row.premake LOOP v_last_partition_created := @extschema@.create_partition_id(v_row.parent_table, ARRAY[v_next_partition_id], p_analyze); IF v_last_partition_created THEN PERFORM @extschema@.create_function_id(v_row.parent_table); PERFORM @extschema@.apply_constraints(v_row.parent_table); END IF; v_next_partition_id := v_next_partition_id + v_row.part_interval::bigint; END LOOP; END IF; -- end main IF check for time or id END LOOP; -- end of creation loop -- Manage dropping old partitions if retention option is set FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND (type = 'time-static' OR type = 'time-dynamic' OR type = 'time-custom') LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); END IF; END IF; END LOOP; FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND (type = 'id-static' OR type = 'id-dynamic') LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); END IF; END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Partition maintenance finished. '||v_create_count||' partitions made. '||v_drop_count||' partitions dropped.'); IF v_step_overflow_id IS NOT NULL OR v_step_serial_id IS NOT NULL THEN PERFORM fail_job(v_job_id); ELSE PERFORM close_job(v_job_id); END IF; EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN RUN MAINTENANCE'')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to drop child tables from an id-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ CREATE OR REPLACE FUNCTION drop_partition_id(p_parent_table text, p_retention bigint DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_child_table text; v_control text; v_drop_count int := 0; v_id_position int; v_index record; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_max bigint; v_old_search_path text; v_part_interval bigint; v_partition_id bigint; v_retention bigint; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_step_id bigint; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman drop_partition_id')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_id already running.'; RETURN 0; END IF; -- Allow override of configuration options IF p_retention IS NULL THEN SELECT part_interval::bigint , control , retention::bigint , retention_keep_table , retention_keep_index , retention_schema , jobmon INTO v_part_interval , v_control , v_retention , v_retention_keep_table , v_retention_keep_index , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic') AND retention IS NOT NULL; IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE SELECT part_interval::bigint , control , retention_keep_table , retention_keep_index , retention_schema , jobmon INTO v_part_interval , v_control , v_retention_keep_table , v_retention_keep_index , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); v_retention := p_retention; IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN DROP ID PARTITION: '|| p_parent_table); END IF; EXECUTE 'SELECT max('||v_control||') FROM '||p_parent_table INTO v_max; -- Loop through child tables of the given parent FOR v_child_table IN SELECT n.nspname||'.'||c.relname FROM pg_inherits i join pg_class c ON i.inhrelid = c.oid join pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC LOOP v_id_position := (length(v_child_table) - position('p_' in reverse(v_child_table))) + 2; v_partition_id := substring(v_child_table from v_id_position)::bigint; -- Add one interval since partition names contain the start of the constraint period IF v_retention <= (v_max - (v_partition_id + v_part_interval)) THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Uninherit table '||v_child_table||' from '||p_parent_table); END IF; EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Drop table '||v_child_table); END IF; EXECUTE 'DROP TABLE '||v_child_table||' CASCADE'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN FOR v_index IN SELECT i.indexrelid::regclass AS name , c.conname FROM pg_catalog.pg_index i LEFT JOIN pg_catalog.pg_constraint c ON i.indexrelid = c.conindid WHERE i.indrelid = v_child_table::regclass LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Drop index '||v_index.name||' from '||v_child_table); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_child_table||' DROP CONSTRAINT '||v_index.conname; ELSE EXECUTE 'DROP INDEX '||v_index.name; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Moving table '||v_child_table||' to schema '||v_retention_schema); END IF; EXECUTE 'ALTER TABLE '||v_child_table||' SET SCHEMA '||v_retention_schema; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if -- If child table is a subpartition, remove it from part_config & part_config_sub (should cascade due to FK) DELETE FROM @extschema@.part_config WHERE parent_table = v_child_table; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', v_drop_count||' partitions dropped.'); PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_drop_count; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN DROP ID PARTITION: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to drop child tables from a time-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ CREATE OR REPLACE FUNCTION drop_partition_time(p_parent_table text, p_retention interval DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_child_table text; v_datetime_string text; v_drop_count int := 0; v_index record; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_part_interval interval; v_partition_timestamp timestamp; v_quarter text; v_retention interval; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_step_id bigint; v_time_position int; v_type text; v_year text; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman drop_partition_time')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_time already running.'; RETURN 0; END IF; -- Allow override of configuration options IF p_retention IS NULL THEN SELECT type , part_interval::interval , retention::interval , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_type , v_part_interval , v_retention , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic' OR type = 'time-custom') AND retention IS NOT NULL; IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE SELECT type , part_interval::interval , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_type , v_part_interval , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic' OR type = 'time-custom'); v_retention := p_retention; IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN DROP TIME PARTITION: '|| p_parent_table); END IF; -- Loop through child tables of the given parent FOR v_child_table IN SELECT n.nspname||'.'||c.relname FROM pg_inherits i join pg_class c ON i.inhrelid = c.oid join pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC LOOP -- pull out datetime portion of partition's tablename to make the next one v_time_position := (length(v_child_table) - position('p_' in reverse(v_child_table))) + 2; IF v_part_interval <> '3 months' OR (v_part_interval = '3 months' AND v_type = 'time-custom') THEN v_partition_timestamp := to_timestamp(substring(v_child_table from v_time_position), v_datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(v_child_table from v_time_position), 'q', 1); v_quarter := split_part(substring(v_child_table from v_time_position), 'q', 2); CASE WHEN v_quarter = '1' THEN v_partition_timestamp := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_partition_timestamp := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_partition_timestamp := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_partition_timestamp := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; -- Add one interval since partition names contain the start of the constraint period IF v_retention < (CURRENT_TIMESTAMP - (v_partition_timestamp + v_part_interval)) THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Uninherit table '||v_child_table||' from '||p_parent_table); END IF; EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Drop table '||v_child_table); END IF; EXECUTE 'DROP TABLE '||v_child_table||' CASCADE'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN FOR v_index IN SELECT i.indexrelid::regclass AS name , c.conname FROM pg_catalog.pg_index i LEFT JOIN pg_catalog.pg_constraint c ON i.indexrelid = c.conindid WHERE i.indrelid = v_child_table::regclass LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Drop index '||v_index.name||' from '||v_child_table); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_child_table||' DROP CONSTRAINT '||v_index.conname; ELSE EXECUTE 'DROP INDEX '||v_index.name; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Moving table '||v_child_table||' to schema '||v_retention_schema); END IF; EXECUTE 'ALTER TABLE '||v_child_table||' SET SCHEMA '||v_retention_schema; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if -- If child table is a subpartition, remove it from part_config & part_config_sub (should cascade due to FK) DELETE FROM @extschema@.part_config WHERE parent_table = v_child_table; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', v_drop_count||' partitions dropped.'); PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_drop_count; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN DROP TIME PARTITION: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to list all child partitions in a set. */ CREATE OR REPLACE FUNCTION show_partitions (p_parent_table text, p_order text DEFAULT 'ASC') RETURNS SETOF text LANGUAGE plpgsql STABLE SECURITY DEFINER AS $$ DECLARE v_datetime_string text; v_part_interval text; v_type text; BEGIN IF p_order NOT IN ('ASC', 'DESC') THEN RAISE EXCEPTION 'p_order paramter must be one of the following values: ASC, DESC'; END IF; SELECT type , part_interval , datetime_string INTO v_type , v_part_interval , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_type IN ('time-static', 'time-dynamic', 'time-custom') THEN RETURN QUERY EXECUTE ' SELECT n.nspname::text ||''.''|| c.relname::text AS partition_name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = '||quote_literal(p_parent_table)||'::regclass ORDER BY to_timestamp(substring(c.relname from ((length(c.relname) - position(''p_'' in reverse(c.relname))) + 2) ), '||quote_literal(v_datetime_string)||') ' || p_order; ELSIF v_type IN ('id-static', 'id-dynamic') THEN RETURN QUERY EXECUTE ' SELECT n.nspname::text ||''.''|| c.relname::text AS partition_name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = '||quote_literal(p_parent_table)||'::regclass ORDER BY substring(c.relname from ((length(c.relname) - position(''p_'' in reverse(c.relname))) + 2) )::bigint ' || p_order; END IF; END $$; /* * Apply constraints managed by partman extension */ CREATE OR REPLACE FUNCTION apply_constraints(p_parent_table text, p_child_table text DEFAULT NULL, p_analyze boolean DEFAULT TRUE, p_debug boolean DEFAULT FALSE) RETURNS void LANGUAGE plpgsql AS $$ DECLARE v_child_table text; v_child_tablename text; v_col text; v_constraint_cols text[]; v_constraint_col_type text; v_constraint_name text; v_datetime_string text; v_existing_constraint_name text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_id int; v_last_partition_timestamp timestamp; v_constraint_values record; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_part_interval text; v_partition_suffix text; v_premake int; v_sql text; v_step_id bigint; v_suffix_position int; v_type text; BEGIN SELECT type , part_interval , premake , datetime_string , constraint_cols , jobmon INTO v_type , v_part_interval , v_premake , v_datetime_string , v_constraint_cols , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_constraint_cols IS NULL THEN IF p_debug THEN RAISE NOTICE 'Given parent table (%) not set up for constraint management (constraint_cols is NULL)', p_parent_table; END IF; -- Returns silently to allow this function to be simply called by maintenance processes without having to check if config options are set. RETURN; END IF; SELECT show_partitions INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE CONSTRAINT: '||p_parent_table); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; -- If p_child_table is null, figure out the partition that is the one right before the premake value backwards. IF p_child_table IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Automatically determining most recent child on which to apply constraints'); END IF; v_suffix_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; IF v_type IN ('time-static', 'time-dynamic') THEN v_last_partition_timestamp := to_timestamp(substring(v_last_partition from v_suffix_position), v_datetime_string); v_partition_suffix := to_char(v_last_partition_timestamp - (v_part_interval::interval * ((v_premake * 2)+1) ), v_datetime_string); ELSIF v_type IN ('id-static', 'id-dynamic') THEN v_last_partition_id := substring(v_last_partition from v_suffix_position)::int; v_partition_suffix := (v_last_partition_id - (v_part_interval::int * ((v_premake * 2)+1) ))::text; END IF; v_child_table := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_partition_suffix, TRUE); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Target child table: '||v_child_table); END IF; ELSE v_child_table := p_child_table; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Checking if target child table exists'); END IF; SELECT tablename INTO v_child_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_child_table; IF v_child_tablename IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', 'Target child table ('||v_child_table||') does not exist. Skipping constraint creation.'); PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; IF p_debug THEN RAISE NOTICE 'Target child table (%) does not exist. Skipping constraint creation.', v_child_table; END IF; RETURN; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; FOREACH v_col IN ARRAY v_constraint_cols LOOP SELECT c.conname INTO v_existing_constraint_name FROM pg_catalog.pg_constraint c JOIN pg_catalog.pg_attribute a ON c.conrelid = a.attrelid WHERE conrelid = v_child_table::regclass AND c.conname LIKE 'partmanconstr_%' AND c.contype = 'c' AND a.attname = v_col AND ARRAY[a.attnum] <@ c.conkey AND a.attisdropped = false; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying new constraint on column: '||v_col); END IF; IF v_existing_constraint_name IS NOT NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', 'Partman managed constraint already exists on this table ('||v_child_table||') and column ('||v_col||'). Skipping creation.'); END IF; RAISE WARNING 'Partman managed constraint already exists on this table (%) and column (%). Skipping creation.', v_child_table, v_col ; CONTINUE; END IF; -- Ensure column name gets put on end of constraint name to help avoid naming conflicts v_constraint_name := @extschema@.check_name_length('partmanconstr_'||v_child_tablename, p_suffix := '_'||v_col); EXECUTE 'SELECT min('||v_col||')::text AS min, max('||v_col||')::text AS max FROM '||v_child_table INTO v_constraint_values; IF v_constraint_values IS NOT NULL THEN v_sql := concat('ALTER TABLE ', v_child_table, ' ADD CONSTRAINT ', v_constraint_name , ' CHECK (', v_col, ' >= ', quote_literal(v_constraint_values.min), ' AND ' , v_col, ' <= ', quote_literal(v_constraint_values.max), ')' ); IF p_debug THEN RAISE NOTICE 'Constraint creation query: %', v_sql; END IF; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'New constraint created: '||v_sql); END IF; ELSE IF p_debug THEN RAISE NOTICE 'Given column (%) contains all NULLs. No constraint created', v_col; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', 'Given column ('||v_col||') contains all NULLs. No constraint created'); END IF; END IF; END LOOP; IF p_analyze THEN EXECUTE 'ANALYZE '||p_parent_table; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE CONSTRAINT: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to undo time-based partitioning created by this extension */ CREATE OR REPLACE FUNCTION undo_partition_time(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval interval DEFAULT NULL, p_keep_table boolean DEFAULT true, p_lock_wait numeric DEFAULT 0) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_batch_loop_count int := 0; v_child_min timestamptz; v_child_loop_total bigint := 0; v_child_table text; v_control text; v_function_name text; v_inner_loop_count int; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_move_sql text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_part_interval interval; v_row record; v_rowcount bigint; v_step_id bigint; v_sub_count int; v_total bigint := 0; v_trig_name text; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_time_partition')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_time_partition already running.'; RETURN 0; END IF; SELECT part_interval::interval , control , jobmon INTO v_part_interval , v_control , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic' OR type = 'time-custom'); IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; -- Check if any child tables are themselves partitioned or part of an inheritance tree. Prevent undo at this level if so. -- Need to either lock child tables at all levels or handle the proper removal of triggers on all child tables first -- before multi-level undo can be performed safely. FOR v_row IN SELECT show_partitions AS child_table FROM @extschema@.show_partitions(p_parent_table) LOOP SELECT count(*) INTO v_sub_count FROM pg_catalog.pg_inherits WHERE inhparent::regclass = v_row.child_table::regclass; IF v_sub_count > 0 THEN RAISE EXCEPTION 'Child table for this parent has child table(s) itself (%). Run undo partitioning on this table or remove inheritance first to ensure all data is properly moved to parent', v_row.child_table; END IF; END LOOP; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Undoing partitioning for table '||p_parent_table); END IF; IF p_batch_interval IS NULL THEN p_batch_interval := v_part_interval; END IF; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables. SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, '_part_trig_func', FALSE); SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t WHERE tgname = v_trig_name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE 'LOCK TABLE ONLY '||p_parent_table||' IN ACCESS EXCLUSIVE MODE NOWAIT'; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; RETURN -1; END IF; END IF; -- END p_lock_wait IF EXECUTE 'DROP TRIGGER IF EXISTS '||v_trig_name||' ON '||p_parent_table; END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE 'DROP FUNCTION IF EXISTS '||v_function_name||'()'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); END IF; <> WHILE v_batch_loop_count < p_batch_count LOOP SELECT n.nspname||'.'||c.relname INTO v_child_table FROM pg_inherits i JOIN pg_class c ON i.inhrelid = c.oid JOIN pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC; EXIT WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing child partition: '||v_child_table); END IF; EXECUTE 'SELECT min('||v_control||') FROM '||v_child_table INTO v_child_min; IF v_child_min IS NULL THEN -- No rows left in this child table. Remove from partition set. -- lockwait timeout for table drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE 'LOCK TABLE ONLY '||v_child_table||' IN ACCESS EXCLUSIVE MODE NOWAIT'; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; RETURN -1; END IF; END IF; -- END p_lock_wait IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF p_keep_table = false THEN EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table UNINHERITED, not DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; END IF; v_undo_count := v_undo_count + 1; CONTINUE outer_child_loop; END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE 'SELECT * FROM ' || v_child_table || ' WHERE '||v_control||' <= '||quote_literal(v_child_min + (p_batch_interval * v_inner_loop_count)) ||' FOR UPDATE NOWAIT'; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; RETURN -1; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval v_move_sql := 'WITH move_data AS (DELETE FROM '||v_child_table|| ' WHERE '||v_control||' <= '||quote_literal(v_child_min + (p_batch_interval * v_inner_loop_count))||' RETURNING *) INSERT INTO '||p_parent_table||' SELECT * FROM move_data'; EXECUTE v_move_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Moved '||v_child_loop_total||' rows to parent.'); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; IF v_batch_loop_count < p_batch_count THEN -- FOR loop never ran, so there's no child tables left. DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) to the parent. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', 'Copied '||v_total||' row(s) to the parent. Removed '||v_undo_count||' partitions.'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_total; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN UNDO PARTITIONING: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to undo id-based partitioning created by this extension */ CREATE OR REPLACE FUNCTION undo_partition_id(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval bigint DEFAULT NULL, p_keep_table boolean DEFAULT true, p_lock_wait numeric DEFAULT 0) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_batch_loop_count int := 0; v_child_loop_total bigint := 0; v_child_min bigint; v_child_table text; v_control text; v_exists int; v_function_name text; v_inner_loop_count int; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_move_sql text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_part_interval bigint; v_row record; v_rowcount bigint; v_step_id bigint; v_sub_count int; v_trig_name text; v_total bigint := 0; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_id_partition')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_id_partition already running.'; RETURN 0; END IF; SELECT part_interval::bigint , control , jobmon INTO v_part_interval , v_control , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; -- Check if any child tables are themselves partitioned or part of an inheritance tree. Prevent undo at this level if so. -- Need to either lock child tables at all levels or handle the proper removal of triggers on all child tables first -- before multi-level undo can be performed safely. FOR v_row IN SELECT show_partitions AS child_table FROM @extschema@.show_partitions(p_parent_table) LOOP SELECT count(*) INTO v_sub_count FROM pg_catalog.pg_inherits WHERE inhparent::regclass = v_row.child_table::regclass; IF v_sub_count > 0 THEN RAISE EXCEPTION 'Child table for this parent has child table(s) itself (%). Run undo partitioning on this table or remove inheritance first to ensure all data is properly moved to parent', v_row.child_table; END IF; END LOOP; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Undoing partitioning for table '||p_parent_table); END IF; IF p_batch_interval IS NULL THEN p_batch_interval := v_part_interval; END IF; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables and stop new id partitions from being made. SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, '_part_trig_func', FALSE); SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t WHERE tgname = v_trig_name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE 'LOCK TABLE ONLY '||p_parent_table||' IN ACCESS EXCLUSIVE MODE NOWAIT'; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; RETURN -1; END IF; END IF; -- END p_lock_wait IF EXECUTE 'DROP TRIGGER IF EXISTS '||v_trig_name||' ON '||p_parent_table; END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE 'DROP FUNCTION IF EXISTS '||v_function_name||'()'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); END IF; <> WHILE v_batch_loop_count < p_batch_count LOOP SELECT n.nspname||'.'||c.relname INTO v_child_table FROM pg_inherits i JOIN pg_class c ON i.inhrelid = c.oid JOIN pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC; EXIT WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing child partition: '||v_child_table); END IF; EXECUTE 'SELECT min('||v_control||') FROM '||v_child_table INTO v_child_min; IF v_child_min IS NULL THEN -- No rows left in this child table. Remove from partition set. -- lockwait timeout for table drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE 'LOCK TABLE ONLY '||v_child_table||' IN ACCESS EXCLUSIVE MODE NOWAIT'; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; RETURN -1; END IF; END IF; -- END p_lock_wait IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF p_keep_table = false THEN EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table UNINHERITED, not DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; END IF; v_undo_count := v_undo_count + 1; CONTINUE outer_child_loop; END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP -- lockwait timeout for row batches IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE 'SELECT * FROM ' || v_child_table || ' WHERE '||v_control||' <= '||quote_literal(v_child_min + (p_batch_interval * v_inner_loop_count)) ||' FOR UPDATE NOWAIT'; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; RETURN -1; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval v_move_sql := 'WITH move_data AS (DELETE FROM '||v_child_table|| ' WHERE '||v_control||' <= '||quote_literal(v_child_min + (p_batch_interval * v_inner_loop_count))||' RETURNING *) INSERT INTO '||p_parent_table||' SELECT * FROM move_data'; EXECUTE v_move_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Moved '||v_child_loop_total||' rows to parent.'); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; IF v_batch_loop_count < p_batch_count THEN -- FOR loop never ran, so there's no child tables left. DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) to the parent. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', 'Copied '||v_total||' row(s) to the parent. Removed '||v_undo_count||' partitions.'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_total; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN UNDO PARTITIONING: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; -- Restore dropped object privileges DO $$ DECLARE v_row record; BEGIN FOR v_row IN SELECT statement FROM partman_preserve_privs_temp LOOP IF v_row.statement IS NOT NULL THEN EXECUTE v_row.statement; END IF; END LOOP; END $$; DROP TABLE IF EXISTS partman_preserve_privs_temp; pg_partman-4.6.0/updates/pg_partman--1.8.0--1.8.1.sql000066400000000000000000001063571412761706100214470ustar00rootroot00000000000000-- The p_analyze parameter to the apply_constraint() function is now FALSE by default instead of TRUE. This makes it so that by default, an analyze is only run by the create_partition_id/time() functions upon new partition creation. The parameter was left in apply_contraints() in case someone needs to call it directly and ensure an analyze is run so statistics are updated. (Github Issue #45) -- Note: If using reapply_constraints.py, an ANALYZE is always done at the end of the script. It was like that before this update. -- Changed the manner in which new partition creation is logged in pg_jobmon. Previously, each individual child table creation was logged as its own, separate job entry in pg_jobmon. Now, if multiple child partitions are created for a single partition set, all those child tables are logged as steps for a single job log entry. This now allows the analyze step (if it is done) to be logged as well in pg_jobmon and allows for easier diagnosis if this if holding up partition maintenance. /* * Apply constraints managed by partman extension */ CREATE OR REPLACE FUNCTION apply_constraints(p_parent_table text, p_child_table text DEFAULT NULL, p_analyze boolean DEFAULT FALSE, p_debug boolean DEFAULT FALSE) RETURNS void LANGUAGE plpgsql AS $$ DECLARE v_child_table text; v_child_tablename text; v_col text; v_constraint_cols text[]; v_constraint_col_type text; v_constraint_name text; v_datetime_string text; v_existing_constraint_name text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_id int; v_last_partition_timestamp timestamp; v_constraint_values record; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_part_interval text; v_partition_suffix text; v_premake int; v_sql text; v_step_id bigint; v_suffix_position int; v_type text; BEGIN SELECT type , part_interval , premake , datetime_string , constraint_cols , jobmon INTO v_type , v_part_interval , v_premake , v_datetime_string , v_constraint_cols , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_constraint_cols IS NULL THEN IF p_debug THEN RAISE NOTICE 'Given parent table (%) not set up for constraint management (constraint_cols is NULL)', p_parent_table; END IF; -- Returns silently to allow this function to be simply called by maintenance processes without having to check if config options are set. RETURN; END IF; SELECT show_partitions INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE CONSTRAINT: '||p_parent_table); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; -- If p_child_table is null, figure out the partition that is the one right before the premake value backwards. IF p_child_table IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Automatically determining most recent child on which to apply constraints'); END IF; v_suffix_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; IF v_type IN ('time-static', 'time-dynamic') THEN v_last_partition_timestamp := to_timestamp(substring(v_last_partition from v_suffix_position), v_datetime_string); v_partition_suffix := to_char(v_last_partition_timestamp - (v_part_interval::interval * ((v_premake * 2)+1) ), v_datetime_string); ELSIF v_type IN ('id-static', 'id-dynamic') THEN v_last_partition_id := substring(v_last_partition from v_suffix_position)::int; v_partition_suffix := (v_last_partition_id - (v_part_interval::int * ((v_premake * 2)+1) ))::text; END IF; v_child_table := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_partition_suffix, TRUE); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Target child table: '||v_child_table); END IF; ELSE v_child_table := p_child_table; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Checking if target child table exists'); END IF; SELECT tablename INTO v_child_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_child_table; IF v_child_tablename IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', 'Target child table ('||v_child_table||') does not exist. Skipping constraint creation.'); PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; IF p_debug THEN RAISE NOTICE 'Target child table (%) does not exist. Skipping constraint creation.', v_child_table; END IF; RETURN; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; FOREACH v_col IN ARRAY v_constraint_cols LOOP SELECT c.conname INTO v_existing_constraint_name FROM pg_catalog.pg_constraint c JOIN pg_catalog.pg_attribute a ON c.conrelid = a.attrelid WHERE conrelid = v_child_table::regclass AND c.conname LIKE 'partmanconstr_%' AND c.contype = 'c' AND a.attname = v_col AND ARRAY[a.attnum] <@ c.conkey AND a.attisdropped = false; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying new constraint on column: '||v_col); END IF; IF v_existing_constraint_name IS NOT NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', 'Partman managed constraint already exists on this table ('||v_child_table||') and column ('||v_col||'). Skipping creation.'); END IF; RAISE WARNING 'Partman managed constraint already exists on this table (%) and column (%). Skipping creation.', v_child_table, v_col ; CONTINUE; END IF; -- Ensure column name gets put on end of constraint name to help avoid naming conflicts v_constraint_name := @extschema@.check_name_length('partmanconstr_'||v_child_tablename, p_suffix := '_'||v_col); EXECUTE 'SELECT min('||v_col||')::text AS min, max('||v_col||')::text AS max FROM '||v_child_table INTO v_constraint_values; IF v_constraint_values IS NOT NULL THEN v_sql := concat('ALTER TABLE ', v_child_table, ' ADD CONSTRAINT ', v_constraint_name , ' CHECK (', v_col, ' >= ', quote_literal(v_constraint_values.min), ' AND ' , v_col, ' <= ', quote_literal(v_constraint_values.max), ')' ); IF p_debug THEN RAISE NOTICE 'Constraint creation query: %', v_sql; END IF; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'New constraint created: '||v_sql); END IF; ELSE IF p_debug THEN RAISE NOTICE 'Given column (%) contains all NULLs. No constraint created', v_col; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', 'Given column ('||v_col||') contains all NULLs. No constraint created'); END IF; END IF; END LOOP; IF p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Running analyze on partition set: '||p_parent_table); END IF; IF p_debug THEN RAISE NOTICE 'Running analyze on partition set: %', p_parent_table; END IF; EXECUTE 'ANALYZE '||p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE CONSTRAINT: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to create id partitions */ CREATE OR REPLACE FUNCTION create_partition_id(p_parent_table text, p_partition_ids bigint[], p_analyze boolean DEFAULT true) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_grantees text[]; v_hasoids boolean; v_id bigint; v_id_position int; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_parent_tablespace text; v_part_interval bigint; v_partition_created boolean := false; v_partition_name text; v_revoke text[]; v_row record; v_sql text; v_step_id bigint; v_sub_id_max bigint; v_sub_id_min bigint; v_tablename text; v_top_interval bigint; v_top_parent text; v_unlogged char; BEGIN SELECT control , part_interval , inherit_fk , jobmon INTO v_control , v_part_interval , v_inherit_fk , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; -- Check if parent table is a subpartition of an already existing id based partition set managed by pg_partman -- If so, limit what child tables can be created based on parent suffix WITH top_oid AS ( SELECT i.inhparent AS top_parent_oid FROM pg_catalog.pg_class c JOIN pg_catalog.pg_inherits i ON c.oid = i.inhrelid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname||'.'||c.relname = p_parent_table ) SELECT n.nspname||'.'||c.relname INTO v_top_parent FROM pg_catalog.pg_class c JOIN top_oid t ON c.oid = t.top_parent_oid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace JOIN @extschema@.part_config p ON p.parent_table = n.nspname||'.'||c.relname WHERE c.oid = t.top_parent_oid AND p.type = 'id-static' OR p.type = 'id-dynamic'; IF v_top_parent IS NOT NULL THEN SELECT part_interval::bigint INTO v_top_interval FROM @extschema@.part_config WHERE parent_table = v_top_parent; v_id_position := (length(p_parent_table) - position('p_' in reverse(p_parent_table))) + 2; v_sub_id_min = substring(p_parent_table from v_id_position)::bigint; v_sub_id_max = (v_sub_id_min + v_top_interval) - 1; END IF; SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); END IF; FOREACH v_id IN ARRAY p_partition_ids LOOP -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_id_min IS NOT NULL THEN IF v_id < v_sub_id_min OR v_id > v_sub_id_max THEN CONTINUE; END IF; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_id::text, TRUE); -- If child table already exists, skip creation SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_tablename IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_id||' to '||(v_id + v_part_interval)-1); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class WHERE oid::regclass = p_parent_table::regclass; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; v_sql := v_sql || ' TABLE '||v_partition_name||' (LIKE '||p_parent_table||' INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)'; SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class WHERE oid::regclass = p_parent_table::regclass; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_parent_tablespace IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_partition_name||' SET TABLESPACE '||v_parent_tablespace; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' ADD CONSTRAINT '||v_tablename||'_partition_check CHECK ('||v_control||'>='||quote_literal(v_id)||' AND '||v_control||'<'||quote_literal(v_id + v_part_interval)||')'; EXECUTE 'ALTER TABLE '||v_partition_name||' INHERIT '||p_parent_table; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP EXECUTE 'GRANT '||array_to_string(v_parent_grant.types, ',')||' ON '||v_partition_name||' TO '||v_parent_grant.grantee; SELECT array_agg(r) INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE '||array_to_string(v_revoke, ',')||' ON '||v_partition_name||' FROM '||v_parent_grant.grantee||' CASCADE'; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN SELECT array_agg(r) INTO v_revoke FROM ( SELECT DISTINCT grantee::text AS r FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_partition_name EXCEPT SELECT unnest(v_grantees)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE ALL ON '||v_partition_name||' FROM '||array_to_string(v_revoke, ','); END IF; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' OWNER TO '||v_parent_owner; IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(quote_ident(v_parent_schema)||'.'||quote_ident(v_parent_tablename), v_partition_name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_control , sub_type , sub_part_interval , sub_constraint_cols , sub_premake , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_use_run_maintenance , sub_jobmon FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Subpartitioning '||v_partition_name); END IF; 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_use_run_maintenance := %L , p_inherit_fk := %L , p_jobmon := %L )' , v_partition_name , v_row.sub_control , v_row.sub_type , v_row.sub_part_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_inherit_fk , v_row.sub_use_run_maintenance , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index WHERE parent_table = v_partition_name; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; -- end sub partitioning LOOP v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Analyzing partition set: '||p_parent_table); END IF; EXECUTE 'ANALYZE '||p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, 'No partitions created for partition set: '||p_parent_table); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; PERFORM close_job(v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_partition_created; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE TABLE: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to create a child table in a time-based partition set */ CREATE OR REPLACE FUNCTION create_partition_time (p_parent_table text, p_partition_times timestamp[], p_analyze boolean DEFAULT true) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_datetime_string text; v_grantees text[]; v_hasoids boolean; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_partition_created boolean := false; v_partition_name text; v_partition_suffix text; v_parent_tablespace text; v_part_interval interval; v_partition_timestamp_end timestamp; v_partition_timestamp_start timestamp; v_quarter text; v_revoke text[]; v_row record; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_sub_timestamp_max timestamp; v_sub_timestamp_min timestamp; v_tablename text; v_time_position int; v_top_interval interval; v_top_parent text; v_trunc_value text; v_time timestamp; v_type text; v_unlogged char; v_year text; BEGIN SELECT type , control , part_interval , inherit_fk , jobmon , datetime_string INTO v_type , v_control , v_part_interval , v_inherit_fk , v_jobmon , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic' OR type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; -- Check if parent table is a subpartition of an already existing time-based partition set managed by pg_partman -- If so, limit what child tables can be created based on parent suffix WITH top_oid AS ( SELECT i.inhparent AS top_parent_oid FROM pg_catalog.pg_class c JOIN pg_catalog.pg_inherits i ON c.oid = i.inhrelid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname||'.'||c.relname = p_parent_table ) SELECT n.nspname||'.'||c.relname INTO v_top_parent FROM pg_catalog.pg_class c JOIN top_oid t ON c.oid = t.top_parent_oid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace JOIN @extschema@.part_config p ON p.parent_table = n.nspname||'.'||c.relname WHERE c.oid = t.top_parent_oid AND p.type = 'time-static' OR p.type = 'time-dynamic'; IF v_top_parent IS NOT NULL THEN SELECT part_interval::interval INTO v_top_interval FROM @extschema@.part_config WHERE parent_table = v_top_parent; v_time_position := (length(p_parent_table) - position('p_' in reverse(p_parent_table))) + 2; IF v_part_interval::interval <> '3 months' OR (v_part_interval::interval = '3 months' AND v_type = 'time-custom') THEN v_sub_timestamp_min := to_timestamp(substring(p_parent_table from v_time_position), v_datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(p_parent_table from v_time_position), 'q', 1); v_quarter := split_part(substring(p_parent_table from v_time_position), 'q', 2); CASE WHEN v_quarter = '1' THEN v_sub_timestamp_min := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_sub_timestamp_min := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_sub_timestamp_min := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_sub_timestamp_min := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; v_sub_timestamp_max = (v_sub_timestamp_min + v_top_interval::interval) - '1 sec'::interval; END IF; SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); END IF; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_timestamp_start := v_time; BEGIN v_partition_timestamp_end := v_time + v_part_interval; 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_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_time||' skipped'); CONTINUE; END; -- This suffix generation code is in partition_data_time() as well v_partition_suffix := to_char(v_time, 'YYYY'); IF v_part_interval < '1 year' AND v_part_interval <> '1 week' THEN v_partition_suffix := v_partition_suffix ||'_'|| to_char(v_time, 'MM'); IF v_part_interval < '1 month' AND v_part_interval <> '1 week' THEN v_partition_suffix := v_partition_suffix ||'_'|| to_char(v_time, 'DD'); IF v_part_interval < '1 day' THEN v_partition_suffix := v_partition_suffix || '_' || to_char(v_time, 'HH24MI'); IF v_part_interval < '1 minute' THEN v_partition_suffix := v_partition_suffix || to_char(v_time, 'SS'); END IF; -- end < minute IF END IF; -- end < day IF END IF; -- end < month IF END IF; -- end < year IF IF v_part_interval = '1 week' THEN v_partition_suffix := to_char(v_time, 'IYYY') || 'w' || to_char(v_time, 'IW'); END IF; -- "Q" is ignored in to_timestamp, so handle special case IF v_part_interval = '3 months' AND (v_type = 'time-static' OR v_type = 'time-dynamic') THEN v_year := to_char(v_time, 'YYYY'); v_quarter := to_char(v_time, 'Q'); v_partition_suffix := v_year || 'q' || v_quarter; END IF; -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_timestamp_min IS NOT NULL THEN IF v_time < v_sub_timestamp_min OR v_time > v_sub_timestamp_max THEN CONTINUE; END IF; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_partition_suffix, TRUE); SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_tablename IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_partition_timestamp_start||' to '||(v_partition_timestamp_end-'1sec'::interval)); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class WHERE oid::regclass = p_parent_table::regclass; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; v_sql := v_sql || ' TABLE '||v_partition_name||' (LIKE '||p_parent_table||' INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)'; SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class WHERE oid::regclass = p_parent_table::regclass; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_parent_tablespace IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_partition_name||' SET TABLESPACE '||v_parent_tablespace; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' ADD CONSTRAINT '||v_tablename||'_partition_check CHECK ('||v_control||'>='||quote_literal(v_partition_timestamp_start)||' AND '||v_control||'<'||quote_literal(v_partition_timestamp_end)||')'; EXECUTE 'ALTER TABLE '||v_partition_name||' INHERIT '||p_parent_table; -- If custom time, set extra config options. IF v_type = 'time-custom' THEN INSERT INTO @extschema@.custom_time_partitions (parent_table, child_table, partition_range) VALUES ( p_parent_table, v_partition_name, tstzrange(v_partition_timestamp_start, v_partition_timestamp_end, '[)') ); END IF; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP EXECUTE 'GRANT '||array_to_string(v_parent_grant.types, ',')||' ON '||v_partition_name||' TO '||v_parent_grant.grantee; SELECT array_agg(r) INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE '||array_to_string(v_revoke, ',')||' ON '||v_partition_name||' FROM '||v_parent_grant.grantee||' CASCADE'; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN SELECT array_agg(r) INTO v_revoke FROM ( SELECT DISTINCT grantee::text AS r FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_partition_name EXCEPT SELECT unnest(v_grantees)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE ALL ON '||v_partition_name||' FROM '||array_to_string(v_revoke, ','); END IF; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' OWNER TO '||v_parent_owner; IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(quote_ident(v_parent_schema)||'.'||quote_ident(v_parent_tablename), v_partition_name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_control , sub_type , sub_part_interval , sub_constraint_cols , sub_premake , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_use_run_maintenance , sub_jobmon FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Subpartitioning '||v_partition_name); END IF; 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_use_run_maintenance := %L , p_inherit_fk := %L , p_jobmon := %L )' , v_partition_name , v_row.sub_control , v_row.sub_type , v_row.sub_part_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_inherit_fk , v_row.sub_use_run_maintenance , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index WHERE parent_table = v_partition_name; END LOOP; -- end sub partitioning LOOP v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Analyzing partition set: '||p_parent_table); END IF; EXECUTE 'ANALYZE '||p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, 'No partitions created for partition set: '||p_parent_table); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; 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; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_partition_created; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE TABLE: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; pg_partman-4.6.0/updates/pg_partman--1.8.1--1.8.2.sql000066400000000000000000002427271412761706100214530ustar00rootroot00000000000000-- Fixed a bug in sub-partitioning that would cause child tables outside of the time boundaries of the parent partitions to be created when using time->time sub-partitioning. A user encountered the error when doing weekly->daily subpartitioning, but it was possible it could have happened in other interval combinations I had not tested as well (Github Issue #47). -- Updated reapply_indexes.py script to, by default, only add new indexes and drop ones that don't exist on the parent. Previously it would drop all indexes on all children and recreate them to match the parent. Now it only does the minimal amount of work to make the children match the parent. An additional option (--recreate_all/-R) was added to allow the old behavior of redoing all indexes from scratch if desired (Github Issue #41) -- Changed the minimal interval that serial partitioning can be done to 10. Ran into issues with an interval of 2 and partitioning anything this low is unrealistic and provides no benefit. 10 seems like a reasonable minimal to have at this point to avoid any future issues. This does not affect any existing partition sets, only newly created ones (Github Issue #39). -- Serial partition maintenance when using run_maintenance() is now much more efficient. Should run significantly faster for very large partition sets. -- Fixed bug in subpartition creation when using "time-custom" partitioning type. May have created subpartition child tables that were outside the time boundaries of the parent partitions. -- Fixed bug in additional constraint management when using "time-custom" partitioning type. May not have always added additional constraints on old child tables. -- Added constraint on part_config_sub to ensure valid partition types. ALTER TABLE @extschema@.part_config_sub ADD CONSTRAINT part_config_sub_type_check CHECK (@extschema@.check_partition_type(sub_type)); /* * Function to turn a table into the parent of a partition set */ CREATE OR REPLACE FUNCTION 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_use_run_maintenance boolean DEFAULT NULL , p_start_partition text DEFAULT NULL , p_inherit_fk boolean DEFAULT true , p_jobmon boolean DEFAULT true , p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_base_timestamp timestamp; v_count int := 1; v_datetime_string text; v_higher_parent text := p_parent_table; v_id_interval bigint; v_id_position int; v_job_id bigint; v_jobmon_schema text; v_last_partition_created boolean; v_max bigint; v_notnull boolean; v_old_search_path text; v_parent_partition_id bigint; v_parent_partition_timestamp timestamp; v_partition_time timestamp; v_partition_time_array timestamp[]; v_partition_id_array bigint[]; v_row record; v_run_maint boolean; v_sql text; v_start_time timestamp; v_starting_partition_id bigint; v_step_id bigint; v_step_overflow_id bigint; v_sub_parent text; v_success boolean := false; v_tablename text; v_time_interval interval; v_time_position int; v_top_datetime_string text; v_top_parent text := p_parent_table; BEGIN IF position('.' in p_parent_table) = 0 THEN RAISE EXCEPTION 'Parent table must be schema qualified'; END IF; SELECT tablename INTO v_tablename FROM pg_tables WHERE schemaname || '.' || tablename = p_parent_table; IF v_tablename IS NULL THEN RAISE EXCEPTION 'Please create given parent table first: %', p_parent_table; END IF; SELECT attnotnull INTO v_notnull FROM pg_attribute WHERE attrelid = p_parent_table::regclass AND attname = p_control; IF v_notnull = false OR v_notnull IS NULL THEN RAISE EXCEPTION 'Control column (%) for parent table (%) must be NOT NULL', p_control, p_parent_table; END IF; IF NOT @extschema@.check_partition_type(p_type) THEN RAISE EXCEPTION '% is not a valid partitioning type', p_type; END IF; IF p_type = 'time-custom' AND @extschema@.check_version('9.2.0') IS FALSE THEN RAISE EXCEPTION 'The "time-custom" type requires a minimum PostgreSQL version of 9.2.0'; END IF; EXECUTE 'LOCK TABLE '||p_parent_table||' IN ACCESS EXCLUSIVE MODE'; IF p_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF p_use_run_maintenance IS NOT NULL THEN IF p_use_run_maintenance IS FALSE AND (p_type = 'time-static' OR p_type = 'time-dynamic' OR p_type = 'time-custom') THEN RAISE EXCEPTION 'p_run_maintenance cannot be set to false for time based partitioning'; END IF; v_run_maint := p_use_run_maintenance; ELSIF p_type = 'time-static' OR p_type = 'time-dynamic' OR p_type = 'time-custom' THEN v_run_maint := TRUE; ELSIF p_type = 'id-static' OR p_type ='id-dynamic' THEN v_run_maint := FALSE; ELSE RAISE EXCEPTION 'use_run_maintenance value cannot be set NULL'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN SETUP PARENT: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating initial partitions on new parent table: '||p_parent_table); END IF; -- If this parent table has siblings that are also partitioned (subpartitions), ensure it 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_inherits h where h.inhrelid::regclass = p_parent_table::regclass ), sibling_children as ( select i.inhrelid::regclass::text as tablename from pg_inherits i join parent_table p on i.inhparent = p.parent_oid ) SELECT DISTINCT sub_type , sub_control , sub_part_interval , sub_constraint_cols , sub_premake , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_use_run_maintenance , sub_jobmon 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_type , sub_control , sub_part_interval , sub_constraint_cols , sub_premake , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_use_run_maintenance , sub_jobmon) VALUES ( p_parent_table , v_row.sub_type , v_row.sub_control , v_row.sub_part_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_use_run_maintenance , v_row.sub_jobmon); END LOOP; IF p_type = 'time-static' OR p_type = 'time-dynamic' OR p_type = 'time-custom' 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 <> 'time-custom' THEN RAISE EXCEPTION 'Must use a predefined time interval if not using type "time-custom". See documentation.'; 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::timestamp, 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))::timestamp; 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 , type , part_interval , control , premake , constraint_cols , datetime_string , use_run_maintenance , inherit_fk , jobmon) VALUES ( p_parent_table , p_type , v_time_interval , p_control , p_premake , p_constraint_cols , v_datetime_string , v_run_maint , p_inherit_fk , p_jobmon); 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 n.nspname||'.'||c.relname = p_parent_table ) SELECT n.nspname||'.'||c.relname, p.datetime_string INTO v_top_parent, v_top_datetime_string 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 IS NOT NULL THEN -- If so create the lowest possible partition that is within the boundary of the parent v_time_position := (length(p_parent_table) - position('p_' in reverse(p_parent_table))) + 2; v_parent_partition_timestamp := to_timestamp(substring(p_parent_table from v_time_position), v_top_datetime_string); 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 -- Currently unknown edge case if code gets here RAISE EXCEPTION 'No child tables created. Unexpected edge case encountered. Please report this error to author with conditions that led to it.'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Time partitions premade: '||p_premake); END IF; END IF; IF p_type = 'id-static' OR p_type = 'id-dynamic' THEN v_id_interval := p_interval::bigint; IF v_id_interval < 10 THEN RAISE EXCEPTION 'Interval for serial 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 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||'.'||c.relname = v_higher_parent ) SELECT n.nspname||'.'||c.relname INTO v_higher_parent 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 WHERE p.type = 'id-static' OR p.type = 'id-dynamic'; IF v_higher_parent IS NOT NULL THEN -- v_top_parent initially set in DECLARE v_top_parent := v_higher_parent; 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 v_sql := 'SELECT COALESCE('||quote_nullable(p_start_partition::bigint)||', max('||p_control||')::bigint, 0) FROM '||v_top_parent||' LIMIT 1'; EXECUTE v_sql INTO v_max; 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 , type , part_interval , control , premake , constraint_cols , use_run_maintenance , inherit_fk , jobmon) VALUES ( p_parent_table , p_type , v_id_interval , p_control , p_premake , p_constraint_cols , v_run_maint , p_inherit_fk , p_jobmon); 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 n.nspname||'.'||c.relname = p_parent_table ) SELECT n.nspname||'.'||c.relname INTO v_top_parent 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 WHERE p.type = 'id-static' OR p.type = 'id-dynamic'; IF v_top_parent IS NOT NULL THEN -- Create the lowest possible partition that is within the boundary of the parent v_id_position := (length(p_parent_table) - position('p_' in reverse(p_parent_table))) + 2; v_parent_partition_id = substring(p_parent_table from v_id_position)::bigint; 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 EXCEPTION 'No child tables created. Unexpected edge case encountered. Please report this error to author with conditions that led to it.'; END IF; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating partition function'); END IF; IF p_type = 'time-static' OR p_type = 'time-dynamic' OR p_type = 'time-custom' THEN PERFORM @extschema@.create_function_time(p_parent_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Time function created'); END IF; ELSIF p_type = 'id-static' OR p_type = 'id-dynamic' THEN PERFORM @extschema@.create_function_id(p_parent_table); 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); 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; EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; v_success := true; RETURN v_success; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE PARENT: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''Partition creation for table '||p_parent_table||' failed'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Create the trigger function for the parent table of an id-based partition set */ CREATE OR REPLACE FUNCTION create_function_id(p_parent_table text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_count int; v_current_partition_name text; v_current_partition_id bigint; v_datetime_string text; v_final_partition_id bigint; v_function_name text; v_higher_parent text := p_parent_table; v_id_position int; v_job_id bigint; v_jobmon text; v_jobmon_schema text; v_last_partition text; v_max bigint; v_next_partition_id bigint; v_next_partition_name text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_part_interval bigint; v_premake int; v_prev_partition_id bigint; v_prev_partition_name text; v_row_max_id record; v_run_maint boolean; v_step_id bigint; v_top_parent text := p_parent_table; v_trig_func text; v_type text; BEGIN SELECT type , part_interval::bigint , control , premake , use_run_maintenance , jobmon INTO v_type , v_part_interval , v_control , v_premake , v_run_maint , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT show_partitions INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE FUNCTION: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating partition function for table '||p_parent_table); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_function_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, '_part_trig_func', FALSE); IF v_type = 'id-static' THEN -- Get the highest level top parent if multi-level partitioned in order to get proper max() value below WHILE v_higher_parent 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||'.'||c.relname = v_higher_parent ) SELECT n.nspname||'.'||c.relname INTO v_higher_parent 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 WHERE p.type = 'id-static' OR p.type = 'id-dynamic'; IF v_higher_parent IS NOT NULL THEN -- initially set in DECLARE v_top_parent := v_higher_parent; END IF; END LOOP; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT show_partitions FROM @extschema@.show_partitions(v_top_parent, 'DESC') LOOP EXECUTE 'SELECT max('||v_control||') FROM '||v_row_max_id.show_partitions INTO v_max; IF v_max IS NOT NULL THEN EXIT; END IF; END LOOP; IF v_max IS NULL THEN v_max := 0; END IF; v_current_partition_id = v_max - (v_max % v_part_interval); v_next_partition_id := v_current_partition_id + v_part_interval; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_current_partition_id::text, TRUE); v_trig_func := 'CREATE OR REPLACE FUNCTION '||v_function_name||'() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_current_partition_id bigint; v_last_partition text := '||quote_literal(v_last_partition)||'; v_id_position int; v_next_partition_id bigint; v_next_partition_name text; v_partition_created boolean; BEGIN IF TG_OP = ''INSERT'' THEN IF NEW.'||v_control||' >= '||v_current_partition_id||' AND NEW.'||v_control||' < '||v_next_partition_id|| ' THEN '; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_current_partition_name; IF v_count > 0 THEN v_trig_func := v_trig_func || ' INSERT INTO '||v_current_partition_name||' VALUES (NEW.*); '; ELSE v_trig_func := v_trig_func || ' -- Child table for current values does not exist in this partition set, so write to parent RETURN NEW;'; END IF; FOR i IN 1..v_premake LOOP v_prev_partition_id := v_current_partition_id - (v_part_interval * i); v_next_partition_id := v_current_partition_id + (v_part_interval * i); v_final_partition_id := v_next_partition_id + v_part_interval; v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_prev_partition_id::text, TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_next_partition_id::text, TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles edge case of changing premake immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_prev_partition_name; IF v_count > 0 THEN -- Only handle previous partitions if they're starting above zero IF v_prev_partition_id >= 0 THEN v_trig_func := v_trig_func ||' ELSIF NEW.'||v_control||' >= '||v_prev_partition_id||' AND NEW.'||v_control||' < '||v_prev_partition_id + v_part_interval|| ' THEN INSERT INTO '||v_prev_partition_name||' VALUES (NEW.*); '; END IF; END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_next_partition_name; IF v_count > 0 THEN v_trig_func := v_trig_func ||' ELSIF NEW.'||v_control||' >= '||v_next_partition_id||' AND NEW.'||v_control||' < '||v_final_partition_id|| ' THEN INSERT INTO '||v_next_partition_name||' VALUES (NEW.*);'; END IF; END LOOP; v_trig_func := v_trig_func ||' ELSE RETURN NEW; END IF;'; IF v_run_maint IS FALSE THEN v_trig_func := v_trig_func ||' v_current_partition_id := NEW.'||v_control||' - (NEW.'||v_control||' % '||v_part_interval||'); IF (NEW.'||v_control||' % '||v_part_interval||') > ('||v_part_interval||' / 2) THEN v_id_position := (length(v_last_partition) - position(''p_'' in reverse(v_last_partition))) + 2; v_next_partition_id := (substring(v_last_partition from v_id_position)::bigint) + '||v_part_interval||'; WHILE ((v_next_partition_id - v_current_partition_id) / '||v_part_interval||') <= '||v_premake||' LOOP v_partition_created := @extschema@.create_partition_id('||quote_literal(p_parent_table)||', ARRAY[v_next_partition_id]); IF v_partition_created THEN PERFORM @extschema@.create_function_id('||quote_literal(p_parent_table)||'); PERFORM @extschema@.apply_constraints('||quote_literal(p_parent_table)||'); END IF; v_next_partition_id := v_next_partition_id + '||v_part_interval||'; END LOOP; END IF;'; END IF; v_trig_func := v_trig_func ||' END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for current id interval: '||v_current_partition_id||' to '||v_final_partition_id-1); END IF; ELSIF v_type = 'id-dynamic' THEN -- The return inside the partition creation check is there to keep really high ID values from creating new partitions. v_trig_func := 'CREATE OR REPLACE FUNCTION '||v_function_name||'() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_current_partition_id bigint; v_current_partition_name text; v_id_position int; v_last_partition text := '||quote_literal(v_last_partition)||'; v_last_partition_id bigint; v_next_partition_id bigint; v_partition_created boolean; BEGIN IF TG_OP = ''INSERT'' THEN v_current_partition_id := NEW.'||v_control||' - (NEW.'||v_control||' % '||v_part_interval||'); v_current_partition_name := @extschema@.check_name_length('''||v_parent_tablename||''', '''||v_parent_schema||''', v_current_partition_id::text, TRUE); SELECT count(*) INTO v_count FROM pg_tables WHERE schemaname ||''.''|| tablename = v_current_partition_name; IF v_count > 0 THEN EXECUTE ''INSERT INTO ''||v_current_partition_name||'' VALUES($1.*)'' USING NEW; ELSE RETURN NEW; END IF;'; IF v_run_maint IS FALSE THEN v_trig_func := v_trig_func ||' IF (NEW.'||v_control||' % '||v_part_interval||') > ('||v_part_interval||' / 2) THEN v_id_position := (length(v_last_partition) - position(''p_'' in reverse(v_last_partition))) + 2; v_last_partition_id = substring(v_last_partition from v_id_position)::bigint; v_next_partition_id := v_last_partition_id + '||v_part_interval||'; IF NEW.'||v_control||' >= v_next_partition_id THEN RETURN NEW; END IF; WHILE ((v_next_partition_id - v_current_partition_id) / '||v_part_interval||') <= '||v_premake||' LOOP v_partition_created := @extschema@.create_partition_id('||quote_literal(p_parent_table)||', ARRAY[v_next_partition_id]); IF v_partition_created THEN PERFORM @extschema@.create_function_id('||quote_literal(p_parent_table)||'); PERFORM @extschema@.apply_constraints('||quote_literal(p_parent_table)||'); END IF; v_next_partition_id := v_next_partition_id + '||v_part_interval||'; END LOOP; END IF;'; END IF; v_trig_func := v_trig_func ||' END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for dynamic id table: '||p_parent_table); END IF; ELSE RAISE EXCEPTION 'ERROR: Invalid id partitioning type given: %', v_type; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE FUNCTION: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''Partition function maintenance for table '||p_parent_table||' failed'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to manage pre-creation of the next partitions in a set. * Also manages dropping old partitions if the retention option is set. * If p_parent_table is passed, will only run run_maintenance() on that one table (no matter what the configuration table may have set for it) * Otherwise, will run on all tables in the config table with p_run_maintenance() set to true. * For large partition sets, running analyze can cause maintenance to take longer than expected. Can set p_analyze to false to avoid a forced analyze run. * Be aware that constraint exclusion may not work properly until an analyze on the partition set is run. */ CREATE OR REPLACE FUNCTION run_maintenance(p_parent_table text DEFAULT NULL, p_analyze boolean DEFAULT true, p_jobmon boolean DEFAULT true) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_create_count int := 0; v_current_partition text; v_current_partition_id bigint; v_current_partition_timestamp timestamp; v_datetime_string text; v_drop_count int := 0; v_id_position int; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_created boolean; v_last_partition_id bigint; v_last_partition_timestamp timestamp; v_next_partition_id bigint; v_next_partition_timestamp timestamp; v_old_search_path text; v_premade_count int; v_quarter text; v_step_id bigint; v_step_overflow_id bigint; v_step_serial_id bigint; v_sub_parent text; v_row record; v_row_max_id record; v_row_sub record; v_tablename text; v_tables_list_sql text; v_time_position int; v_year text; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman maintenance already running.'; RETURN; END IF; 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' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN RUN MAINTENANCE'); v_step_id := add_step(v_job_id, 'Running maintenance loop'); END IF; v_tables_list_sql := 'SELECT parent_table , type , part_interval , control , premake , datetime_string , undo_in_progress FROM @extschema@.part_config'; IF p_parent_table IS NULL THEN v_tables_list_sql := v_tables_list_sql || ' WHERE use_run_maintenance = true'; ELSE v_tables_list_sql := v_tables_list_sql || format(' WHERE parent_table = %L', p_parent_table); END IF; FOR v_row IN EXECUTE v_tables_list_sql LOOP CONTINUE WHEN v_row.undo_in_progress; SELECT show_partitions INTO v_last_partition FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LIMIT 1; IF v_row.type = 'time-static' OR v_row.type = 'time-dynamic' OR v_row.type = 'time-custom' THEN IF v_row.type = 'time-static' OR v_row.type = 'time-dynamic' THEN CASE WHEN v_row.part_interval::interval = '15 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_row.part_interval::interval = '30 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_row.part_interval::interval = '1 hour' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_row.part_interval::interval = '1 day' THEN v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_row.part_interval::interval = '1 week' THEN v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_row.part_interval::interval = '1 month' THEN v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_row.part_interval::interval = '3 months' THEN v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_row.part_interval::interval = '1 year' THEN v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; ELSIF v_row.type = 'time-custom' THEN SELECT child_table INTO v_current_partition FROM @extschema@.custom_time_partitions WHERE parent_table = v_row.parent_table AND partition_range @> CURRENT_TIMESTAMP; IF v_current_partition IS NULL THEN RAISE EXCEPTION 'Current time partition missing from custom_time_partitions config table for table % and timestamp %', CURRENT_TIMESTAMP, v_row.parent_table; END IF; v_time_position := (length(v_current_partition) - position('p_' in reverse(v_current_partition))) + 2; v_current_partition_timestamp := to_timestamp(substring(v_current_partition from v_time_position), v_row.datetime_string); END IF; v_time_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; IF v_row.part_interval::interval <> '3 months' OR (v_row.part_interval::interval = '3 months' AND v_row.type = 'time-custom') THEN v_last_partition_timestamp := to_timestamp(substring(v_last_partition from v_time_position), v_row.datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(v_last_partition from v_time_position), 'q', 1); v_quarter := split_part(substring(v_last_partition from v_time_position), 'q', 2); CASE WHEN v_quarter = '1' THEN v_last_partition_timestamp := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_last_partition_timestamp := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_last_partition_timestamp := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_last_partition_timestamp := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; -- Check and see how many premade partitions there are. -- Can be negative when subpartitioning and there are parent partitions in the past compared to current timestamp value. -- abs() prevents run_maintenence from running on those old parent tables v_premade_count = abs(round(EXTRACT('epoch' FROM age(v_last_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.part_interval::interval))); v_next_partition_timestamp := v_last_partition_timestamp; -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed. WHILE v_premade_count < v_row.premake LOOP BEGIN v_next_partition_timestamp := v_next_partition_timestamp + v_row.part_interval::interval; EXCEPTION WHEN datetime_field_overflow THEN v_premade_count := v_row.premake; -- do this so it can exit the premake check loop and continue in the outer for loop IF v_jobmon_schema IS NOT NULL THEN 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 skippd for parent table '||v_partition_time); END IF; RAISE WARNING 'Attempted partition time interval is outside PostgreSQL''s supported time range. Child partition creation skipped for parent table %', v_row.parent_table; CONTINUE; END; v_last_partition_created := @extschema@.create_partition_time(v_row.parent_table, ARRAY[v_next_partition_timestamp], p_analyze); v_create_count := v_create_count + 1; IF v_row.type = 'time-static' AND v_last_partition_created THEN PERFORM @extschema@.create_function_time(v_row.parent_table); END IF; -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(v_row.parent_table); -- Can be negative when subpartitioning and there are parent partitions in the past compared to current timestamp value. -- abs() prevents run_maintenence from running on those old parent tables v_premade_count = abs(round(EXTRACT('epoch' FROM age(v_next_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.part_interval::interval))); END LOOP; ELSIF v_row.type = 'id-static' OR v_row.type ='id-dynamic' THEN -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT show_partitions FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP EXECUTE 'SELECT '||v_row.control||' - ('||v_row.control||' % '||v_row.part_interval::int||') FROM '||v_row_max_id.show_partitions||' WHERE '||v_row.control||' = (SELECT max('||v_row.control||') FROM '||v_row_max_id.show_partitions||')' INTO v_current_partition_id; IF v_current_partition_id IS NOT NULL THEN EXIT; END IF; END LOOP; v_id_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; v_last_partition_id = substring(v_last_partition from v_id_position)::bigint; v_next_partition_id := v_last_partition_id + v_row.part_interval::bigint; -- Can be negative when subpartitioning and there are parent partitions with lower values compared to current id value. -- abs() prevents run_maintenence from running on those old parent tables WHILE (abs((v_next_partition_id - v_current_partition_id) / v_row.part_interval::bigint)) <= v_row.premake LOOP v_last_partition_created := @extschema@.create_partition_id(v_row.parent_table, ARRAY[v_next_partition_id], p_analyze); IF v_last_partition_created THEN PERFORM @extschema@.create_function_id(v_row.parent_table); PERFORM @extschema@.apply_constraints(v_row.parent_table); END IF; v_next_partition_id := v_next_partition_id + v_row.part_interval::bigint; END LOOP; END IF; -- end main IF check for time or id END LOOP; -- end of creation loop -- Manage dropping old partitions if retention option is set FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND (type = 'time-static' OR type = 'time-dynamic' OR type = 'time-custom') LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); END IF; END IF; END LOOP; FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND (type = 'id-static' OR type = 'id-dynamic') LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); END IF; END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Partition maintenance finished. '||v_create_count||' partitions made. '||v_drop_count||' partitions dropped.'); IF v_step_overflow_id IS NOT NULL OR v_step_serial_id IS NOT NULL THEN PERFORM fail_job(v_job_id); ELSE PERFORM close_job(v_job_id); END IF; EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN RUN MAINTENANCE'')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to drop child tables from an id-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ CREATE OR REPLACE FUNCTION drop_partition_id(p_parent_table text, p_retention bigint DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_child_table text; v_control text; v_drop_count int := 0; v_id_position int; v_index record; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_max bigint; v_old_search_path text; v_part_interval bigint; v_partition_id bigint; v_retention bigint; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_row_max_id record; v_step_id bigint; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman drop_partition_id')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_id already running.'; RETURN 0; END IF; -- Allow override of configuration options IF p_retention IS NULL THEN SELECT part_interval::bigint , control , retention::bigint , retention_keep_table , retention_keep_index , retention_schema , jobmon INTO v_part_interval , v_control , v_retention , v_retention_keep_table , v_retention_keep_index , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic') AND retention IS NOT NULL; IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE SELECT part_interval::bigint , control , retention_keep_table , retention_keep_index , retention_schema , jobmon INTO v_part_interval , v_control , v_retention_keep_table , v_retention_keep_index , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); v_retention := p_retention; IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN DROP ID PARTITION: '|| p_parent_table); END IF; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT show_partitions FROM @extschema@.show_partitions(p_parent_table, 'DESC') LOOP EXECUTE 'SELECT max('||v_control||') FROM '||v_row_max_id.show_partitions INTO v_max; IF v_max IS NOT NULL THEN EXIT; END IF; END LOOP; -- Loop through child tables of the given parent FOR v_child_table IN SELECT n.nspname||'.'||c.relname FROM pg_inherits i join pg_class c ON i.inhrelid = c.oid join pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC LOOP v_id_position := (length(v_child_table) - position('p_' in reverse(v_child_table))) + 2; v_partition_id := substring(v_child_table from v_id_position)::bigint; -- Add one interval since partition names contain the start of the constraint period IF v_retention <= (v_max - (v_partition_id + v_part_interval)) THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Uninherit table '||v_child_table||' from '||p_parent_table); END IF; EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Drop table '||v_child_table); END IF; EXECUTE 'DROP TABLE '||v_child_table||' CASCADE'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN FOR v_index IN SELECT i.indexrelid::regclass AS name , c.conname FROM pg_catalog.pg_index i LEFT JOIN pg_catalog.pg_constraint c ON i.indexrelid = c.conindid WHERE i.indrelid = v_child_table::regclass LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Drop index '||v_index.name||' from '||v_child_table); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_child_table||' DROP CONSTRAINT '||v_index.conname; ELSE EXECUTE 'DROP INDEX '||v_index.name; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Moving table '||v_child_table||' to schema '||v_retention_schema); END IF; EXECUTE 'ALTER TABLE '||v_child_table||' SET SCHEMA '||v_retention_schema; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if -- If child table is a subpartition, remove it from part_config & part_config_sub (should cascade due to FK) DELETE FROM @extschema@.part_config WHERE parent_table = v_child_table; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', v_drop_count||' partitions dropped.'); PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_drop_count; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN DROP ID PARTITION: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to create a child table in a time-based partition set */ CREATE OR REPLACE FUNCTION create_partition_time (p_parent_table text, p_partition_times timestamp[], p_analyze boolean DEFAULT true) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_datetime_string text; v_grantees text[]; v_hasoids boolean; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_partition_created boolean := false; v_partition_name text; v_partition_suffix text; v_parent_tablespace text; v_part_interval interval; v_partition_timestamp_end timestamp; v_partition_timestamp_start timestamp; v_quarter text; v_revoke text[]; v_row record; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_sub_timestamp_max timestamp; v_sub_timestamp_min timestamp; v_tablename text; v_time_position int; v_top_datetime_string text; v_top_interval interval; v_top_parent text; v_trunc_value text; v_time timestamp; v_type text; v_unlogged char; v_year text; BEGIN SELECT type , control , part_interval , inherit_fk , jobmon , datetime_string INTO v_type , v_control , v_part_interval , v_inherit_fk , v_jobmon , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic' OR type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; -- Check if parent table is a subpartition of an already existing time-based partition set managed by pg_partman -- If so, limit what child tables can be created based on parent suffix WITH top_oid AS ( SELECT i.inhparent AS top_parent_oid FROM pg_catalog.pg_class c JOIN pg_catalog.pg_inherits i ON c.oid = i.inhrelid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname||'.'||c.relname = p_parent_table ) SELECT n.nspname||'.'||c.relname, p.datetime_string INTO v_top_parent, v_top_datetime_string FROM pg_catalog.pg_class c JOIN top_oid t ON c.oid = t.top_parent_oid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace JOIN @extschema@.part_config p ON p.parent_table = n.nspname||'.'||c.relname WHERE c.oid = t.top_parent_oid AND p.type = 'time-static' OR p.type = 'time-dynamic' OR p.type = 'time-custom'; IF v_top_parent IS NOT NULL THEN SELECT part_interval::interval INTO v_top_interval FROM @extschema@.part_config WHERE parent_table = v_top_parent; v_time_position := (length(p_parent_table) - position('p_' in reverse(p_parent_table))) + 2; IF v_part_interval::interval <> '3 months' OR (v_part_interval::interval = '3 months' AND v_type = 'time-custom') THEN v_sub_timestamp_min := to_timestamp(substring(p_parent_table from v_time_position), v_top_datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(p_parent_table from v_time_position), 'q', 1); v_quarter := split_part(substring(p_parent_table from v_time_position), 'q', 2); CASE WHEN v_quarter = '1' THEN v_sub_timestamp_min := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_sub_timestamp_min := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_sub_timestamp_min := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_sub_timestamp_min := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; v_sub_timestamp_max = (v_sub_timestamp_min + v_top_interval::interval) - '1 sec'::interval; END IF; SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); END IF; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_timestamp_start := v_time; BEGIN v_partition_timestamp_end := v_time + v_part_interval; 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_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_time||' skipped'); CONTINUE; END; -- This suffix generation code is in partition_data_time() as well v_partition_suffix := to_char(v_time, 'YYYY'); IF v_part_interval < '1 year' AND v_part_interval <> '1 week' THEN v_partition_suffix := v_partition_suffix ||'_'|| to_char(v_time, 'MM'); IF v_part_interval < '1 month' AND v_part_interval <> '1 week' THEN v_partition_suffix := v_partition_suffix ||'_'|| to_char(v_time, 'DD'); IF v_part_interval < '1 day' THEN v_partition_suffix := v_partition_suffix || '_' || to_char(v_time, 'HH24MI'); IF v_part_interval < '1 minute' THEN v_partition_suffix := v_partition_suffix || to_char(v_time, 'SS'); END IF; -- end < minute IF END IF; -- end < day IF END IF; -- end < month IF END IF; -- end < year IF IF v_part_interval = '1 week' THEN v_partition_suffix := to_char(v_time, 'IYYY') || 'w' || to_char(v_time, 'IW'); END IF; -- "Q" is ignored in to_timestamp, so handle special case IF v_part_interval = '3 months' AND (v_type = 'time-static' OR v_type = 'time-dynamic') THEN v_year := to_char(v_time, 'YYYY'); v_quarter := to_char(v_time, 'Q'); v_partition_suffix := v_year || 'q' || v_quarter; END IF; -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_timestamp_min IS NOT NULL THEN IF v_time < v_sub_timestamp_min OR v_time > v_sub_timestamp_max THEN CONTINUE; END IF; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_partition_suffix, TRUE); SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_tablename IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_partition_timestamp_start||' to '||(v_partition_timestamp_end-'1sec'::interval)); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class WHERE oid::regclass = p_parent_table::regclass; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; v_sql := v_sql || ' TABLE '||v_partition_name||' (LIKE '||p_parent_table||' INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)'; SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class WHERE oid::regclass = p_parent_table::regclass; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_parent_tablespace IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_partition_name||' SET TABLESPACE '||v_parent_tablespace; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' ADD CONSTRAINT '||v_tablename||'_partition_check CHECK ('||v_control||'>='||quote_literal(v_partition_timestamp_start)||' AND '||v_control||'<'||quote_literal(v_partition_timestamp_end)||')'; EXECUTE 'ALTER TABLE '||v_partition_name||' INHERIT '||p_parent_table; -- If custom time, set extra config options. IF v_type = 'time-custom' THEN INSERT INTO @extschema@.custom_time_partitions (parent_table, child_table, partition_range) VALUES ( p_parent_table, v_partition_name, tstzrange(v_partition_timestamp_start, v_partition_timestamp_end, '[)') ); END IF; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP EXECUTE 'GRANT '||array_to_string(v_parent_grant.types, ',')||' ON '||v_partition_name||' TO '||v_parent_grant.grantee; SELECT array_agg(r) INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE '||array_to_string(v_revoke, ',')||' ON '||v_partition_name||' FROM '||v_parent_grant.grantee||' CASCADE'; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN SELECT array_agg(r) INTO v_revoke FROM ( SELECT DISTINCT grantee::text AS r FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_partition_name EXCEPT SELECT unnest(v_grantees)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE ALL ON '||v_partition_name||' FROM '||array_to_string(v_revoke, ','); END IF; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' OWNER TO '||v_parent_owner; IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(quote_ident(v_parent_schema)||'.'||quote_ident(v_parent_tablename), v_partition_name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_control , sub_type , sub_part_interval , sub_constraint_cols , sub_premake , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_use_run_maintenance , sub_jobmon FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Subpartitioning '||v_partition_name); END IF; 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_use_run_maintenance := %L , p_inherit_fk := %L , p_jobmon := %L )' , v_partition_name , v_row.sub_control , v_row.sub_type , v_row.sub_part_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_inherit_fk , v_row.sub_use_run_maintenance , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index WHERE parent_table = v_partition_name; END LOOP; -- end sub partitioning LOOP v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Analyzing partition set: '||p_parent_table); END IF; EXECUTE 'ANALYZE '||p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, 'No partitions created for partition set: '||p_parent_table); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; 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; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_partition_created; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE TABLE: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Apply constraints managed by partman extension */ CREATE OR REPLACE FUNCTION apply_constraints(p_parent_table text, p_child_table text DEFAULT NULL, p_analyze boolean DEFAULT FALSE, p_debug boolean DEFAULT FALSE) RETURNS void LANGUAGE plpgsql AS $$ DECLARE v_child_table text; v_child_tablename text; v_col text; v_constraint_cols text[]; v_constraint_col_type text; v_constraint_name text; v_datetime_string text; v_existing_constraint_name text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_id int; v_last_partition_timestamp timestamp; v_constraint_values record; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_part_interval text; v_partition_suffix text; v_premake int; v_sql text; v_step_id bigint; v_suffix_position int; v_type text; BEGIN SELECT type , part_interval , premake , datetime_string , constraint_cols , jobmon INTO v_type , v_part_interval , v_premake , v_datetime_string , v_constraint_cols , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_constraint_cols IS NULL THEN IF p_debug THEN RAISE NOTICE 'Given parent table (%) not set up for constraint management (constraint_cols is NULL)', p_parent_table; END IF; -- Returns silently to allow this function to be simply called by maintenance processes without having to check if config options are set. RETURN; END IF; SELECT show_partitions INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE CONSTRAINT: '||p_parent_table); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; -- If p_child_table is null, figure out the partition that is the one right before the premake value backwards. IF p_child_table IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Automatically determining most recent child on which to apply constraints'); END IF; v_suffix_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; IF v_type IN ('time-static', 'time-dynamic', 'time-custom') THEN v_last_partition_timestamp := to_timestamp(substring(v_last_partition from v_suffix_position), v_datetime_string); v_partition_suffix := to_char(v_last_partition_timestamp - (v_part_interval::interval * ((v_premake * 2)+1) ), v_datetime_string); ELSIF v_type IN ('id-static', 'id-dynamic') THEN v_last_partition_id := substring(v_last_partition from v_suffix_position)::int; v_partition_suffix := (v_last_partition_id - (v_part_interval::int * ((v_premake * 2)+1) ))::text; END IF; v_child_table := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_partition_suffix, TRUE); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Target child table: '||v_child_table); END IF; ELSE v_child_table := p_child_table; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Checking if target child table exists'); END IF; SELECT tablename INTO v_child_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_child_table; IF v_child_tablename IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', 'Target child table ('||v_child_table||') does not exist. Skipping constraint creation.'); PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; IF p_debug THEN RAISE NOTICE 'Target child table (%) does not exist. Skipping constraint creation.', v_child_table; END IF; RETURN; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; FOREACH v_col IN ARRAY v_constraint_cols LOOP SELECT c.conname INTO v_existing_constraint_name FROM pg_catalog.pg_constraint c JOIN pg_catalog.pg_attribute a ON c.conrelid = a.attrelid WHERE conrelid = v_child_table::regclass AND c.conname LIKE 'partmanconstr_%' AND c.contype = 'c' AND a.attname = v_col AND ARRAY[a.attnum] <@ c.conkey AND a.attisdropped = false; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying new constraint on column: '||v_col); END IF; IF v_existing_constraint_name IS NOT NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', 'Partman managed constraint already exists on this table ('||v_child_table||') and column ('||v_col||'). Skipping creation.'); END IF; RAISE WARNING 'Partman managed constraint already exists on this table (%) and column (%). Skipping creation.', v_child_table, v_col ; CONTINUE; END IF; -- Ensure column name gets put on end of constraint name to help avoid naming conflicts v_constraint_name := @extschema@.check_name_length('partmanconstr_'||v_child_tablename, p_suffix := '_'||v_col); EXECUTE 'SELECT min('||v_col||')::text AS min, max('||v_col||')::text AS max FROM '||v_child_table INTO v_constraint_values; IF v_constraint_values IS NOT NULL THEN v_sql := concat('ALTER TABLE ', v_child_table, ' ADD CONSTRAINT ', v_constraint_name , ' CHECK (', v_col, ' >= ', quote_literal(v_constraint_values.min), ' AND ' , v_col, ' <= ', quote_literal(v_constraint_values.max), ')' ); IF p_debug THEN RAISE NOTICE 'Constraint creation query: %', v_sql; END IF; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'New constraint created: '||v_sql); END IF; ELSE IF p_debug THEN RAISE NOTICE 'Given column (%) contains all NULLs. No constraint created', v_col; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', 'Given column ('||v_col||') contains all NULLs. No constraint created'); END IF; END IF; END LOOP; IF p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Running analyze on partition set: '||p_parent_table); END IF; IF p_debug THEN RAISE NOTICE 'Running analyze on partition set: %', p_parent_table; END IF; EXECUTE 'ANALYZE '||p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE CONSTRAINT: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; pg_partman-4.6.0/updates/pg_partman--1.8.2--1.8.3.sql000066400000000000000000001376671412761706100214630ustar00rootroot00000000000000-- Fix both the retention system and the undo partitioning functions/scripts not cleaning up the custom_time_partitions table when using a custom time interval (Github Issue #49). -- When using sub-partitioning, the call to create_parent() that is within create_partition_id() & create_partition_time() was passing 2 of the parameters through incorrectly. p_use_run_maintenance was being fed to p_inherit_fk and vice versa, so the inherit_fk and use_run_maintenance columns in part_config & part_config_sub may be reversed. Since these are both boolean parameters, no error was being raised. If you've used sub-partitioning and used anything other than the default values for either of these configuration options, please double-check the part_config & part_config_sub tables to ensure the proper values are there. If you did not set them specifically, the default values were set for both and things should be fine. -- If p_use_run_maintenance was set wrong, you likely noticed that new partitions were not getting created for new sub-partition sets. You'll still have to fix any existing config settings, but future ones shoould be fine now. -- If p_inherit_fk was set wrong, child tables were likely not inheriting FKs or they were inheriting them when you didn't want them to. Again, fix this for existing partition sets by correcting the config table and all future sub-partitions should now be set properly. If you need to generate FKs on child tables that were missing them, you can use the reapply_foreign_keys.py script. /* * Function to undo time-based partitioning created by this extension */ CREATE OR REPLACE FUNCTION undo_partition_time(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval interval DEFAULT NULL, p_keep_table boolean DEFAULT true, p_lock_wait numeric DEFAULT 0) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_batch_loop_count int := 0; v_child_min timestamptz; v_child_loop_total bigint := 0; v_child_table text; v_control text; v_function_name text; v_inner_loop_count int; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_move_sql text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_part_interval interval; v_row record; v_rowcount bigint; v_step_id bigint; v_sub_count int; v_total bigint := 0; v_trig_name text; v_type text; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_time_partition')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_time_partition already running.'; RETURN 0; END IF; SELECT type , part_interval::interval , control , jobmon INTO v_type , v_part_interval , v_control , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic' OR type = 'time-custom'); IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; -- Check if any child tables are themselves partitioned or part of an inheritance tree. Prevent undo at this level if so. -- Need to either lock child tables at all levels or handle the proper removal of triggers on all child tables first -- before multi-level undo can be performed safely. FOR v_row IN SELECT show_partitions AS child_table FROM @extschema@.show_partitions(p_parent_table) LOOP SELECT count(*) INTO v_sub_count FROM pg_catalog.pg_inherits WHERE inhparent::regclass = v_row.child_table::regclass; IF v_sub_count > 0 THEN RAISE EXCEPTION 'Child table for this parent has child table(s) itself (%). Run undo partitioning on this table or remove inheritance first to ensure all data is properly moved to parent', v_row.child_table; END IF; END LOOP; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Undoing partitioning for table '||p_parent_table); END IF; IF p_batch_interval IS NULL THEN p_batch_interval := v_part_interval; END IF; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables. SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, '_part_trig_func', FALSE); SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t WHERE tgname = v_trig_name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE 'LOCK TABLE ONLY '||p_parent_table||' IN ACCESS EXCLUSIVE MODE NOWAIT'; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; RETURN -1; END IF; END IF; -- END p_lock_wait IF EXECUTE 'DROP TRIGGER IF EXISTS '||v_trig_name||' ON '||p_parent_table; END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE 'DROP FUNCTION IF EXISTS '||v_function_name||'()'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); END IF; <> WHILE v_batch_loop_count < p_batch_count LOOP SELECT n.nspname||'.'||c.relname INTO v_child_table FROM pg_inherits i JOIN pg_class c ON i.inhrelid = c.oid JOIN pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC; EXIT WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing child partition: '||v_child_table); END IF; EXECUTE 'SELECT min('||v_control||') FROM '||v_child_table INTO v_child_min; IF v_child_min IS NULL THEN -- No rows left in this child table. Remove from partition set. -- lockwait timeout for table drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE 'LOCK TABLE ONLY '||v_child_table||' IN ACCESS EXCLUSIVE MODE NOWAIT'; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; RETURN -1; END IF; END IF; -- END p_lock_wait IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF p_keep_table = false THEN EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table UNINHERITED, not DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; END IF; IF v_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_child_table; END IF; v_undo_count := v_undo_count + 1; CONTINUE outer_child_loop; END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE 'SELECT * FROM ' || v_child_table || ' WHERE '||v_control||' <= '||quote_literal(v_child_min + (p_batch_interval * v_inner_loop_count)) ||' FOR UPDATE NOWAIT'; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; RETURN -1; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval v_move_sql := 'WITH move_data AS (DELETE FROM '||v_child_table|| ' WHERE '||v_control||' <= '||quote_literal(v_child_min + (p_batch_interval * v_inner_loop_count))||' RETURNING *) INSERT INTO '||p_parent_table||' SELECT * FROM move_data'; EXECUTE v_move_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Moved '||v_child_loop_total||' rows to parent.'); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; IF v_batch_loop_count < p_batch_count THEN -- FOR loop never ran, so there's no child tables left. DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) to the parent. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', 'Copied '||v_total||' row(s) to the parent. Removed '||v_undo_count||' partitions.'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_total; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN UNDO PARTITIONING: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to drop child tables from a time-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ CREATE OR REPLACE FUNCTION drop_partition_time(p_parent_table text, p_retention interval DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_child_table text; v_datetime_string text; v_drop_count int := 0; v_index record; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_part_interval interval; v_partition_timestamp timestamp; v_quarter text; v_retention interval; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_step_id bigint; v_time_position int; v_type text; v_year text; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman drop_partition_time')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_time already running.'; RETURN 0; END IF; -- Allow override of configuration options IF p_retention IS NULL THEN SELECT type , part_interval::interval , retention::interval , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_type , v_part_interval , v_retention , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic' OR type = 'time-custom') AND retention IS NOT NULL; IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE SELECT type , part_interval::interval , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_type , v_part_interval , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic' OR type = 'time-custom'); v_retention := p_retention; IF v_part_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN DROP TIME PARTITION: '|| p_parent_table); END IF; -- Loop through child tables of the given parent FOR v_child_table IN SELECT n.nspname||'.'||c.relname FROM pg_inherits i join pg_class c ON i.inhrelid = c.oid join pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC LOOP -- pull out datetime portion of partition's tablename to make the next one v_time_position := (length(v_child_table) - position('p_' in reverse(v_child_table))) + 2; IF v_part_interval <> '3 months' OR (v_part_interval = '3 months' AND v_type = 'time-custom') THEN v_partition_timestamp := to_timestamp(substring(v_child_table from v_time_position), v_datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(v_child_table from v_time_position), 'q', 1); v_quarter := split_part(substring(v_child_table from v_time_position), 'q', 2); CASE WHEN v_quarter = '1' THEN v_partition_timestamp := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_partition_timestamp := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_partition_timestamp := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_partition_timestamp := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; -- Add one interval since partition names contain the start of the constraint period IF v_retention < (CURRENT_TIMESTAMP - (v_partition_timestamp + v_part_interval)) THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Uninherit table '||v_child_table||' from '||p_parent_table); END IF; IF v_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_child_table; END IF; EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Drop table '||v_child_table); END IF; EXECUTE 'DROP TABLE '||v_child_table||' CASCADE'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN FOR v_index IN SELECT i.indexrelid::regclass AS name , c.conname FROM pg_catalog.pg_index i LEFT JOIN pg_catalog.pg_constraint c ON i.indexrelid = c.conindid WHERE i.indrelid = v_child_table::regclass LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Drop index '||v_index.name||' from '||v_child_table); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_child_table||' DROP CONSTRAINT '||v_index.conname; ELSE EXECUTE 'DROP INDEX '||v_index.name; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Moving table '||v_child_table||' to schema '||v_retention_schema); END IF; EXECUTE 'ALTER TABLE '||v_child_table||' SET SCHEMA '||v_retention_schema; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if -- If child table is a subpartition, remove it from part_config & part_config_sub (should cascade due to FK) DELETE FROM @extschema@.part_config WHERE parent_table = v_child_table; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', v_drop_count||' partitions dropped.'); PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_drop_count; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN DROP TIME PARTITION: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to create id partitions */ CREATE OR REPLACE FUNCTION create_partition_id(p_parent_table text, p_partition_ids bigint[], p_analyze boolean DEFAULT true) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_grantees text[]; v_hasoids boolean; v_id bigint; v_id_position int; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_parent_tablespace text; v_part_interval bigint; v_partition_created boolean := false; v_partition_name text; v_revoke text[]; v_row record; v_sql text; v_step_id bigint; v_sub_id_max bigint; v_sub_id_min bigint; v_tablename text; v_top_interval bigint; v_top_parent text; v_unlogged char; BEGIN SELECT control , part_interval , inherit_fk , jobmon INTO v_control , v_part_interval , v_inherit_fk , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; -- Check if parent table is a subpartition of an already existing id based partition set managed by pg_partman -- If so, limit what child tables can be created based on parent suffix WITH top_oid AS ( SELECT i.inhparent AS top_parent_oid FROM pg_catalog.pg_class c JOIN pg_catalog.pg_inherits i ON c.oid = i.inhrelid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname||'.'||c.relname = p_parent_table ) SELECT n.nspname||'.'||c.relname INTO v_top_parent FROM pg_catalog.pg_class c JOIN top_oid t ON c.oid = t.top_parent_oid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace JOIN @extschema@.part_config p ON p.parent_table = n.nspname||'.'||c.relname WHERE c.oid = t.top_parent_oid AND p.type = 'id-static' OR p.type = 'id-dynamic'; IF v_top_parent IS NOT NULL THEN SELECT part_interval::bigint INTO v_top_interval FROM @extschema@.part_config WHERE parent_table = v_top_parent; v_id_position := (length(p_parent_table) - position('p_' in reverse(p_parent_table))) + 2; v_sub_id_min = substring(p_parent_table from v_id_position)::bigint; v_sub_id_max = (v_sub_id_min + v_top_interval) - 1; END IF; SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); END IF; FOREACH v_id IN ARRAY p_partition_ids LOOP -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_id_min IS NOT NULL THEN IF v_id < v_sub_id_min OR v_id > v_sub_id_max THEN CONTINUE; END IF; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_id::text, TRUE); -- If child table already exists, skip creation SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_tablename IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_id||' to '||(v_id + v_part_interval)-1); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class WHERE oid::regclass = p_parent_table::regclass; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; v_sql := v_sql || ' TABLE '||v_partition_name||' (LIKE '||p_parent_table||' INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)'; SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class WHERE oid::regclass = p_parent_table::regclass; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_parent_tablespace IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_partition_name||' SET TABLESPACE '||v_parent_tablespace; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' ADD CONSTRAINT '||v_tablename||'_partition_check CHECK ('||v_control||'>='||quote_literal(v_id)||' AND '||v_control||'<'||quote_literal(v_id + v_part_interval)||')'; EXECUTE 'ALTER TABLE '||v_partition_name||' INHERIT '||p_parent_table; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP EXECUTE 'GRANT '||array_to_string(v_parent_grant.types, ',')||' ON '||v_partition_name||' TO '||v_parent_grant.grantee; SELECT array_agg(r) INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE '||array_to_string(v_revoke, ',')||' ON '||v_partition_name||' FROM '||v_parent_grant.grantee||' CASCADE'; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN SELECT array_agg(r) INTO v_revoke FROM ( SELECT DISTINCT grantee::text AS r FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_partition_name EXCEPT SELECT unnest(v_grantees)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE ALL ON '||v_partition_name||' FROM '||array_to_string(v_revoke, ','); END IF; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' OWNER TO '||v_parent_owner; IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(quote_ident(v_parent_schema)||'.'||quote_ident(v_parent_tablename), v_partition_name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_control , sub_type , sub_part_interval , sub_constraint_cols , sub_premake , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_use_run_maintenance , sub_jobmon FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Subpartitioning '||v_partition_name); END IF; 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_use_run_maintenance := %L , p_inherit_fk := %L , p_jobmon := %L )' , v_partition_name , v_row.sub_control , v_row.sub_type , v_row.sub_part_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_use_run_maintenance , v_row.sub_inherit_fk , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index WHERE parent_table = v_partition_name; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; -- end sub partitioning LOOP v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Analyzing partition set: '||p_parent_table); END IF; EXECUTE 'ANALYZE '||p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, 'No partitions created for partition set: '||p_parent_table); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; PERFORM close_job(v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_partition_created; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE TABLE: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to create a child table in a time-based partition set */ CREATE OR REPLACE FUNCTION create_partition_time (p_parent_table text, p_partition_times timestamp[], p_analyze boolean DEFAULT true) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_datetime_string text; v_grantees text[]; v_hasoids boolean; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_partition_created boolean := false; v_partition_name text; v_partition_suffix text; v_parent_tablespace text; v_part_interval interval; v_partition_timestamp_end timestamp; v_partition_timestamp_start timestamp; v_quarter text; v_revoke text[]; v_row record; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_sub_timestamp_max timestamp; v_sub_timestamp_min timestamp; v_tablename text; v_time_position int; v_top_datetime_string text; v_top_interval interval; v_top_parent text; v_trunc_value text; v_time timestamp; v_type text; v_unlogged char; v_year text; BEGIN SELECT type , control , part_interval , inherit_fk , jobmon , datetime_string INTO v_type , v_control , v_part_interval , v_inherit_fk , v_jobmon , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic' OR type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; -- Check if parent table is a subpartition of an already existing time-based partition set managed by pg_partman -- If so, limit what child tables can be created based on parent suffix WITH top_oid AS ( SELECT i.inhparent AS top_parent_oid FROM pg_catalog.pg_class c JOIN pg_catalog.pg_inherits i ON c.oid = i.inhrelid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname||'.'||c.relname = p_parent_table ) SELECT n.nspname||'.'||c.relname, p.datetime_string INTO v_top_parent, v_top_datetime_string FROM pg_catalog.pg_class c JOIN top_oid t ON c.oid = t.top_parent_oid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace JOIN @extschema@.part_config p ON p.parent_table = n.nspname||'.'||c.relname WHERE c.oid = t.top_parent_oid AND p.type = 'time-static' OR p.type = 'time-dynamic' OR p.type = 'time-custom'; IF v_top_parent IS NOT NULL THEN SELECT part_interval::interval INTO v_top_interval FROM @extschema@.part_config WHERE parent_table = v_top_parent; v_time_position := (length(p_parent_table) - position('p_' in reverse(p_parent_table))) + 2; IF v_part_interval::interval <> '3 months' OR (v_part_interval::interval = '3 months' AND v_type = 'time-custom') THEN v_sub_timestamp_min := to_timestamp(substring(p_parent_table from v_time_position), v_top_datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(p_parent_table from v_time_position), 'q', 1); v_quarter := split_part(substring(p_parent_table from v_time_position), 'q', 2); CASE WHEN v_quarter = '1' THEN v_sub_timestamp_min := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_sub_timestamp_min := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_sub_timestamp_min := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_sub_timestamp_min := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; v_sub_timestamp_max = (v_sub_timestamp_min + v_top_interval::interval) - '1 sec'::interval; END IF; SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); END IF; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_timestamp_start := v_time; BEGIN v_partition_timestamp_end := v_time + v_part_interval; 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_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_time||' skipped'); CONTINUE; END; -- This suffix generation code is in partition_data_time() as well v_partition_suffix := to_char(v_time, 'YYYY'); IF v_part_interval < '1 year' AND v_part_interval <> '1 week' THEN v_partition_suffix := v_partition_suffix ||'_'|| to_char(v_time, 'MM'); IF v_part_interval < '1 month' AND v_part_interval <> '1 week' THEN v_partition_suffix := v_partition_suffix ||'_'|| to_char(v_time, 'DD'); IF v_part_interval < '1 day' THEN v_partition_suffix := v_partition_suffix || '_' || to_char(v_time, 'HH24MI'); IF v_part_interval < '1 minute' THEN v_partition_suffix := v_partition_suffix || to_char(v_time, 'SS'); END IF; -- end < minute IF END IF; -- end < day IF END IF; -- end < month IF END IF; -- end < year IF IF v_part_interval = '1 week' THEN v_partition_suffix := to_char(v_time, 'IYYY') || 'w' || to_char(v_time, 'IW'); END IF; -- "Q" is ignored in to_timestamp, so handle special case IF v_part_interval = '3 months' AND (v_type = 'time-static' OR v_type = 'time-dynamic') THEN v_year := to_char(v_time, 'YYYY'); v_quarter := to_char(v_time, 'Q'); v_partition_suffix := v_year || 'q' || v_quarter; END IF; -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_timestamp_min IS NOT NULL THEN IF v_time < v_sub_timestamp_min OR v_time > v_sub_timestamp_max THEN CONTINUE; END IF; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_partition_suffix, TRUE); SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_tablename IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_partition_timestamp_start||' to '||(v_partition_timestamp_end-'1sec'::interval)); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class WHERE oid::regclass = p_parent_table::regclass; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; v_sql := v_sql || ' TABLE '||v_partition_name||' (LIKE '||p_parent_table||' INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)'; SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class WHERE oid::regclass = p_parent_table::regclass; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_parent_tablespace IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_partition_name||' SET TABLESPACE '||v_parent_tablespace; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' ADD CONSTRAINT '||v_tablename||'_partition_check CHECK ('||v_control||'>='||quote_literal(v_partition_timestamp_start)||' AND '||v_control||'<'||quote_literal(v_partition_timestamp_end)||')'; EXECUTE 'ALTER TABLE '||v_partition_name||' INHERIT '||p_parent_table; -- If custom time, set extra config options. IF v_type = 'time-custom' THEN INSERT INTO @extschema@.custom_time_partitions (parent_table, child_table, partition_range) VALUES ( p_parent_table, v_partition_name, tstzrange(v_partition_timestamp_start, v_partition_timestamp_end, '[)') ); END IF; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP EXECUTE 'GRANT '||array_to_string(v_parent_grant.types, ',')||' ON '||v_partition_name||' TO '||v_parent_grant.grantee; SELECT array_agg(r) INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE '||array_to_string(v_revoke, ',')||' ON '||v_partition_name||' FROM '||v_parent_grant.grantee||' CASCADE'; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN SELECT array_agg(r) INTO v_revoke FROM ( SELECT DISTINCT grantee::text AS r FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_partition_name EXCEPT SELECT unnest(v_grantees)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE ALL ON '||v_partition_name||' FROM '||array_to_string(v_revoke, ','); END IF; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' OWNER TO '||v_parent_owner; IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(quote_ident(v_parent_schema)||'.'||quote_ident(v_parent_tablename), v_partition_name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_control , sub_type , sub_part_interval , sub_constraint_cols , sub_premake , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_use_run_maintenance , sub_jobmon FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Subpartitioning '||v_partition_name); END IF; 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_use_run_maintenance := %L , p_inherit_fk := %L , p_jobmon := %L )' , v_partition_name , v_row.sub_control , v_row.sub_type , v_row.sub_part_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_use_run_maintenance , v_row.sub_inherit_fk , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index WHERE parent_table = v_partition_name; END LOOP; -- end sub partitioning LOOP v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Analyzing partition set: '||p_parent_table); END IF; EXECUTE 'ANALYZE '||p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, 'No partitions created for partition set: '||p_parent_table); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; 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; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_partition_created; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE TABLE: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; pg_partman-4.6.0/updates/pg_partman--1.8.3--1.8.4.sql000066400000000000000000000166301412761706100214470ustar00rootroot00000000000000-- When inheriting foreign keys to children, also account for the following additional options: -- MATCH FULL/PARTIAL/SIMPLE -- ON UPDATE/DELETE NO ACTION/RESTRICT/CASCADE/SET NULL/SET DEFAULT -- DEFERRABLE / NOT DEFERRABLE -- INITIALLY IMMEDIATE/DEFERRED -- Note that none of the above properties were being inherited to child tables before. If you need to reapply foreign keys on children to enforce these options, see the reapply_foreign_keys.py python script or apply_foreign_keys() plpgsql function. The script is the preferred method to avoid contentions. -- reapply_foreign_keys.py claimed it could work on partition sets not managed by pg_partman, but that wasn't true. Removed dependency on show_partitions() function, so now that is true. /* * Apply foreign keys that exist on the given parent to the given child table */ CREATE OR REPLACE FUNCTION apply_foreign_keys(p_parent_table text, p_child_table text DEFAULT NULL, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql AS $$ DECLARE v_job_id bigint; v_jobmon text; v_jobmon_schema text; v_old_search_path text; v_ref_schema text; v_ref_table text; v_row record; v_schemaname text; v_sql text; v_step_id bigint; v_tablename text; BEGIN SELECT jobmon INTO v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN APPLYING FOREIGN KEYS: '||p_parent_table); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Checking if target child table exists'); END IF; SELECT schemaname, tablename INTO v_schemaname, v_tablename FROM pg_catalog.pg_tables WHERE schemaname||'.'||tablename = p_child_table; IF v_tablename IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'CRITICAL', 'Target child table ('||v_child_table||') does not exist.'); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION 'Target child table (%.%) does not exist.', v_schemaname, v_tablename; RETURN; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; FOR v_row IN SELECT n.nspname||'.'||cl.relname AS ref_table , '"'||string_agg(att.attname, '","')||'"' AS ref_column , '"'||string_agg(att2.attname, '","')||'"' AS child_column , keys.condeferred , keys.condeferrable , keys.confupdtype , keys.confdeltype , keys.confmatchtype FROM ( SELECT unnest(con.conkey) as ref , unnest(con.confkey) as child , con.confrelid , con.conrelid , con.condeferred , con.condeferrable , con.confupdtype , con.confdeltype , con.confmatchtype FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN pg_catalog.pg_constraint con ON c.oid = con.conrelid WHERE n.nspname ||'.'|| c.relname = p_parent_table AND con.contype = 'f' ORDER BY con.conkey ) keys JOIN pg_catalog.pg_class cl ON cl.oid = keys.confrelid JOIN pg_catalog.pg_namespace n ON cl.relnamespace = n.oid JOIN pg_catalog.pg_attribute att ON att.attrelid = keys.confrelid AND att.attnum = keys.child JOIN pg_catalog.pg_attribute att2 ON att2.attrelid = keys.conrelid AND att2.attnum = keys.ref GROUP BY n.nspname, cl.relname, keys.condeferred, keys.condeferrable, keys.confupdtype, keys.confdeltype, keys.confmatchtype LOOP SELECT schemaname, tablename INTO v_ref_schema, v_ref_table FROM pg_tables WHERE schemaname||'.'||tablename = v_row.ref_table; v_sql := format('ALTER TABLE %I.%I ADD FOREIGN KEY (%s) REFERENCES %I.%I (%s)', v_schemaname, v_tablename, v_row.child_column, v_ref_schema, v_ref_table, v_row.ref_column); CASE WHEN v_row.confmatchtype = 'f' THEN v_sql := v_sql || ' MATCH FULL '; WHEN (v_row.confmatchtype = 's' OR v_row.confmatchtype = 'u') THEN v_sql := v_sql || ' MATCH SIMPLE '; WHEN v_row.confmatchtype = 'p' THEN v_sql := v_sql || ' MATCH PARTIAL '; END CASE; CASE WHEN v_row.confupdtype = 'a' THEN v_sql := v_sql || ' ON UPDATE NO ACTION '; WHEN v_row.confupdtype = 'r' THEN v_sql := v_sql || ' ON UPDATE RESTRICT '; WHEN v_row.confupdtype = 'c' THEN v_sql := v_sql || ' ON UPDATE CASCADE '; WHEN v_row.confupdtype = 'n' THEN v_sql := v_sql || ' ON UPDATE SET NULL '; WHEN v_row.confupdtype = 'd' THEN v_sql := v_sql || ' ON UPDATE SET DEFAULT '; END CASE; CASE WHEN v_row.confdeltype = 'a' THEN v_sql := v_sql || ' ON DELETE NO ACTION '; WHEN v_row.confdeltype = 'r' THEN v_sql := v_sql || ' ON DELETE RESTRICT '; WHEN v_row.confdeltype = 'c' THEN v_sql := v_sql || ' ON DELETE CASCADE '; WHEN v_row.confdeltype = 'n' THEN v_sql := v_sql || ' ON DELETE SET NULL '; WHEN v_row.confdeltype = 'd' THEN v_sql := v_sql || ' ON DELETE SET DEFAULT '; END CASE; CASE WHEN v_row.condeferrable = true AND v_row.condeferred = true THEN v_sql := v_sql || ' DEFERRABLE INITIALLY DEFERRED '; WHEN v_row.condeferrable = false AND v_row.condeferred = false THEN v_sql := v_sql || ' NOT DEFERRABLE '; WHEN v_row.condeferrable = true AND v_row.condeferred = false THEN v_sql := v_sql || ' DEFERRABLE INITIALLY IMMEDIATE '; END CASE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying FK: '||v_sql); END IF; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'FK applied'); END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN APPLYING FOREIGN KEYS: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; pg_partman-4.6.0/updates/pg_partman--1.8.4--1.8.5.sql000066400000000000000000001253411412761706100214510ustar00rootroot00000000000000-- If run_maintenance() had not been called for a while outside of a partition set's interval, running it again was not creating the necessary child partitions to catch up. Recovery from this scenario was possible if the latest partition was manually created. This update fixes the problem and if you have a partition set that was not catching up before, running it with this version installed should fix things with no further manual intervention. This bug was introduced with version 1.8.0. (Github Issue #56). /* * Check if parent table is a subpartition of an already existing id based partition set managed by pg_partman * If so, limit what child tables can be created based on parent suffix */ CREATE FUNCTION check_subpartition_limits(p_parent_table text, p_type text, OUT sub_min text, OUT sub_max text) RETURNS record LANGUAGE plpgsql AS $$ DECLARE v_datetime_string text; v_id_position int; v_partition_interval interval; v_quarter text; v_sub_id_max bigint; v_sub_id_min bigint; v_sub_timestamp_max timestamp; v_sub_timestamp_min timestamp; v_time_position int; v_top_datetime_string text; v_top_interval text; v_top_parent text; v_top_type text; v_year text; BEGIN -- CTE query is done individually for each type (time, id) because it should return NULL if the top parent is not the same type in a subpartition set (id->time or time->id) IF p_type = 'id' THEN WITH top_oid AS ( SELECT i.inhparent AS top_parent_oid FROM pg_catalog.pg_class c JOIN pg_catalog.pg_inherits i ON c.oid = i.inhrelid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname||'.'||c.relname = p_parent_table ) SELECT n.nspname||'.'||c.relname, p.datetime_string, p.part_interval, p.type INTO v_top_parent, v_top_datetime_string, v_top_interval, v_top_type FROM pg_catalog.pg_class c JOIN top_oid t ON c.oid = t.top_parent_oid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace JOIN @extschema@.part_config p ON p.parent_table = n.nspname||'.'||c.relname WHERE c.oid = t.top_parent_oid AND p.type = 'id-static' OR p.type = 'id-dynamic'; IF v_top_parent IS NOT NULL THEN v_id_position := (length(p_parent_table) - position('p_' in reverse(p_parent_table))) + 2; v_sub_id_min := substring(p_parent_table from v_id_position)::bigint; v_sub_id_max := (v_sub_id_min + v_top_interval::bigint) - 1; sub_min := v_sub_id_min::text; sub_max := v_sub_id_max::text; END IF; ELSIF p_type = 'time' THEN WITH top_oid AS ( SELECT i.inhparent AS top_parent_oid FROM pg_catalog.pg_class c JOIN pg_catalog.pg_inherits i ON c.oid = i.inhrelid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname||'.'||c.relname = p_parent_table ) SELECT n.nspname||'.'||c.relname, p.datetime_string, p.part_interval, p.type INTO v_top_parent, v_top_datetime_string, v_top_interval, v_top_type FROM pg_catalog.pg_class c JOIN top_oid t ON c.oid = t.top_parent_oid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace JOIN @extschema@.part_config p ON p.parent_table = n.nspname||'.'||c.relname WHERE c.oid = t.top_parent_oid AND p.type = 'time-static' OR p.type = 'time-dynamic' OR p.type = 'time-custom'; IF v_top_parent IS NOT NULL THEN v_time_position := (length(p_parent_table) - position('p_' in reverse(p_parent_table))) + 2; IF v_top_interval::interval <> '3 months' OR (v_top_interval::interval = '3 months' AND v_top_type = 'time-custom') THEN v_sub_timestamp_min := to_timestamp(substring(p_parent_table from v_time_position), v_top_datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(p_parent_table from v_time_position), 'q', 1); v_quarter := split_part(substring(p_parent_table from v_time_position), 'q', 2); CASE WHEN v_quarter = '1' THEN v_sub_timestamp_min := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_sub_timestamp_min := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_sub_timestamp_min := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_sub_timestamp_min := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; v_sub_timestamp_max = (v_sub_timestamp_min + v_top_interval::interval) - '1 sec'::interval; sub_min := v_sub_timestamp_min::text; sub_max := v_sub_timestamp_max::text; END IF; ELSE RAISE EXCEPTION 'Invalid type given as parameter to check_subpartition_limits()'; END IF; RETURN; END $$; /* * Function to manage pre-creation of the next partitions in a set. * Also manages dropping old partitions if the retention option is set. * If p_parent_table is passed, will only run run_maintenance() on that one table (no matter what the configuration table may have set for it) * Otherwise, will run on all tables in the config table with p_run_maintenance() set to true. * For large partition sets, running analyze can cause maintenance to take longer than expected. Can set p_analyze to false to avoid a forced analyze run. * Be aware that constraint exclusion may not work properly until an analyze on the partition set is run. */ CREATE OR REPLACE FUNCTION run_maintenance(p_parent_table text DEFAULT NULL, p_analyze boolean DEFAULT true, p_jobmon boolean DEFAULT true) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_create_count int := 0; v_current_partition text; v_current_partition_id bigint; v_current_partition_timestamp timestamp; v_datetime_string text; v_drop_count int := 0; v_id_position int; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_created boolean; v_last_partition_id bigint; v_last_partition_timestamp timestamp; v_next_partition_id bigint; v_next_partition_timestamp timestamp; v_old_search_path text; v_premade_count int; v_quarter text; v_step_id bigint; v_step_overflow_id bigint; v_step_serial_id bigint; v_sub_id_max bigint; v_sub_id_min bigint; v_sub_parent text; v_sub_timestamp_max timestamp; v_sub_timestamp_min timestamp; v_row record; v_row_max_id record; v_row_sub record; v_tablename text; v_tables_list_sql text; v_time_position int; v_year text; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman maintenance already running.'; RETURN; END IF; 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' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN RUN MAINTENANCE'); v_step_id := add_step(v_job_id, 'Running maintenance loop'); END IF; v_tables_list_sql := 'SELECT parent_table , type , part_interval , control , premake , datetime_string , undo_in_progress FROM @extschema@.part_config'; IF p_parent_table IS NULL THEN v_tables_list_sql := v_tables_list_sql || ' WHERE use_run_maintenance = true'; ELSE v_tables_list_sql := v_tables_list_sql || format(' WHERE parent_table = %L', p_parent_table); END IF; FOR v_row IN EXECUTE v_tables_list_sql LOOP CONTINUE WHEN v_row.undo_in_progress; SELECT show_partitions INTO v_last_partition FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LIMIT 1; IF v_row.type = 'time-static' OR v_row.type = 'time-dynamic' OR v_row.type = 'time-custom' THEN IF v_row.type = 'time-static' OR v_row.type = 'time-dynamic' THEN CASE WHEN v_row.part_interval::interval = '15 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_row.part_interval::interval = '30 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_row.part_interval::interval = '1 hour' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_row.part_interval::interval = '1 day' THEN v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_row.part_interval::interval = '1 week' THEN v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_row.part_interval::interval = '1 month' THEN v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_row.part_interval::interval = '3 months' THEN v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_row.part_interval::interval = '1 year' THEN v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; ELSIF v_row.type = 'time-custom' THEN SELECT child_table INTO v_current_partition FROM @extschema@.custom_time_partitions WHERE parent_table = v_row.parent_table AND partition_range @> CURRENT_TIMESTAMP; IF v_current_partition IS NULL THEN RAISE EXCEPTION 'Current time partition missing from custom_time_partitions config table for table % and timestamp %', CURRENT_TIMESTAMP, v_row.parent_table; END IF; v_time_position := (length(v_current_partition) - position('p_' in reverse(v_current_partition))) + 2; v_current_partition_timestamp := to_timestamp(substring(v_current_partition from v_time_position), v_row.datetime_string); END IF; -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::timestamp, sub_max::timestamp INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'time'); -- No need to run maintenance if it's outside the bounds of the top parent. IF v_sub_timestamp_min IS NOT NULL THEN IF v_current_partition_timestamp < v_sub_timestamp_min OR v_current_partition_timestamp > v_sub_timestamp_max THEN CONTINUE; END IF; END IF; v_time_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; IF v_row.part_interval::interval <> '3 months' OR (v_row.part_interval::interval = '3 months' AND v_row.type = 'time-custom') THEN v_last_partition_timestamp := to_timestamp(substring(v_last_partition from v_time_position), v_row.datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(v_last_partition from v_time_position), 'q', 1); v_quarter := split_part(substring(v_last_partition from v_time_position), 'q', 2); CASE WHEN v_quarter = '1' THEN v_last_partition_timestamp := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_last_partition_timestamp := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_last_partition_timestamp := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_last_partition_timestamp := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; -- Check and see how many premade partitions there are. v_premade_count = round(EXTRACT('epoch' FROM age(v_last_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.part_interval::interval)); v_next_partition_timestamp := v_last_partition_timestamp; -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed. WHILE v_premade_count < v_row.premake LOOP BEGIN v_next_partition_timestamp := v_next_partition_timestamp + v_row.part_interval::interval; EXCEPTION WHEN datetime_field_overflow THEN v_premade_count := v_row.premake; -- do this so it can exit the premake check loop and continue in the outer for loop IF v_jobmon_schema IS NOT NULL THEN 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 skippd for parent table '||v_partition_time); END IF; RAISE WARNING 'Attempted partition time interval is outside PostgreSQL''s supported time range. Child partition creation skipped for parent table %', v_row.parent_table; CONTINUE; END; v_last_partition_created := @extschema@.create_partition_time(v_row.parent_table, ARRAY[v_next_partition_timestamp], p_analyze); v_create_count := v_create_count + 1; IF v_row.type = 'time-static' AND v_last_partition_created THEN PERFORM @extschema@.create_function_time(v_row.parent_table); END IF; -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(v_row.parent_table); -- Can be negative when subpartitioning and there are parent partitions in the past compared to current timestamp value. -- abs() prevents run_maintenence from running on those old parent tables v_premade_count = round(EXTRACT('epoch' FROM age(v_next_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.part_interval::interval)); END LOOP; ELSIF v_row.type = 'id-static' OR v_row.type ='id-dynamic' THEN -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT show_partitions FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP EXECUTE 'SELECT '||v_row.control||' - ('||v_row.control||' % '||v_row.part_interval::int||') FROM '||v_row_max_id.show_partitions||' WHERE '||v_row.control||' = (SELECT max('||v_row.control||') FROM '||v_row_max_id.show_partitions||')' INTO v_current_partition_id; IF v_current_partition_id IS NOT NULL THEN EXIT; END IF; END LOOP; -- Determine if this table is a child of a subpartition parent. If so, get limits to see if run_maintenance even needs to run for it. SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'id'); -- No need to run maintenance if it's outside the bounds of the top parent. IF v_sub_id_min IS NOT NULL THEN IF v_current_partition_id < v_sub_id_min OR v_current_partition_id > v_sub_id_max THEN CONTINUE; END IF; END IF; v_id_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; v_last_partition_id = substring(v_last_partition from v_id_position)::bigint; v_next_partition_id := v_last_partition_id + v_row.part_interval::bigint; WHILE ((v_next_partition_id - v_current_partition_id) / v_row.part_interval::bigint) <= v_row.premake LOOP v_last_partition_created := @extschema@.create_partition_id(v_row.parent_table, ARRAY[v_next_partition_id], p_analyze); IF v_last_partition_created THEN PERFORM @extschema@.create_function_id(v_row.parent_table); PERFORM @extschema@.apply_constraints(v_row.parent_table); END IF; v_next_partition_id := v_next_partition_id + v_row.part_interval::bigint; END LOOP; END IF; -- end main IF check for time or id END LOOP; -- end of creation loop -- Manage dropping old partitions if retention option is set FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND (type = 'time-static' OR type = 'time-dynamic' OR type = 'time-custom') LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); END IF; END IF; END LOOP; FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND (type = 'id-static' OR type = 'id-dynamic') LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); END IF; END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Partition maintenance finished. '||v_create_count||' partitions made. '||v_drop_count||' partitions dropped.'); IF v_step_overflow_id IS NOT NULL OR v_step_serial_id IS NOT NULL THEN PERFORM fail_job(v_job_id); ELSE PERFORM close_job(v_job_id); END IF; EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN RUN MAINTENANCE'')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to create a child table in a time-based partition set */ CREATE OR REPLACE FUNCTION create_partition_time (p_parent_table text, p_partition_times timestamp[], p_analyze boolean DEFAULT true) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_grantees text[]; v_hasoids boolean; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_partition_created boolean := false; v_partition_name text; v_partition_suffix text; v_parent_tablespace text; v_part_interval interval; v_partition_timestamp_end timestamp; v_partition_timestamp_start timestamp; v_quarter text; v_revoke text[]; v_row record; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_sub_timestamp_max timestamp; v_sub_timestamp_min timestamp; v_tablename text; v_trunc_value text; v_time timestamp; v_type text; v_unlogged char; v_year text; BEGIN SELECT type , control , part_interval , inherit_fk , jobmon , datetime_string INTO v_type , v_control , v_part_interval , v_inherit_fk , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'time-static' OR type = 'time-dynamic' OR type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::timestamp, sub_max::timestamp INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'time'); SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); END IF; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_timestamp_start := v_time; BEGIN v_partition_timestamp_end := v_time + v_part_interval; 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_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_time||' skipped'); CONTINUE; END; -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_timestamp_min IS NOT NULL THEN IF v_time < v_sub_timestamp_min OR v_time > v_sub_timestamp_max THEN CONTINUE; END IF; END IF; -- This suffix generation code is in partition_data_time() as well v_partition_suffix := to_char(v_time, 'YYYY'); IF v_part_interval < '1 year' AND v_part_interval <> '1 week' THEN v_partition_suffix := v_partition_suffix ||'_'|| to_char(v_time, 'MM'); IF v_part_interval < '1 month' AND v_part_interval <> '1 week' THEN v_partition_suffix := v_partition_suffix ||'_'|| to_char(v_time, 'DD'); IF v_part_interval < '1 day' THEN v_partition_suffix := v_partition_suffix || '_' || to_char(v_time, 'HH24MI'); IF v_part_interval < '1 minute' THEN v_partition_suffix := v_partition_suffix || to_char(v_time, 'SS'); END IF; -- end < minute IF END IF; -- end < day IF END IF; -- end < month IF END IF; -- end < year IF IF v_part_interval = '1 week' THEN v_partition_suffix := to_char(v_time, 'IYYY') || 'w' || to_char(v_time, 'IW'); END IF; -- "Q" is ignored in to_timestamp, so handle special case IF v_part_interval = '3 months' AND (v_type = 'time-static' OR v_type = 'time-dynamic') THEN v_year := to_char(v_time, 'YYYY'); v_quarter := to_char(v_time, 'Q'); v_partition_suffix := v_year || 'q' || v_quarter; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_partition_suffix, TRUE); SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_tablename IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_partition_timestamp_start||' to '||(v_partition_timestamp_end-'1sec'::interval)); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class WHERE oid::regclass = p_parent_table::regclass; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; v_sql := v_sql || ' TABLE '||v_partition_name||' (LIKE '||p_parent_table||' INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)'; SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class WHERE oid::regclass = p_parent_table::regclass; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_parent_tablespace IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_partition_name||' SET TABLESPACE '||v_parent_tablespace; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' ADD CONSTRAINT '||v_tablename||'_partition_check CHECK ('||v_control||'>='||quote_literal(v_partition_timestamp_start)||' AND '||v_control||'<'||quote_literal(v_partition_timestamp_end)||')'; EXECUTE 'ALTER TABLE '||v_partition_name||' INHERIT '||p_parent_table; -- If custom time, set extra config options. IF v_type = 'time-custom' THEN INSERT INTO @extschema@.custom_time_partitions (parent_table, child_table, partition_range) VALUES ( p_parent_table, v_partition_name, tstzrange(v_partition_timestamp_start, v_partition_timestamp_end, '[)') ); END IF; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP EXECUTE 'GRANT '||array_to_string(v_parent_grant.types, ',')||' ON '||v_partition_name||' TO '||v_parent_grant.grantee; SELECT array_agg(r) INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE '||array_to_string(v_revoke, ',')||' ON '||v_partition_name||' FROM '||v_parent_grant.grantee||' CASCADE'; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN SELECT array_agg(r) INTO v_revoke FROM ( SELECT DISTINCT grantee::text AS r FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_partition_name EXCEPT SELECT unnest(v_grantees)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE ALL ON '||v_partition_name||' FROM '||array_to_string(v_revoke, ','); END IF; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' OWNER TO '||v_parent_owner; IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(quote_ident(v_parent_schema)||'.'||quote_ident(v_parent_tablename), v_partition_name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_control , sub_type , sub_part_interval , sub_constraint_cols , sub_premake , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_use_run_maintenance , sub_jobmon FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Subpartitioning '||v_partition_name); END IF; 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_use_run_maintenance := %L , p_inherit_fk := %L , p_jobmon := %L )' , v_partition_name , v_row.sub_control , v_row.sub_type , v_row.sub_part_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_use_run_maintenance , v_row.sub_inherit_fk , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index WHERE parent_table = v_partition_name; END LOOP; -- end sub partitioning LOOP v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Analyzing partition set: '||p_parent_table); END IF; EXECUTE 'ANALYZE '||p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, 'No partitions created for partition set: '||p_parent_table); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; 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; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_partition_created; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE TABLE: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to create id partitions */ CREATE OR REPLACE FUNCTION create_partition_id(p_parent_table text, p_partition_ids bigint[], p_analyze boolean DEFAULT true) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_grantees text[]; v_hasoids boolean; v_id bigint; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_parent_tablespace text; v_part_interval bigint; v_partition_created boolean := false; v_partition_name text; v_revoke text[]; v_row record; v_sql text; v_step_id bigint; v_sub_id_max bigint; v_sub_id_min bigint; v_tablename text; v_unlogged char; BEGIN SELECT control , part_interval , inherit_fk , jobmon INTO v_control , v_part_interval , v_inherit_fk , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (type = 'id-static' OR type = 'id-dynamic'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'id'); SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); END IF; FOREACH v_id IN ARRAY p_partition_ids LOOP -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_id_min IS NOT NULL THEN IF v_id < v_sub_id_min OR v_id > v_sub_id_max THEN CONTINUE; END IF; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_id::text, TRUE); -- If child table already exists, skip creation SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_tablename IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_id||' to '||(v_id + v_part_interval)-1); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class WHERE oid::regclass = p_parent_table::regclass; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; v_sql := v_sql || ' TABLE '||v_partition_name||' (LIKE '||p_parent_table||' INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)'; SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class WHERE oid::regclass = p_parent_table::regclass; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_parent_tablespace IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_partition_name||' SET TABLESPACE '||v_parent_tablespace; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' ADD CONSTRAINT '||v_tablename||'_partition_check CHECK ('||v_control||'>='||quote_literal(v_id)||' AND '||v_control||'<'||quote_literal(v_id + v_part_interval)||')'; EXECUTE 'ALTER TABLE '||v_partition_name||' INHERIT '||p_parent_table; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP EXECUTE 'GRANT '||array_to_string(v_parent_grant.types, ',')||' ON '||v_partition_name||' TO '||v_parent_grant.grantee; SELECT array_agg(r) INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE '||array_to_string(v_revoke, ',')||' ON '||v_partition_name||' FROM '||v_parent_grant.grantee||' CASCADE'; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN SELECT array_agg(r) INTO v_revoke FROM ( SELECT DISTINCT grantee::text AS r FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_partition_name EXCEPT SELECT unnest(v_grantees)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE ALL ON '||v_partition_name||' FROM '||array_to_string(v_revoke, ','); END IF; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' OWNER TO '||v_parent_owner; IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(quote_ident(v_parent_schema)||'.'||quote_ident(v_parent_tablename), v_partition_name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_control , sub_type , sub_part_interval , sub_constraint_cols , sub_premake , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_use_run_maintenance , sub_jobmon FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Subpartitioning '||v_partition_name); END IF; 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_use_run_maintenance := %L , p_inherit_fk := %L , p_jobmon := %L )' , v_partition_name , v_row.sub_control , v_row.sub_type , v_row.sub_part_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_use_run_maintenance , v_row.sub_inherit_fk , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index WHERE parent_table = v_partition_name; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; -- end sub partitioning LOOP v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Analyzing partition set: '||p_parent_table); END IF; EXECUTE 'ANALYZE '||p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, 'No partitions created for partition set: '||p_parent_table); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; PERFORM close_job(v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_partition_created; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN CREATE TABLE: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; pg_partman-4.6.0/updates/pg_partman--1.8.5--1.8.6.sql000066400000000000000000000366061412761706100214600ustar00rootroot00000000000000-- Fixed bug with run_maintenance() and subpartitioning. If you had any subpartitioned sets that went back a significant amount of time, or had a significant number of subpartitions, calls to run_maintenance() would seem to hang. It would finish eventually, but could take quite a long time and if you have pg_jobmon installed, would also cause many entries mentioning old partition sets. This bug was introduced with the fixed included in v1.8.5. /* * Function to manage pre-creation of the next partitions in a set. * Also manages dropping old partitions if the retention option is set. * If p_parent_table is passed, will only run run_maintenance() on that one table (no matter what the configuration table may have set for it) * Otherwise, will run on all tables in the config table with p_run_maintenance() set to true. * For large partition sets, running analyze can cause maintenance to take longer than expected. Can set p_analyze to false to avoid a forced analyze run. * Be aware that constraint exclusion may not work properly until an analyze on the partition set is run. */ CREATE OR REPLACE FUNCTION run_maintenance(p_parent_table text DEFAULT NULL, p_analyze boolean DEFAULT true, p_jobmon boolean DEFAULT true) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_create_count int := 0; v_current_partition text; v_current_partition_id bigint; v_current_partition_timestamp timestamp; v_datetime_string text; v_drop_count int := 0; v_id_position int; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_created boolean; v_last_partition_id bigint; v_last_partition_timestamp timestamp; v_next_partition_id bigint; v_next_partition_timestamp timestamp; v_old_search_path text; v_premade_count int; v_quarter text; v_step_id bigint; v_step_overflow_id bigint; v_step_serial_id bigint; v_sub_id_max bigint; v_sub_id_min bigint; v_sub_parent text; v_sub_timestamp_max timestamp; v_sub_timestamp_min timestamp; v_row record; v_row_max_id record; v_row_sub record; v_tablename text; v_tables_list_sql text; v_time_position int; v_year text; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman maintenance already running.'; RETURN; END IF; 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' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN RUN MAINTENANCE'); v_step_id := add_step(v_job_id, 'Running maintenance loop'); END IF; v_tables_list_sql := 'SELECT parent_table , type , part_interval , control , premake , datetime_string , undo_in_progress FROM @extschema@.part_config'; IF p_parent_table IS NULL THEN v_tables_list_sql := v_tables_list_sql || ' WHERE use_run_maintenance = true'; ELSE v_tables_list_sql := v_tables_list_sql || format(' WHERE parent_table = %L', p_parent_table); END IF; FOR v_row IN EXECUTE v_tables_list_sql LOOP CONTINUE WHEN v_row.undo_in_progress; SELECT show_partitions INTO v_last_partition FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LIMIT 1; IF v_row.type = 'time-static' OR v_row.type = 'time-dynamic' OR v_row.type = 'time-custom' THEN IF v_row.type = 'time-static' OR v_row.type = 'time-dynamic' THEN CASE WHEN v_row.part_interval::interval = '15 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_row.part_interval::interval = '30 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_row.part_interval::interval = '1 hour' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_row.part_interval::interval = '1 day' THEN v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_row.part_interval::interval = '1 week' THEN v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_row.part_interval::interval = '1 month' THEN v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_row.part_interval::interval = '3 months' THEN v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_row.part_interval::interval = '1 year' THEN v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; ELSIF v_row.type = 'time-custom' THEN SELECT child_table INTO v_current_partition FROM @extschema@.custom_time_partitions WHERE parent_table = v_row.parent_table AND partition_range @> CURRENT_TIMESTAMP; IF v_current_partition IS NULL THEN RAISE EXCEPTION 'Current time partition missing from custom_time_partitions config table for table % and timestamp %', CURRENT_TIMESTAMP, v_row.parent_table; END IF; v_time_position := (length(v_current_partition) - position('p_' in reverse(v_current_partition))) + 2; v_current_partition_timestamp := to_timestamp(substring(v_current_partition from v_time_position), v_row.datetime_string); END IF; -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::timestamp, sub_max::timestamp INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'time'); -- No need to run maintenance if it's outside the bounds of the top parent. IF v_sub_timestamp_min IS NOT NULL THEN IF v_current_partition_timestamp < v_sub_timestamp_min OR v_current_partition_timestamp > v_sub_timestamp_max THEN CONTINUE; END IF; END IF; v_time_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; IF v_row.part_interval::interval <> '3 months' OR (v_row.part_interval::interval = '3 months' AND v_row.type = 'time-custom') THEN v_last_partition_timestamp := to_timestamp(substring(v_last_partition from v_time_position), v_row.datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(v_last_partition from v_time_position), 'q', 1); v_quarter := split_part(substring(v_last_partition from v_time_position), 'q', 2); CASE WHEN v_quarter = '1' THEN v_last_partition_timestamp := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_last_partition_timestamp := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_last_partition_timestamp := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_last_partition_timestamp := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; -- Check and see how many premade partitions there are. v_premade_count = round(EXTRACT('epoch' FROM age(v_last_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.part_interval::interval)); v_next_partition_timestamp := v_last_partition_timestamp; -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed. WHILE v_premade_count < v_row.premake LOOP BEGIN v_next_partition_timestamp := v_next_partition_timestamp + v_row.part_interval::interval; EXCEPTION WHEN datetime_field_overflow THEN v_premade_count := v_row.premake; -- do this so it can exit the premake check loop and continue in the outer for loop IF v_jobmon_schema IS NOT NULL THEN 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 skippd for parent table '||v_partition_time); END IF; RAISE WARNING 'Attempted partition time interval is outside PostgreSQL''s supported time range. Child partition creation skipped for parent table %', v_row.parent_table; CONTINUE; END; v_last_partition_created := @extschema@.create_partition_time(v_row.parent_table, ARRAY[v_next_partition_timestamp], p_analyze); v_create_count := v_create_count + 1; IF v_row.type = 'time-static' AND v_last_partition_created THEN PERFORM @extschema@.create_function_time(v_row.parent_table); END IF; -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(v_row.parent_table); -- Can be negative when subpartitioning and there are parent partitions in the past compared to current timestamp value. -- abs() prevents run_maintenence from running on those old parent tables v_premade_count = round(EXTRACT('epoch' FROM age(v_next_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.part_interval::interval)); END LOOP; ELSIF v_row.type = 'id-static' OR v_row.type ='id-dynamic' THEN -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT show_partitions FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP EXECUTE 'SELECT '||v_row.control||' - ('||v_row.control||' % '||v_row.part_interval::int||') FROM '||v_row_max_id.show_partitions||' WHERE '||v_row.control||' = (SELECT max('||v_row.control||') FROM '||v_row_max_id.show_partitions||')' INTO v_current_partition_id; IF v_current_partition_id IS NOT NULL THEN EXIT; END IF; END LOOP; -- Determine if this table is a child of a subpartition parent. If so, get limits to see if run_maintenance even needs to run for it. SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'id'); -- No need to run maintenance if it's outside the bounds of the top parent. IF v_sub_id_min IS NOT NULL THEN IF v_current_partition_id < v_sub_id_min OR v_current_partition_id > v_sub_id_max THEN CONTINUE; END IF; END IF; v_id_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; v_last_partition_id = substring(v_last_partition from v_id_position)::bigint; v_next_partition_id := v_last_partition_id + v_row.part_interval::bigint; WHILE ((v_next_partition_id - v_current_partition_id) / v_row.part_interval::bigint) <= v_row.premake LOOP v_last_partition_created := @extschema@.create_partition_id(v_row.parent_table, ARRAY[v_next_partition_id], p_analyze); IF v_last_partition_created THEN PERFORM @extschema@.create_function_id(v_row.parent_table); PERFORM @extschema@.apply_constraints(v_row.parent_table); END IF; v_next_partition_id := v_next_partition_id + v_row.part_interval::bigint; END LOOP; END IF; -- end main IF check for time or id END LOOP; -- end of creation loop -- Manage dropping old partitions if retention option is set FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND (type = 'time-static' OR type = 'time-dynamic' OR type = 'time-custom') LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); END IF; END IF; END LOOP; FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND (type = 'id-static' OR type = 'id-dynamic') LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); END IF; END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Partition maintenance finished. '||v_create_count||' partitions made. '||v_drop_count||' partitions dropped.'); IF v_step_overflow_id IS NOT NULL OR v_step_serial_id IS NOT NULL THEN PERFORM fail_job(v_job_id); ELSE PERFORM close_job(v_job_id); END IF; EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN RUN MAINTENANCE'')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; pg_partman-4.6.0/updates/pg_partman--1.8.6--1.8.7.sql000066400000000000000000000040241412761706100214470ustar00rootroot00000000000000-- This update has no core code changes. It is being released to give users that ran into a situation where the trigger functions for serial partition sets called a function that was renamed. In v1.8.0 create_id_partition() was renamed to create_partition_id(). All core code was updated to use this new function, but any existing trigger function from previous versions may have the old function name in the code that creates new partitions when the current one reaches 50% of the max. -- The code block below will go through and recreate all the trigger functions on all partition sets managed by pg_partman. This should fix the issue described above. While only serial partition sets should have been affected, the code below does it for both time & id to just ensure everything is fixed. -- This code MUST must be run manually because if the trigger functions are recreated as part of an extension update, then those trigger functions become members of the pg_partman extension itself. Just copy-n-paste it into psql and things should be good to go. /*********************************************************** -- Begin code to fix partition functions -- You may need to adjust the setting of the search_path below to point the following commands to the schema that partman is installed to SELECT set_config('search_path','partman, "$user", public',false); CREATE TEMP TABLE partman_trigfix_temp (statement text); INSERT INTO partman_trigfix_temp SELECT format('SELECT create_function_id(%L);', parent_table) FROM part_config WHERE type like 'id%'; INSERT INTO partman_trigfix_temp SELECT format('SELECT create_function_time(%L);', parent_table) FROM part_config WHERE type like 'time%'; DO $$ DECLARE v_row record; BEGIN FOR v_row IN SELECT statement FROM partman_trigfix_temp LOOP IF v_row.statement IS NOT NULL THEN EXECUTE v_row.statement; END IF; END LOOP; END $$; DROP TABLE IF EXISTS partman_trigfix_temp; -- End code to fix partition functions ************************************************************/ pg_partman-4.6.0/updates/pg_partman--1.8.7--1.8.8.sql000066400000000000000000000474211412761706100214610ustar00rootroot00000000000000-- Fixed bug in apply_foreign_keys() that was causing it to fail if there were multiple FKs to the same foreign column. Also fixed a bug that allow the child name parameter to not be passed and didn't handle it if it wasn't. Thanks to Andrew Dunstan for the bug fix and tremendously easier to manage code (Github Issue #64). -- Recreate the trigger function when dropping tables as part of retention to ensure those tables aren't used in the trigger condition (Github Issue #62). CREATE TEMP TABLE partman_preserve_privs_temp (statement text); INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.apply_foreign_keys(text, text, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'apply_foreign_keys'; DROP FUNCTION @extschema@.apply_foreign_keys(text, text, boolean); /* * Apply foreign keys that exist on the given parent to the given child table */ CREATE OR REPLACE FUNCTION apply_foreign_keys(p_parent_table text, p_child_table text, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql AS $$ DECLARE v_job_id bigint; v_jobmon text; v_jobmon_schema text; v_old_search_path text; v_ref_schema text; v_ref_table text; v_row record; v_schemaname text; v_sql text; v_step_id bigint; v_tablename text; BEGIN SELECT jobmon INTO v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN APPLYING FOREIGN KEYS: '||p_parent_table); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Checking if target child table exists'); END IF; SELECT schemaname, tablename INTO v_schemaname, v_tablename FROM pg_catalog.pg_tables WHERE schemaname||'.'||tablename = p_child_table; IF v_tablename IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'CRITICAL', 'Target child table ('||p_child_table||') does not exist.'); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION 'Target child table (%.%) does not exist.', v_schemaname, v_tablename; RETURN; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; FOR v_row IN SELECT pg_get_constraintdef(oid) AS constraint_def FROM pg_catalog.pg_constraint WHERE conrelid = p_parent_table::regclass AND contype = 'f' LOOP v_sql := format('ALTER TABLE %I.%I ADD %s' , v_schemaname , v_tablename , v_row.constraint_def); EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'FK applied'); END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN APPLYING FOREIGN KEYS: '||p_parent_table||''')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; /* * Function to manage pre-creation of the next partitions in a set. * Also manages dropping old partitions if the retention option is set. * If p_parent_table is passed, will only run run_maintenance() on that one table (no matter what the configuration table may have set for it) * Otherwise, will run on all tables in the config table with p_run_maintenance() set to true. * For large partition sets, running analyze can cause maintenance to take longer than expected. Can set p_analyze to false to avoid a forced analyze run. * Be aware that constraint exclusion may not work properly until an analyze on the partition set is run. */ CREATE OR REPLACE FUNCTION run_maintenance(p_parent_table text DEFAULT NULL, p_analyze boolean DEFAULT true, p_jobmon boolean DEFAULT true) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_adv_lock boolean; v_create_count int := 0; v_current_partition text; v_current_partition_id bigint; v_current_partition_timestamp timestamp; v_datetime_string text; v_drop_count int := 0; v_id_position int; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_created boolean; v_last_partition_id bigint; v_last_partition_timestamp timestamp; v_next_partition_id bigint; v_next_partition_timestamp timestamp; v_old_search_path text; v_premade_count int; v_quarter text; v_step_id bigint; v_step_overflow_id bigint; v_step_serial_id bigint; v_sub_id_max bigint; v_sub_id_min bigint; v_sub_parent text; v_sub_timestamp_max timestamp; v_sub_timestamp_min timestamp; v_row record; v_row_max_id record; v_row_sub record; v_tablename text; v_tables_list_sql text; v_time_position int; v_year text; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman maintenance already running.'; RETURN; END IF; 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' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN RUN MAINTENANCE'); v_step_id := add_step(v_job_id, 'Running maintenance loop'); END IF; v_tables_list_sql := 'SELECT parent_table , type , part_interval , control , premake , datetime_string , undo_in_progress FROM @extschema@.part_config'; IF p_parent_table IS NULL THEN v_tables_list_sql := v_tables_list_sql || ' WHERE use_run_maintenance = true'; ELSE v_tables_list_sql := v_tables_list_sql || format(' WHERE parent_table = %L', p_parent_table); END IF; FOR v_row IN EXECUTE v_tables_list_sql LOOP CONTINUE WHEN v_row.undo_in_progress; SELECT show_partitions INTO v_last_partition FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LIMIT 1; IF v_row.type = 'time-static' OR v_row.type = 'time-dynamic' OR v_row.type = 'time-custom' THEN IF v_row.type = 'time-static' OR v_row.type = 'time-dynamic' THEN CASE WHEN v_row.part_interval::interval = '15 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_row.part_interval::interval = '30 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_row.part_interval::interval = '1 hour' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_row.part_interval::interval = '1 day' THEN v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_row.part_interval::interval = '1 week' THEN v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_row.part_interval::interval = '1 month' THEN v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_row.part_interval::interval = '3 months' THEN v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_row.part_interval::interval = '1 year' THEN v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; ELSIF v_row.type = 'time-custom' THEN SELECT child_table INTO v_current_partition FROM @extschema@.custom_time_partitions WHERE parent_table = v_row.parent_table AND partition_range @> CURRENT_TIMESTAMP; IF v_current_partition IS NULL THEN RAISE EXCEPTION 'Current time partition missing from custom_time_partitions config table for table % and timestamp %', CURRENT_TIMESTAMP, v_row.parent_table; END IF; v_time_position := (length(v_current_partition) - position('p_' in reverse(v_current_partition))) + 2; v_current_partition_timestamp := to_timestamp(substring(v_current_partition from v_time_position), v_row.datetime_string); END IF; -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::timestamp, sub_max::timestamp INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'time'); -- No need to run maintenance if it's outside the bounds of the top parent. IF v_sub_timestamp_min IS NOT NULL THEN IF v_current_partition_timestamp < v_sub_timestamp_min OR v_current_partition_timestamp > v_sub_timestamp_max THEN CONTINUE; END IF; END IF; v_time_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; IF v_row.part_interval::interval <> '3 months' OR (v_row.part_interval::interval = '3 months' AND v_row.type = 'time-custom') THEN v_last_partition_timestamp := to_timestamp(substring(v_last_partition from v_time_position), v_row.datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(v_last_partition from v_time_position), 'q', 1); v_quarter := split_part(substring(v_last_partition from v_time_position), 'q', 2); CASE WHEN v_quarter = '1' THEN v_last_partition_timestamp := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_last_partition_timestamp := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_last_partition_timestamp := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_last_partition_timestamp := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; -- Check and see how many premade partitions there are. v_premade_count = round(EXTRACT('epoch' FROM age(v_last_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.part_interval::interval)); v_next_partition_timestamp := v_last_partition_timestamp; -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed. WHILE v_premade_count < v_row.premake LOOP BEGIN v_next_partition_timestamp := v_next_partition_timestamp + v_row.part_interval::interval; EXCEPTION WHEN datetime_field_overflow THEN v_premade_count := v_row.premake; -- do this so it can exit the premake check loop and continue in the outer for loop IF v_jobmon_schema IS NOT NULL THEN 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 skippd for parent table '||v_partition_time); END IF; RAISE WARNING 'Attempted partition time interval is outside PostgreSQL''s supported time range. Child partition creation skipped for parent table %', v_row.parent_table; CONTINUE; END; v_last_partition_created := @extschema@.create_partition_time(v_row.parent_table, ARRAY[v_next_partition_timestamp], p_analyze); v_create_count := v_create_count + 1; IF v_row.type = 'time-static' AND v_last_partition_created THEN PERFORM @extschema@.create_function_time(v_row.parent_table); END IF; -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(v_row.parent_table); -- Can be negative when subpartitioning and there are parent partitions in the past compared to current timestamp value. -- abs() prevents run_maintenence from running on those old parent tables v_premade_count = round(EXTRACT('epoch' FROM age(v_next_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.part_interval::interval)); END LOOP; ELSIF v_row.type = 'id-static' OR v_row.type ='id-dynamic' THEN -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT show_partitions FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP EXECUTE 'SELECT '||v_row.control||' - ('||v_row.control||' % '||v_row.part_interval::int||') FROM '||v_row_max_id.show_partitions||' WHERE '||v_row.control||' = (SELECT max('||v_row.control||') FROM '||v_row_max_id.show_partitions||')' INTO v_current_partition_id; IF v_current_partition_id IS NOT NULL THEN EXIT; END IF; END LOOP; -- Determine if this table is a child of a subpartition parent. If so, get limits to see if run_maintenance even needs to run for it. SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'id'); -- No need to run maintenance if it's outside the bounds of the top parent. IF v_sub_id_min IS NOT NULL THEN IF v_current_partition_id < v_sub_id_min OR v_current_partition_id > v_sub_id_max THEN CONTINUE; END IF; END IF; v_id_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; v_last_partition_id = substring(v_last_partition from v_id_position)::bigint; v_next_partition_id := v_last_partition_id + v_row.part_interval::bigint; WHILE ((v_next_partition_id - v_current_partition_id) / v_row.part_interval::bigint) <= v_row.premake LOOP v_last_partition_created := @extschema@.create_partition_id(v_row.parent_table, ARRAY[v_next_partition_id], p_analyze); IF v_last_partition_created THEN PERFORM @extschema@.create_function_id(v_row.parent_table); PERFORM @extschema@.apply_constraints(v_row.parent_table); END IF; v_next_partition_id := v_next_partition_id + v_row.part_interval::bigint; END LOOP; END IF; -- end main IF check for time or id END LOOP; -- end of creation loop -- Manage dropping old partitions if retention option is set FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND (type = 'time-static' OR type = 'time-dynamic' OR type = 'time-custom') LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); END IF; END IF; IF v_drop_count > 0 THEN PERFORM @extschema@.create_function_time(v_row.parent_table); END IF; END LOOP; FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND (type = 'id-static' OR type = 'id-dynamic') LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); END IF; END IF; IF v_drop_count > 0 THEN PERFORM @extschema@.create_function_id(v_row.parent_table); END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Partition maintenance finished. '||v_create_count||' partitions made. '||v_drop_count||' partitions dropped.'); IF v_step_overflow_id IS NOT NULL OR v_step_serial_id IS NOT NULL THEN PERFORM fail_job(v_job_id); ELSE PERFORM close_job(v_job_id); END IF; EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; EXCEPTION WHEN OTHERS THEN IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_job(''PARTMAN RUN MAINTENANCE'')' INTO v_job_id; EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before job logging started'')' INTO v_step_id; ELSIF v_step_id IS NULL THEN EXECUTE 'SELECT '||v_jobmon_schema||'.add_step('||v_job_id||', ''EXCEPTION before first step logged'')' INTO v_step_id; END IF; EXECUTE 'SELECT '||v_jobmon_schema||'.update_step('||v_step_id||', ''CRITICAL'', ''ERROR: '||coalesce(SQLERRM,'unknown')||''')'; EXECUTE 'SELECT '||v_jobmon_schema||'.fail_job('||v_job_id||')'; END IF; RAISE EXCEPTION '%', SQLERRM; END $$; -- Restore dropped object privileges DO $$ DECLARE v_row record; BEGIN FOR v_row IN SELECT statement FROM partman_preserve_privs_temp LOOP IF v_row.statement IS NOT NULL THEN EXECUTE v_row.statement; END IF; END LOOP; END $$; DROP TABLE IF EXISTS partman_preserve_privs_temp; pg_partman-4.6.0/updates/pg_partman--1.8.7--2.0.0.sql000066400000000000000000006016551412761706100214470ustar00rootroot00000000000000-- IMPORTANT NOTE: This version, and all future versions of pg_partman >= 2.0.0, are only compatible with PostgreSQL 9.4 and greater. -- I do not have any current plans for development on the v1.x.x branch anymore. Reported bugs will be fixed, but all development will only be on the 2.x.x from here on out. Bug fixes will also only be done while versions of PostgreSQL older than 9.4 are themselves officially supported. If a bug only exists in an unsupported version of PostgreSQL, it WILL NOT be fixed. And once PostgreSQL 9.3 goes EOL, the 1.8 series will be deprecated entirely. -- No new features, API changes, configuration options or major internal structure changes will be accepted for the 1.x.x series. These will be locked to the 1.8.x series. No future 1.x.x versions after 1.8.7 will be able to be installed directly. You will have to install 1.8.7 (tagged version releases are available on github) and then install the updates found in the "updates" folder of later versions to get to the latest 1.x.x version. I unfortunately don't have time to be maintaining two independent branches with the way extension versions work now in PostgreSQL. Even fixing bugs in the 1.8.x series will be troublesome since I then have to redo every 1.8.x -> 2.x.x update file every time to ensure all fixes are applied no matter the upgrade path. This will unfortunately break managing the 1.x.x series via PGXN. I'm sorry but I don't see any other way around this with the way extension versions are managed right now. I highly recommend updating your database to 9.4+ if you wish to easily maintain this extension going forward. There's tons of other great new features and performance improvements, so it's definitely worth the effort! -- A background worker process (BGW) has been added to pg_partman for general partition maintenance. -- A separate scheduler is no longer required in most cases if you compile pg_partman with the background worker option. -- New postgresql.conf configuration options are listed in the documentation. You must at minimum give the database names you want the BGW to run for. The rest have defaults if not set. Only a reload is required to change values once the database is started with the BGW running. -- The BGW can only be started at cluster start. This may change later, but it is done this way for initial simplicity and with future design ideas in mind. -- There are no longer distinct "static" and "dynamic" partitioning modes. The features of each mode have been combined into a single trigger format. -- All triggers now have the static INSERT statements for the premake window first followed by a fallback, dynamic option to insert to the child table if it exists. -- Whenever the next child partition is created for a partition set, its function will automatically be updated to the new version. If you'd like to update all partition sets immediately, see the code following these release notes in the update file. I cannot make this automatically part of the update because doing so would cause all the new trigger functions that are created to be part of the extension. After updating, all old "static" sets will still work as they did before, but will have an additional fallback case to handle inserts to child tables outside the current premake value if the child table exists. All old "dynamic" sets will now have the "static" conditions added before the dynamic portion and you should see a performance improvement for any data inserted within the premake window. As before, any data that is inserted that has no associated child table will go to the parent. -- The "time-custom" partition type still exists and has not changed for those that need an interval other than the predefined ones. It still uses a lookup table and sacrifices performance for flexibility. -- Eliminated nearly half of the existing pgtap tests due to this change (but added some new ones). -- See my blog post for the explanation of what "static" and "dynamic" meant. http://www.keithf4.com/postgresql-partition-manager -- If retention system is turned on, jobmon no longer logs entries if no retention work was actually done. Would previously just log that zero tables were dropped. If anything is dropped/uninherited, it will be logged as expected. -- Changed column "type" in part_config to "partition_type". "type" is a reserved word, but not currently strictly enforced (doesn't require double-quoting). This avoids any possible future issues. Also changed sub_type in part_config_sub to "sub_partition_type" for consistency -- Changed column "part_interval" in part_config & part_config_sub to "partition_interval" to be more consistent with above renamed column. -- Now uses new, more extensive GET STACKED DIAGNOSTIC feature added in 9.2 to provide more detailed errors when an exception is encountered. Previously when functions called other functions and a custom exception block was used, only the latest function called would report the error. Now a more full stack trace is available to see the original function that caused the error. -- Thanks to https://github.com/IMSoP for the extensive documentation formatting improvements. /*********************************************************** -- Begin code to migrate existing partition sets to new hybrid function -- You may need to adjust the setting of the search_path below to point the following commands to the schema that partman is installed to SELECT set_config('search_path','partman, public',false); CREATE TEMP TABLE partman_migrate_hybrid_temp (statement text); INSERT INTO partman_migrate_hybrid_temp SELECT format('SELECT create_function_id(%L);', parent_table) FROM part_config WHERE partition_type = 'id'; INSERT INTO partman_migrate_hybrid_temp SELECT format('SELECT create_function_time(%L);', parent_table) FROM part_config WHERE partition_type = 'time'; DO $$ DECLARE v_row record; BEGIN FOR v_row IN SELECT statement FROM partman_migrate_hybrid_temp LOOP IF v_row.statement IS NOT NULL THEN EXECUTE v_row.statement; END IF; END LOOP; END $$; DROP TABLE IF EXISTS partman_migrate_hybrid_temp; -- End code to migrate existing partition sets to new hybrid function ************************************************************/ ALTER TABLE @extschema@.part_config RENAME COLUMN "type" TO partition_type; ALTER TABLE @extschema@.part_config_sub RENAME COLUMN "sub_type" to "sub_partition_type"; ALTER TABLE @extschema@.part_config RENAME COLUMN part_interval TO partition_interval; ALTER TABLE @extschema@.part_config_sub RENAME COLUMN sub_part_interval TO sub_partition_interval; -- This table was only created if the version of postgres was >= 9.2 at the time of v1.6.0 installation. Account for -- when people are upgrading from 1.x to 2.x and if they may have been on 9.1 at the time CREATE TABLE IF NOT EXISTS @extschema@.custom_time_partitions ( parent_table text NOT NULL , child_table text NOT NULL , partition_range tstzrange NOT NULL , PRIMARY KEY (parent_table, child_table)); -- IF EXISTS clause to CREATE INDEX was not added until 9.5, so use DO block instead since we're 9.4 compatible DO $$ DECLARE v_exists smallint; BEGIN SELECT count(*) FROM pg_catalog.pg_class INTO v_exists WHERE relname = 'custom_time_partitions_partition_range_idx' AND relkind = 'i'; IF v_exists < 1 THEN CREATE INDEX custom_time_partitions_partition_range_idx ON custom_time_partitions USING gist (partition_range); END IF; END $$; SELECT pg_catalog.pg_extension_config_dump('custom_time_partitions', ''); CREATE INDEX IF NOT EXISTS custom_time_partitions_partition_range_idx ON @extschema@.custom_time_partitions USING gist (partition_range); SELECT pg_catalog.pg_extension_config_dump('custom_time_partitions', ''); --NOTE This function is in the table sql file /* * Ensure that sub-partitioned tables that are themselves sub-partitions have the same configuration options set when they are part of the same inheritance tree */ CREATE OR REPLACE FUNCTION @extschema@.check_subpart_sameconfig(text) RETURNS boolean LANGUAGE sql STABLE AS $$ WITH child_tables AS ( SELECT n.nspname||'.'||c.relname AS tablename FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent::regclass = $1::regclass ) SELECT CASE WHEN count(*) <= 1 THEN true WHEN count(*) > 1 THEN false END FROM ( 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_use_run_maintenance , sub_jobmon FROM @extschema@.part_config_sub a JOIN child_tables b on a.sub_parent = b.tablename) x; $$; /* * Check function for config table partition types */ CREATE OR REPLACE FUNCTION @extschema@.check_partition_type (p_type text) RETURNS boolean LANGUAGE plpgsql IMMUTABLE SECURITY DEFINER AS $$ DECLARE v_result boolean; BEGIN SELECT p_type IN ('time', 'time-custom', 'id') INTO v_result; RETURN v_result; END $$; UPDATE @extschema@.part_config SET partition_type = 'time' WHERE partition_type = 'time-static' OR partition_type = 'time-dynamic'; UPDATE @extschema@.part_config_sub SET sub_partition_type = 'time' WHERE sub_partition_type = 'time-static' OR sub_partition_type = 'time-dynamic'; UPDATE @extschema@.part_config_sub SET sub_partition_type = 'id' WHERE sub_partition_type = 'id-static' OR sub_partition_type = 'id-dynamic'; /* * Function to turn a table into the parent of a partition set */ CREATE OR REPLACE FUNCTION 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_use_run_maintenance boolean DEFAULT NULL , p_start_partition text DEFAULT NULL , p_inherit_fk boolean DEFAULT true , p_jobmon boolean DEFAULT true , p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_base_timestamp timestamp; v_count int := 1; v_datetime_string text; v_higher_parent text := p_parent_table; v_id_interval bigint; v_id_position int; v_job_id bigint; v_jobmon_schema text; v_last_partition_created boolean; v_max bigint; v_notnull boolean; v_old_search_path text; v_parent_partition_id bigint; v_parent_partition_timestamp timestamp; v_partition_time timestamp; v_partition_time_array timestamp[]; v_partition_id_array bigint[]; v_row record; v_run_maint boolean; v_sql text; v_start_time timestamp; v_starting_partition_id bigint; v_step_id bigint; v_step_overflow_id bigint; v_sub_parent text; v_success boolean := false; v_tablename text; v_time_interval interval; v_time_position int; v_top_datetime_string text; v_top_parent text := p_parent_table; BEGIN IF position('.' in p_parent_table) = 0 THEN RAISE EXCEPTION 'Parent table must be schema qualified'; END IF; SELECT tablename INTO v_tablename FROM pg_tables WHERE schemaname || '.' || tablename = p_parent_table; IF v_tablename IS NULL THEN RAISE EXCEPTION 'Please create given parent table first: %', p_parent_table; END IF; SELECT attnotnull INTO v_notnull FROM pg_attribute WHERE attrelid = p_parent_table::regclass AND attname = p_control; IF v_notnull = false OR v_notnull IS NULL THEN RAISE EXCEPTION 'Control column (%) for parent table (%) must be NOT NULL', p_control, p_parent_table; END IF; IF NOT @extschema@.check_partition_type(p_type) THEN RAISE EXCEPTION '% is not a valid partitioning type', p_type; END IF; EXECUTE 'LOCK TABLE '||p_parent_table||' IN ACCESS EXCLUSIVE MODE'; IF p_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF p_use_run_maintenance IS NOT NULL THEN IF p_use_run_maintenance IS FALSE AND (p_type = 'time' OR p_type = 'time-custom') THEN RAISE EXCEPTION 'p_run_maintenance cannot be set to false for time based partitioning'; END IF; v_run_maint := p_use_run_maintenance; ELSIF p_type = 'time' OR p_type = 'time-custom' THEN v_run_maint := TRUE; ELSIF p_type = 'id' THEN v_run_maint := FALSE; ELSE RAISE EXCEPTION 'use_run_maintenance value cannot be set NULL'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN SETUP PARENT: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating initial partitions on new parent table: '||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_inherits h where h.inhrelid::regclass = p_parent_table::regclass ), sibling_children as ( select i.inhrelid::regclass::text as tablename from pg_inherits i join parent_table p on i.inhparent = p.parent_oid ) 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_use_run_maintenance , sub_jobmon 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_use_run_maintenance , sub_jobmon) 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_use_run_maintenance , v_row.sub_jobmon); END LOOP; IF p_type = 'time' OR p_type = 'time-custom' 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 <> 'time-custom' THEN RAISE EXCEPTION 'Must use a predefined time interval if not using type "time-custom". See documentation.'; 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::timestamp, 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))::timestamp; 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 , control , premake , constraint_cols , datetime_string , use_run_maintenance , inherit_fk , jobmon) VALUES ( p_parent_table , p_type , v_time_interval , p_control , p_premake , p_constraint_cols , v_datetime_string , v_run_maint , p_inherit_fk , p_jobmon); 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 n.nspname||'.'||c.relname = p_parent_table ) SELECT n.nspname||'.'||c.relname, p.datetime_string INTO v_top_parent, v_top_datetime_string 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 IS NOT NULL THEN -- If so create the lowest possible partition that is within the boundary of the parent v_time_position := (length(p_parent_table) - position('p_' in reverse(p_parent_table))) + 2; v_parent_partition_timestamp := to_timestamp(substring(p_parent_table from v_time_position), v_top_datetime_string); 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 -- Currently unknown edge case if code gets here RAISE EXCEPTION 'No child tables created. Unexpected edge case encountered. Please report this error to author with conditions that led to it.'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Time partitions premade: '||p_premake); END IF; END IF; IF p_type = 'id' THEN v_id_interval := p_interval::bigint; IF v_id_interval < 10 THEN RAISE EXCEPTION 'Interval for serial 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 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||'.'||c.relname = v_higher_parent ) SELECT n.nspname||'.'||c.relname INTO v_higher_parent 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 WHERE p.partition_type = 'id'; IF v_higher_parent IS NOT NULL THEN -- v_top_parent initially set in DECLARE v_top_parent := v_higher_parent; 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 v_sql := 'SELECT COALESCE('||quote_nullable(p_start_partition::bigint)||', max('||p_control||')::bigint, 0) FROM '||v_top_parent||' LIMIT 1'; EXECUTE v_sql INTO v_max; 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 , use_run_maintenance , inherit_fk , jobmon) VALUES ( p_parent_table , p_type , v_id_interval , p_control , p_premake , p_constraint_cols , v_run_maint , p_inherit_fk , p_jobmon); 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 n.nspname||'.'||c.relname = p_parent_table ) SELECT n.nspname||'.'||c.relname INTO v_top_parent 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 WHERE p.partition_type = 'id'; IF v_top_parent IS NOT NULL THEN -- Create the lowest possible partition that is within the boundary of the parent v_id_position := (length(p_parent_table) - position('p_' in reverse(p_parent_table))) + 2; v_parent_partition_id = substring(p_parent_table from v_id_position)::bigint; 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 EXCEPTION 'No child tables created. Unexpected edge case encountered. Please report this error to author with conditions that led to it.'; END IF; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating partition function'); END IF; IF p_type = 'time' OR p_type = 'time-custom' THEN PERFORM @extschema@.create_function_time(p_parent_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Time function created'); END IF; ELSIF p_type = 'id' THEN PERFORM @extschema@.create_function_id(p_parent_table); 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); 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; EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; 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 $$; /* * Create the trigger function for the parent table of an id-based partition set */ CREATE OR REPLACE FUNCTION create_function_id(p_parent_table text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_control text; v_count int; v_current_partition_name text; v_current_partition_id bigint; v_datetime_string text; v_final_partition_id bigint; v_function_name text; v_higher_parent text := p_parent_table; v_id_position int; v_job_id bigint; v_jobmon text; v_jobmon_schema text; v_last_partition text; v_max bigint; v_next_partition_id bigint; v_next_partition_name text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_premake int; v_prev_partition_id bigint; v_prev_partition_name text; v_row_max_id record; v_run_maint boolean; v_step_id bigint; v_top_parent text := p_parent_table; v_trig_func text; BEGIN SELECT partition_interval::bigint , control , premake , use_run_maintenance , jobmon INTO v_partition_interval , v_control , v_premake , v_run_maint , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT show_partitions INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE FUNCTION: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating partition function for table '||p_parent_table); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_function_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, '_part_trig_func', FALSE); -- Get the highest level top parent if multi-level partitioned in order to get proper max() value below WHILE v_higher_parent 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||'.'||c.relname = v_higher_parent ) SELECT n.nspname||'.'||c.relname INTO v_higher_parent 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 WHERE p.partition_type = 'id'; IF v_higher_parent IS NOT NULL THEN -- initially set in DECLARE v_top_parent := v_higher_parent; END IF; END LOOP; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT show_partitions FROM @extschema@.show_partitions(v_top_parent, 'DESC') LOOP EXECUTE 'SELECT max('||v_control||') FROM '||v_row_max_id.show_partitions INTO v_max; IF v_max IS NOT NULL THEN EXIT; END IF; END LOOP; IF v_max IS NULL THEN v_max := 0; END IF; v_current_partition_id = v_max - (v_max % v_partition_interval); v_next_partition_id := v_current_partition_id + v_partition_interval; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_current_partition_id::text, TRUE); v_trig_func := 'CREATE OR REPLACE FUNCTION '||v_function_name||'() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_current_partition_id bigint; v_current_partition_name text; v_id_position int; v_last_partition text := '||quote_literal(v_last_partition)||'; v_next_partition_id bigint; v_next_partition_name text; v_partition_created boolean; BEGIN IF TG_OP = ''INSERT'' THEN IF NEW.'||v_control||' >= '||v_current_partition_id||' AND NEW.'||v_control||' < '||v_next_partition_id|| ' THEN '; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_current_partition_name; IF v_count > 0 THEN v_trig_func := v_trig_func || ' INSERT INTO '||v_current_partition_name||' VALUES (NEW.*); '; ELSE v_trig_func := v_trig_func || ' -- Child table for current values does not exist in this partition set, so write to parent RETURN NEW;'; END IF; FOR i IN 1..v_premake LOOP v_prev_partition_id := v_current_partition_id - (v_partition_interval * i); v_next_partition_id := v_current_partition_id + (v_partition_interval * i); v_final_partition_id := v_next_partition_id + v_partition_interval; v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_prev_partition_id::text, TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_next_partition_id::text, TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles edge case of changing premake immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_prev_partition_name; IF v_count > 0 THEN -- Only handle previous partitions if they're starting above zero IF v_prev_partition_id >= 0 THEN v_trig_func := v_trig_func ||' ELSIF NEW.'||v_control||' >= '||v_prev_partition_id||' AND NEW.'||v_control||' < '||v_prev_partition_id + v_partition_interval|| ' THEN INSERT INTO '||v_prev_partition_name||' VALUES (NEW.*); '; END IF; END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_next_partition_name; IF v_count > 0 THEN v_trig_func := v_trig_func ||' ELSIF NEW.'||v_control||' >= '||v_next_partition_id||' AND NEW.'||v_control||' < '||v_final_partition_id|| ' THEN INSERT INTO '||v_next_partition_name||' VALUES (NEW.*);'; END IF; END LOOP; v_trig_func := v_trig_func ||' ELSE v_current_partition_id := NEW.'||v_control||' - (NEW.'||v_control||' % '||v_partition_interval||'); v_current_partition_name := @extschema@.check_name_length('''||v_parent_tablename||''', '''||v_parent_schema||''', v_current_partition_id::text, TRUE); SELECT count(*) INTO v_count FROM pg_tables WHERE schemaname ||''.''|| tablename = v_current_partition_name; IF v_count > 0 THEN EXECUTE ''INSERT INTO ''||v_current_partition_name||'' VALUES($1.*)'' USING NEW; ELSE RETURN NEW; END IF; END IF;'; IF v_run_maint IS FALSE THEN v_trig_func := v_trig_func ||' v_current_partition_id := NEW.'||v_control||' - (NEW.'||v_control||' % '||v_partition_interval||'); IF (NEW.'||v_control||' % '||v_partition_interval||') > ('||v_partition_interval||' / 2) THEN v_id_position := (length(v_last_partition) - position(''p_'' in reverse(v_last_partition))) + 2; v_next_partition_id := (substring(v_last_partition from v_id_position)::bigint) + '||v_partition_interval||'; WHILE ((v_next_partition_id - v_current_partition_id) / '||v_partition_interval||') <= '||v_premake||' LOOP v_partition_created := @extschema@.create_partition_id('||quote_literal(p_parent_table)||', ARRAY[v_next_partition_id]); IF v_partition_created THEN PERFORM @extschema@.create_function_id('||quote_literal(p_parent_table)||'); PERFORM @extschema@.apply_constraints('||quote_literal(p_parent_table)||'); END IF; v_next_partition_id := v_next_partition_id + '||v_partition_interval||'; END LOOP; END IF;'; END IF; v_trig_func := v_trig_func ||' END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for current id interval: '||v_current_partition_id||' to '||v_final_partition_id-1); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; 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 FUNCTION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''Partition function maintenance for table %s 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 $$; /* * Create the trigger function for the parent table of a time-based partition set */ CREATE OR REPLACE FUNCTION create_function_time(p_parent_table text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_control text; v_count int; v_current_partition_name text; v_current_partition_timestamp timestamptz; v_datetime_string text; v_final_partition_timestamp timestamptz; v_function_name text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_new_length int; v_next_partition_name text; v_next_partition_timestamp timestamptz; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_premake int; v_prev_partition_name text; v_prev_partition_timestamp timestamptz; v_step_id bigint; v_trig_func text; v_type text; BEGIN SELECT partition_type , partition_interval::interval , control , premake , datetime_string , jobmon INTO v_type , v_partition_interval , v_control , v_premake , v_datetime_string , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE FUNCTION: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating partition function for table '||p_parent_table); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_function_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, '_part_trig_func', FALSE); IF v_type = 'time' THEN v_trig_func := 'CREATE OR REPLACE FUNCTION '||v_function_name||'() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_partition_name text; v_partition_timestamp timestamptz; BEGIN IF TG_OP = ''INSERT'' THEN '; CASE WHEN v_partition_interval = '15 mins' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''hour'', NEW.'||v_control||') + ''15min''::interval * floor(date_part(''minute'', NEW.'||v_control||') / 15.0);'; v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''hour'', NEW.'||v_control||') + ''30min''::interval * floor(date_part(''minute'', NEW.'||v_control||') / 30.0);'; v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''hour'', NEW.'||v_control||');'; v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 day' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''day'', NEW.'||v_control||');'; v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 week' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''week'', NEW.'||v_control||');'; v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 month' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''month'', NEW.'||v_control||');'; v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_partition_interval = '3 months' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''quarter'', NEW.'||v_control||');'; v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 year' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''year'', NEW.'||v_control||');'; v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, to_char(v_current_partition_timestamp, v_datetime_string), TRUE); v_next_partition_timestamp := v_current_partition_timestamp + v_partition_interval::interval; v_trig_func := v_trig_func ||' IF NEW.'||v_control||' >= '||quote_literal(v_current_partition_timestamp)||' AND NEW.'||v_control||' < '||quote_literal(v_next_partition_timestamp)|| ' THEN '; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_current_partition_name; IF v_count > 0 THEN v_trig_func := v_trig_func || ' INSERT INTO '||v_current_partition_name||' VALUES (NEW.*); '; ELSE v_trig_func := v_trig_func || ' -- Child table for current values does not exist in this partition set, so write to parent RETURN NEW;'; END IF; FOR i IN 1..v_premake LOOP v_prev_partition_timestamp := v_current_partition_timestamp - (v_partition_interval::interval * i); v_next_partition_timestamp := v_current_partition_timestamp + (v_partition_interval::interval * i); v_final_partition_timestamp := v_next_partition_timestamp + (v_partition_interval::interval); v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, to_char(v_prev_partition_timestamp, v_datetime_string), TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, to_char(v_next_partition_timestamp, v_datetime_string), TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles edge case of changing premake immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_prev_partition_name; IF v_count > 0 THEN v_trig_func := v_trig_func ||' ELSIF NEW.'||v_control||' >= '||quote_literal(v_prev_partition_timestamp)||' AND NEW.'||v_control||' < '|| quote_literal(v_prev_partition_timestamp + v_partition_interval::interval)|| ' THEN INSERT INTO '||v_prev_partition_name||' VALUES (NEW.*);'; END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_next_partition_name; IF v_count > 0 THEN v_trig_func := v_trig_func ||' ELSIF NEW.'||v_control||' >= '||quote_literal(v_next_partition_timestamp)||' AND NEW.'||v_control||' < '|| quote_literal(v_final_partition_timestamp)|| ' THEN INSERT INTO '||v_next_partition_name||' VALUES (NEW.*);'; END IF; END LOOP; v_trig_func := v_trig_func||' ELSE v_partition_name := @extschema@.check_name_length('''||v_parent_tablename||''', '''||v_parent_schema||''', to_char(v_partition_timestamp, '||quote_literal(v_datetime_string)||'), TRUE); SELECT count(*) INTO v_count FROM pg_tables WHERE schemaname ||''.''|| tablename = v_partition_name; IF v_count > 0 THEN EXECUTE ''INSERT INTO ''||v_partition_name||'' VALUES($1.*)'' USING NEW; ELSE RETURN NEW; END IF; END IF;'; v_trig_func := v_trig_func ||' END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for current time interval: '|| v_current_partition_timestamp||' to '||(v_final_partition_timestamp-'1sec'::interval)); END IF; ELSIF v_type = 'time-custom' THEN v_trig_func := 'CREATE OR REPLACE FUNCTION '||v_function_name||'() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_child_table text; v_count int; BEGIN SELECT child_table INTO v_child_table FROM @extschema@.custom_time_partitions WHERE partition_range @> NEW.'||v_control||' AND parent_table = '||quote_literal(p_parent_table)||'; SELECT count(*) INTO v_count FROM pg_tables WHERE schemaname ||''.''|| tablename = v_child_table; IF v_count > 0 THEN EXECUTE ''INSERT INTO ''||v_child_table||'' VALUES ($1.*)'' USING NEW; ELSE RETURN NEW; END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for custom time table: '||p_parent_table); END IF; ELSE RAISE EXCEPTION 'ERROR: Invalid time partitioning type given: %', v_type; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; 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 FUNCTION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''Partition function maintenance for table %s 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 $$; /* * Function to create id partitions */ CREATE OR REPLACE FUNCTION create_partition_id(p_parent_table text, p_partition_ids bigint[], p_analyze boolean DEFAULT true) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_grantees text[]; v_hasoids boolean; v_id bigint; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_parent_tablespace text; v_partition_interval bigint; v_partition_created boolean := false; v_partition_name text; v_revoke text[]; v_row record; v_sql text; v_step_id bigint; v_sub_id_max bigint; v_sub_id_min bigint; v_tablename text; v_unlogged char; BEGIN SELECT control , partition_interval , inherit_fk , jobmon INTO v_control , v_partition_interval , v_inherit_fk , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'id'); SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); END IF; FOREACH v_id IN ARRAY p_partition_ids LOOP -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_id_min IS NOT NULL THEN IF v_id < v_sub_id_min OR v_id > v_sub_id_max THEN CONTINUE; END IF; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_id::text, TRUE); -- If child table already exists, skip creation SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_tablename IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_id||' to '||(v_id + v_partition_interval)-1); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class WHERE oid::regclass = p_parent_table::regclass; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; v_sql := v_sql || ' TABLE '||v_partition_name||' (LIKE '||p_parent_table||' INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)'; SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class WHERE oid::regclass = p_parent_table::regclass; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_parent_tablespace IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_partition_name||' SET TABLESPACE '||v_parent_tablespace; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' ADD CONSTRAINT '||v_tablename||'_partition_check CHECK ('||v_control||'>='||quote_literal(v_id)||' AND '||v_control||'<'||quote_literal(v_id + v_partition_interval)||')'; EXECUTE 'ALTER TABLE '||v_partition_name||' INHERIT '||p_parent_table; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP EXECUTE 'GRANT '||array_to_string(v_parent_grant.types, ',')||' ON '||v_partition_name||' TO '||v_parent_grant.grantee; SELECT array_agg(r) INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE '||array_to_string(v_revoke, ',')||' ON '||v_partition_name||' FROM '||v_parent_grant.grantee||' CASCADE'; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN SELECT array_agg(r) INTO v_revoke FROM ( SELECT DISTINCT grantee::text AS r FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_partition_name EXCEPT SELECT unnest(v_grantees)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE ALL ON '||v_partition_name||' FROM '||array_to_string(v_revoke, ','); END IF; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' OWNER TO '||v_parent_owner; IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(quote_ident(v_parent_schema)||'.'||quote_ident(v_parent_tablename), v_partition_name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_control , sub_partition_type , 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_use_run_maintenance , sub_jobmon FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Subpartitioning '||v_partition_name); END IF; 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_use_run_maintenance := %L , p_inherit_fk := %L , p_jobmon := %L )' , v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_use_run_maintenance , v_row.sub_inherit_fk , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index WHERE parent_table = v_partition_name; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; -- end sub partitioning LOOP v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Analyzing partition set: '||p_parent_table); END IF; EXECUTE 'ANALYZE '||p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, 'No partitions created for partition set: '||p_parent_table); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; PERFORM close_job(v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to create a child table in a time-based partition set */ CREATE OR REPLACE FUNCTION create_partition_time (p_parent_table text, p_partition_times timestamp[], p_analyze boolean DEFAULT true) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_grantees text[]; v_hasoids boolean; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_partition_created boolean := false; v_partition_name text; v_partition_suffix text; v_parent_tablespace text; v_partition_interval interval; v_partition_timestamp_end timestamp; v_partition_timestamp_start timestamp; v_quarter text; v_revoke text[]; v_row record; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_sub_timestamp_max timestamp; v_sub_timestamp_min timestamp; v_tablename text; v_trunc_value text; v_time timestamp; v_type text; v_unlogged char; v_year text; BEGIN SELECT partition_type , control , partition_interval , inherit_fk , jobmon , datetime_string INTO v_type , v_control , v_partition_interval , v_inherit_fk , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'time' OR partition_type = 'time-custom'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::timestamp, sub_max::timestamp INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'time'); SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); END IF; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_timestamp_start := v_time; BEGIN v_partition_timestamp_end := v_time + v_partition_interval; 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_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_time||' skipped'); CONTINUE; END; -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_timestamp_min IS NOT NULL THEN IF v_time < v_sub_timestamp_min OR v_time > v_sub_timestamp_max THEN CONTINUE; END IF; END IF; -- This suffix generation code is in partition_data_time() as well v_partition_suffix := to_char(v_time, 'YYYY'); IF v_partition_interval < '1 year' AND v_partition_interval <> '1 week' THEN v_partition_suffix := v_partition_suffix ||'_'|| to_char(v_time, 'MM'); IF v_partition_interval < '1 month' AND v_partition_interval <> '1 week' THEN v_partition_suffix := v_partition_suffix ||'_'|| to_char(v_time, 'DD'); IF v_partition_interval < '1 day' THEN v_partition_suffix := v_partition_suffix || '_' || to_char(v_time, 'HH24MI'); IF v_partition_interval < '1 minute' THEN v_partition_suffix := v_partition_suffix || to_char(v_time, 'SS'); END IF; -- end < minute IF END IF; -- end < day IF END IF; -- end < month IF END IF; -- end < year IF IF v_partition_interval = '1 week' THEN v_partition_suffix := to_char(v_time, 'IYYY') || 'w' || to_char(v_time, 'IW'); END IF; -- "Q" is ignored in to_timestamp, so handle special case IF v_partition_interval = '3 months' AND v_type = 'time' THEN v_year := to_char(v_time, 'YYYY'); v_quarter := to_char(v_time, 'Q'); v_partition_suffix := v_year || 'q' || v_quarter; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_partition_suffix, TRUE); SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_tablename IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_partition_timestamp_start||' to '||(v_partition_timestamp_end-'1sec'::interval)); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class WHERE oid::regclass = p_parent_table::regclass; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; v_sql := v_sql || ' TABLE '||v_partition_name||' (LIKE '||p_parent_table||' INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)'; SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class WHERE oid::regclass = p_parent_table::regclass; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_parent_tablespace IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_partition_name||' SET TABLESPACE '||v_parent_tablespace; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' ADD CONSTRAINT '||v_tablename||'_partition_check CHECK ('||v_control||'>='||quote_literal(v_partition_timestamp_start)||' AND '||v_control||'<'||quote_literal(v_partition_timestamp_end)||')'; EXECUTE 'ALTER TABLE '||v_partition_name||' INHERIT '||p_parent_table; -- If custom time, set extra config options. IF v_type = 'time-custom' THEN INSERT INTO @extschema@.custom_time_partitions (parent_table, child_table, partition_range) VALUES ( p_parent_table, v_partition_name, tstzrange(v_partition_timestamp_start, v_partition_timestamp_end, '[)') ); END IF; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP EXECUTE 'GRANT '||array_to_string(v_parent_grant.types, ',')||' ON '||v_partition_name||' TO '||v_parent_grant.grantee; SELECT array_agg(r) INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE '||array_to_string(v_revoke, ',')||' ON '||v_partition_name||' FROM '||v_parent_grant.grantee||' CASCADE'; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN SELECT array_agg(r) INTO v_revoke FROM ( SELECT DISTINCT grantee::text AS r FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_partition_name EXCEPT SELECT unnest(v_grantees)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE ALL ON '||v_partition_name||' FROM '||array_to_string(v_revoke, ','); END IF; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' OWNER TO '||v_parent_owner; IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(quote_ident(v_parent_schema)||'.'||quote_ident(v_parent_tablename), v_partition_name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_control , sub_partition_type , 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_use_run_maintenance , sub_jobmon FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Subpartitioning '||v_partition_name); END IF; 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_use_run_maintenance := %L , p_inherit_fk := %L , p_jobmon := %L )' , v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_use_run_maintenance , v_row.sub_inherit_fk , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index WHERE parent_table = v_partition_name; END LOOP; -- end sub partitioning LOOP v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Analyzing partition set: '||p_parent_table); END IF; EXECUTE 'ANALYZE '||p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, 'No partitions created for partition set: '||p_parent_table); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; 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; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Apply constraints managed by partman extension */ CREATE OR REPLACE FUNCTION apply_constraints(p_parent_table text, p_child_table text DEFAULT NULL, p_analyze boolean DEFAULT FALSE, p_debug boolean DEFAULT FALSE) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_child_table text; v_child_tablename text; v_col text; v_constraint_cols text[]; v_constraint_col_type text; v_constraint_name text; v_datetime_string text; v_existing_constraint_name text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_id int; v_last_partition_timestamp timestamp; v_constraint_values record; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval text; v_partition_suffix text; v_premake int; v_sql text; v_step_id bigint; v_suffix_position int; v_type text; BEGIN SELECT partition_type , partition_interval , premake , datetime_string , constraint_cols , jobmon INTO v_type , v_partition_interval , v_premake , v_datetime_string , v_constraint_cols , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_constraint_cols IS NULL THEN IF p_debug THEN RAISE NOTICE 'Given parent table (%) not set up for constraint management (constraint_cols is NULL)', p_parent_table; END IF; -- Returns silently to allow this function to be simply called by maintenance processes without having to check if config options are set. RETURN; END IF; SELECT show_partitions INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE CONSTRAINT: '||p_parent_table); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; -- If p_child_table is null, figure out the partition that is the one right before the premake value backwards. IF p_child_table IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Automatically determining most recent child on which to apply constraints'); END IF; v_suffix_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; IF v_type IN ('time', 'time-custom') THEN v_last_partition_timestamp := to_timestamp(substring(v_last_partition from v_suffix_position), v_datetime_string); v_partition_suffix := to_char(v_last_partition_timestamp - (v_partition_interval::interval * ((v_premake * 2)+1) ), v_datetime_string); ELSIF v_type = 'id' THEN v_last_partition_id := substring(v_last_partition from v_suffix_position)::int; v_partition_suffix := (v_last_partition_id - (v_partition_interval::int * ((v_premake * 2)+1) ))::text; END IF; v_child_table := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_partition_suffix, TRUE); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Target child table: '||v_child_table); END IF; ELSE v_child_table := p_child_table; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Checking if target child table exists'); END IF; SELECT tablename INTO v_child_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_child_table; IF v_child_tablename IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', 'Target child table ('||v_child_table||') does not exist. Skipping constraint creation.'); PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; IF p_debug THEN RAISE NOTICE 'Target child table (%) does not exist. Skipping constraint creation.', v_child_table; END IF; RETURN; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; FOREACH v_col IN ARRAY v_constraint_cols LOOP SELECT c.conname INTO v_existing_constraint_name FROM pg_catalog.pg_constraint c JOIN pg_catalog.pg_attribute a ON c.conrelid = a.attrelid WHERE conrelid = v_child_table::regclass AND c.conname LIKE 'partmanconstr_%' AND c.contype = 'c' AND a.attname = v_col AND ARRAY[a.attnum] <@ c.conkey AND a.attisdropped = false; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying new constraint on column: '||v_col); END IF; IF v_existing_constraint_name IS NOT NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', 'Partman managed constraint already exists on this table ('||v_child_table||') and column ('||v_col||'). Skipping creation.'); END IF; RAISE WARNING 'Partman managed constraint already exists on this table (%) and column (%). Skipping creation.', v_child_table, v_col ; CONTINUE; END IF; -- Ensure column name gets put on end of constraint name to help avoid naming conflicts v_constraint_name := @extschema@.check_name_length('partmanconstr_'||v_child_tablename, p_suffix := '_'||v_col); EXECUTE 'SELECT min('||v_col||')::text AS min, max('||v_col||')::text AS max FROM '||v_child_table INTO v_constraint_values; IF v_constraint_values IS NOT NULL THEN v_sql := concat('ALTER TABLE ', v_child_table, ' ADD CONSTRAINT ', v_constraint_name , ' CHECK (', v_col, ' >= ', quote_literal(v_constraint_values.min), ' AND ' , v_col, ' <= ', quote_literal(v_constraint_values.max), ')' ); IF p_debug THEN RAISE NOTICE 'Constraint creation query: %', v_sql; END IF; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'New constraint created: '||v_sql); END IF; ELSE IF p_debug THEN RAISE NOTICE 'Given column (%) contains all NULLs. No constraint created', v_col; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', 'Given column ('||v_col||') contains all NULLs. No constraint created'); END IF; END IF; END LOOP; IF p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Running analyze on partition set: '||p_parent_table); END IF; IF p_debug THEN RAISE NOTICE 'Running analyze on partition set: %', p_parent_table; END IF; EXECUTE 'ANALYZE '||p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; 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 CONSTRAINT: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Apply foreign keys that exist on the given parent to the given child table */ CREATE OR REPLACE FUNCTION apply_foreign_keys(p_parent_table text, p_child_table text DEFAULT NULL, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_job_id bigint; v_jobmon text; v_jobmon_schema text; v_old_search_path text; v_ref_schema text; v_ref_table text; v_row record; v_schemaname text; v_sql text; v_step_id bigint; v_tablename text; BEGIN SELECT jobmon INTO v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN APPLYING FOREIGN KEYS: '||p_parent_table); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Checking if target child table exists'); END IF; SELECT schemaname, tablename INTO v_schemaname, v_tablename FROM pg_catalog.pg_tables WHERE schemaname||'.'||tablename = p_child_table; IF v_tablename IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'CRITICAL', 'Target child table ('||v_child_table||') does not exist.'); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION 'Target child table (%.%) does not exist.', v_schemaname, v_tablename; RETURN; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; FOR v_row IN SELECT n.nspname||'.'||cl.relname AS ref_table , '"'||string_agg(att.attname, '","')||'"' AS ref_column , '"'||string_agg(att2.attname, '","')||'"' AS child_column , keys.condeferred , keys.condeferrable , keys.confupdtype , keys.confdeltype , keys.confmatchtype FROM ( SELECT unnest(con.conkey) as ref , unnest(con.confkey) as child , con.confrelid , con.conrelid , con.condeferred , con.condeferrable , con.confupdtype , con.confdeltype , con.confmatchtype FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN pg_catalog.pg_constraint con ON c.oid = con.conrelid WHERE n.nspname ||'.'|| c.relname = p_parent_table AND con.contype = 'f' ORDER BY con.conkey ) keys JOIN pg_catalog.pg_class cl ON cl.oid = keys.confrelid JOIN pg_catalog.pg_namespace n ON cl.relnamespace = n.oid JOIN pg_catalog.pg_attribute att ON att.attrelid = keys.confrelid AND att.attnum = keys.child JOIN pg_catalog.pg_attribute att2 ON att2.attrelid = keys.conrelid AND att2.attnum = keys.ref GROUP BY n.nspname, cl.relname, keys.condeferred, keys.condeferrable, keys.confupdtype, keys.confdeltype, keys.confmatchtype LOOP SELECT schemaname, tablename INTO v_ref_schema, v_ref_table FROM pg_tables WHERE schemaname||'.'||tablename = v_row.ref_table; v_sql := format('ALTER TABLE %I.%I ADD FOREIGN KEY (%s) REFERENCES %I.%I (%s)', v_schemaname, v_tablename, v_row.child_column, v_ref_schema, v_ref_table, v_row.ref_column); CASE WHEN v_row.confmatchtype = 'f' THEN v_sql := v_sql || ' MATCH FULL '; WHEN v_row.confmatchtype = 's' THEN v_sql := v_sql || ' MATCH SIMPLE '; WHEN v_row.confmatchtype = 'p' THEN v_sql := v_sql || ' MATCH PARTIAL '; END CASE; CASE WHEN v_row.confupdtype = 'a' THEN v_sql := v_sql || ' ON UPDATE NO ACTION '; WHEN v_row.confupdtype = 'r' THEN v_sql := v_sql || ' ON UPDATE RESTRICT '; WHEN v_row.confupdtype = 'c' THEN v_sql := v_sql || ' ON UPDATE CASCADE '; WHEN v_row.confupdtype = 'n' THEN v_sql := v_sql || ' ON UPDATE SET NULL '; WHEN v_row.confupdtype = 'd' THEN v_sql := v_sql || ' ON UPDATE SET DEFAULT '; END CASE; CASE WHEN v_row.confdeltype = 'a' THEN v_sql := v_sql || ' ON DELETE NO ACTION '; WHEN v_row.confdeltype = 'r' THEN v_sql := v_sql || ' ON DELETE RESTRICT '; WHEN v_row.confdeltype = 'c' THEN v_sql := v_sql || ' ON DELETE CASCADE '; WHEN v_row.confdeltype = 'n' THEN v_sql := v_sql || ' ON DELETE SET NULL '; WHEN v_row.confdeltype = 'd' THEN v_sql := v_sql || ' ON DELETE SET DEFAULT '; END CASE; CASE WHEN v_row.condeferrable = true AND v_row.condeferred = true THEN v_sql := v_sql || ' DEFERRABLE INITIALLY DEFERRED '; WHEN v_row.condeferrable = false AND v_row.condeferred = false THEN v_sql := v_sql || ' NOT DEFERRABLE '; WHEN v_row.condeferrable = true AND v_row.condeferred = false THEN v_sql := v_sql || ' DEFERRABLE INITIALLY IMMEDIATE '; END CASE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying FK: '||v_sql); END IF; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'FK applied'); END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; 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 APPLYING FOREIGN KEYS: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to monitor for data getting inserted into parent tables managed by extension */ CREATE OR REPLACE FUNCTION check_parent() RETURNS SETOF @extschema@.check_parent_table LANGUAGE plpgsql STABLE SECURITY DEFINER AS $$ DECLARE v_count bigint = 0; v_sql text; v_tables record; v_trouble @extschema@.check_parent_table%rowtype; BEGIN FOR v_tables IN SELECT DISTINCT parent_table FROM @extschema@.part_config LOOP v_sql := 'SELECT count(1) AS n FROM ONLY '||v_tables.parent_table; EXECUTE v_sql INTO v_count; IF v_count > 0 THEN v_trouble.parent_table := v_tables.parent_table; v_trouble.count := v_count; RETURN NEXT v_trouble; END IF; v_count := 0; END LOOP; RETURN; END $$; /* * 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. * To avoid logical complications and contention issues, ALL subpartitions must be maintained using run_maintenance(). * This means the automatic, trigger based partition creation for serial partitioning will not work if it is a subpartition. */ CREATE OR REPLACE FUNCTION create_sub_parent( p_top_parent text , p_control text , p_type text , p_interval text , p_constraint_cols text[] DEFAULT NULL , p_premake int DEFAULT 4 , p_start_partition text DEFAULT NULL , p_inherit_fk boolean DEFAULT true , p_jobmon boolean DEFAULT true , p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_last_partition text; v_row record; v_row_last_part record; v_run_maint boolean; v_sql text; v_success boolean := false; v_top_type text; BEGIN SELECT use_run_maintenance INTO v_run_maint FROM @extschema@.part_config WHERE parent_table = p_top_parent; IF v_run_maint 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; ELSIF v_run_maint = false THEN RAISE EXCEPTION 'Any parent table that will be part of a sub-partitioned set (on any level) must have use_run_maintenance set to true in part_config table, even for serial partitioning. See documentation for more info.'; END IF; FOR v_row IN -- Loop through all current children to turn them into partitioned tables SELECT show_partitions AS child_table FROM @extschema@.show_partitions(p_top_parent) LOOP -- Just call existing create_parent() function but add the given parameters to the part_config_sub table as well 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_use_run_maintenance := %L , p_start_partition := %L , p_inherit_fk := %L , p_jobmon := %L , p_debug := %L )' , v_row.child_table , p_control , p_type , p_interval , p_constraint_cols , p_premake , true , p_start_partition , p_inherit_fk , p_jobmon , p_debug); EXECUTE v_sql; END LOOP; 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_use_run_maintenance , sub_jobmon) VALUES ( p_top_parent , p_control , p_type , p_interval , p_constraint_cols , p_premake , p_inherit_fk , true , p_jobmon); v_success := true; RETURN v_success; END $$; /* * Drop constraints managed by pg_partman */ CREATE OR REPLACE FUNCTION drop_constraints(p_parent_table text, p_child_table text, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_col text; v_constraint_cols text[]; v_existing_constraint_name text; v_exists boolean := FALSE; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_sql text; v_step_id bigint; BEGIN SELECT constraint_cols , jobmon INTO v_constraint_cols , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_constraint_cols IS NULL THEN RAISE EXCEPTION 'Given parent table (%) not set up for constraint management (constraint_cols is NULL)', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN DROP CONSTRAINT: '||p_parent_table); v_step_id := add_step(v_job_id, 'Entering constraint drop loop'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; FOREACH v_col IN ARRAY v_constraint_cols LOOP SELECT c.conname INTO v_existing_constraint_name FROM pg_catalog.pg_constraint c JOIN pg_catalog.pg_attribute a ON c.conrelid = a.attrelid WHERE conrelid = p_child_table::regclass AND c.conname LIKE 'partmanconstr_%' AND c.contype = 'c' AND a.attname = v_col AND ARRAY[a.attnum] <@ c.conkey AND a.attisdropped = false; IF v_existing_constraint_name IS NOT NULL THEN v_exists := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Dropping constraint on column: '||v_col); END IF; v_sql := 'ALTER TABLE '||p_child_table||' DROP CONSTRAINT '||v_existing_constraint_name; IF p_debug THEN RAISE NOTICE 'Constraint drop query: %', v_sql; END IF; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Drop constraint query: '||v_sql); END IF; END IF; END LOOP; IF v_jobmon_schema IS NOT NULL AND v_exists IS FALSE THEN v_step_id := add_step(v_job_id, 'No constraints found to drop on child table: '||p_child_table); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; 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 DROP CONSTRAINT: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to drop child tables from an id-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ CREATE OR REPLACE FUNCTION drop_partition_id(p_parent_table text, p_retention bigint DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_child_table text; v_control text; v_drop_count int := 0; v_id_position int; v_index record; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_max bigint; v_old_search_path text; v_partition_interval bigint; v_partition_id bigint; v_retention bigint; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_row_max_id record; v_step_id bigint; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman drop_partition_id')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_id already running.'; RETURN 0; END IF; -- Allow override of configuration options IF p_retention IS NULL THEN SELECT partition_interval::bigint , control , retention::bigint , retention_keep_table , retention_keep_index , retention_schema , jobmon INTO v_partition_interval , v_control , v_retention , v_retention_keep_table , v_retention_keep_index , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id' AND retention IS NOT NULL; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE SELECT partition_interval::bigint , control , retention_keep_table , retention_keep_index , retention_schema , jobmon INTO v_partition_interval , v_control , v_retention_keep_table , v_retention_keep_index , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; v_retention := p_retention; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT show_partitions FROM @extschema@.show_partitions(p_parent_table, 'DESC') LOOP EXECUTE 'SELECT max('||v_control||') FROM '||v_row_max_id.show_partitions INTO v_max; IF v_max IS NOT NULL THEN EXIT; END IF; END LOOP; -- Loop through child tables of the given parent FOR v_child_table IN SELECT n.nspname||'.'||c.relname FROM pg_inherits i join pg_class c ON i.inhrelid = c.oid join pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC LOOP v_id_position := (length(v_child_table) - position('p_' in reverse(v_child_table))) + 2; v_partition_id := substring(v_child_table from v_id_position)::bigint; -- Add one interval since partition names contain the start of the constraint period IF v_retention <= (v_max - (v_partition_id + v_partition_interval)) THEN -- Only create a jobmon entry if there's actual retention work done IF v_jobmon_schema IS NOT NULL AND v_job_id IS NULL THEN v_job_id := add_job('PARTMAN DROP ID PARTITION: '|| p_parent_table); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Uninherit table '||v_child_table||' from '||p_parent_table); END IF; EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Drop table '||v_child_table); END IF; EXECUTE 'DROP TABLE '||v_child_table||' CASCADE'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN FOR v_index IN SELECT i.indexrelid::regclass AS name , c.conname FROM pg_catalog.pg_index i LEFT JOIN pg_catalog.pg_constraint c ON i.indexrelid = c.conindid WHERE i.indrelid = v_child_table::regclass LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Drop index '||v_index.name||' from '||v_child_table); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_child_table||' DROP CONSTRAINT '||v_index.conname; ELSE EXECUTE 'DROP INDEX '||v_index.name; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Moving table '||v_child_table||' to schema '||v_retention_schema); END IF; EXECUTE 'ALTER TABLE '||v_child_table||' SET SCHEMA '||v_retention_schema; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if -- If child table is a subpartition, remove it from part_config & part_config_sub (should cascade due to FK) DELETE FROM @extschema@.part_config WHERE parent_table = v_child_table; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', v_drop_count||' partitions dropped.'); PERFORM close_job(v_job_id); END IF; EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_drop_count; 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 DROP ID PARTITION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to drop child tables from a time-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ CREATE OR REPLACE FUNCTION drop_partition_time(p_parent_table text, p_retention interval DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_child_table text; v_datetime_string text; v_drop_count int := 0; v_index record; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_partition_interval interval; v_partition_timestamp timestamp; v_quarter text; v_retention interval; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_step_id bigint; v_time_position int; v_type text; v_year text; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman drop_partition_time')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_time already running.'; RETURN 0; END IF; -- Allow override of configuration options IF p_retention IS NULL THEN SELECT partition_type , partition_interval::interval , retention::interval , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_type , v_partition_interval , v_retention , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom') AND retention IS NOT NULL; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE SELECT partition_type , partition_interval::interval , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_type , v_partition_interval , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); v_retention := p_retention; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; -- Loop through child tables of the given parent FOR v_child_table IN SELECT n.nspname||'.'||c.relname FROM pg_inherits i join pg_class c ON i.inhrelid = c.oid join pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC LOOP -- pull out datetime portion of partition's tablename to make the next one v_time_position := (length(v_child_table) - position('p_' in reverse(v_child_table))) + 2; IF v_partition_interval <> '3 months' OR (v_partition_interval = '3 months' AND v_type = 'time-custom') THEN v_partition_timestamp := to_timestamp(substring(v_child_table from v_time_position), v_datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(v_child_table from v_time_position), 'q', 1); v_quarter := split_part(substring(v_child_table from v_time_position), 'q', 2); CASE WHEN v_quarter = '1' THEN v_partition_timestamp := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_partition_timestamp := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_partition_timestamp := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_partition_timestamp := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; -- Add one interval since partition names contain the start of the constraint period IF v_retention < (CURRENT_TIMESTAMP - (v_partition_timestamp + v_partition_interval)) THEN -- Only create a jobmon entry if there's actual retention work done IF v_jobmon_schema IS NOT NULL AND v_job_id IS NULL THEN v_job_id := add_job('PARTMAN DROP TIME PARTITION: '|| p_parent_table); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Uninherit table '||v_child_table||' from '||p_parent_table); END IF; EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF v_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_child_table; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Drop table '||v_child_table); END IF; EXECUTE 'DROP TABLE '||v_child_table||' CASCADE'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN FOR v_index IN SELECT i.indexrelid::regclass AS name , c.conname FROM pg_catalog.pg_index i LEFT JOIN pg_catalog.pg_constraint c ON i.indexrelid = c.conindid WHERE i.indrelid = v_child_table::regclass LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Drop index '||v_index.name||' from '||v_child_table); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_child_table||' DROP CONSTRAINT '||v_index.conname; ELSE EXECUTE 'DROP INDEX '||v_index.name; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Moving table '||v_child_table||' to schema '||v_retention_schema); END IF; EXECUTE 'ALTER TABLE '||v_child_table||' SET SCHEMA '||v_retention_schema; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if -- If child table is a subpartition, remove it from part_config & part_config_sub (should cascade due to FK) DELETE FROM @extschema@.part_config WHERE parent_table = v_child_table; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', v_drop_count||' partitions dropped.'); PERFORM close_job(v_job_id); END IF; EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_drop_count; 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 DROP TIME PARTITION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Populate the child table(s) of an id-based partition set with old data from the original parent */ CREATE OR REPLACE FUNCTION partition_data_id(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval int DEFAULT NULL, p_lock_wait numeric DEFAULT 0, p_order text DEFAULT 'ASC') RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_current_partition_name text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_id bigint; v_min_partition_id bigint; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_partition_id bigint[]; v_rowcount bigint; v_sql text; v_start_control bigint; v_total_rows bigint := 0; BEGIN SELECT partition_interval::bigint , control INTO v_partition_interval , v_control FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; FOR i IN 1..p_batch_count LOOP IF p_order = 'ASC' THEN EXECUTE 'SELECT min('||v_control||') FROM ONLY '||p_parent_table INTO v_start_control; IF v_start_control IS NULL THEN EXIT; END IF; v_min_partition_id = v_start_control - (v_start_control % v_partition_interval); v_partition_id := ARRAY[v_min_partition_id]; -- Check if custom batch interval overflows current partition maximum IF (v_start_control + p_batch_interval) >= (v_min_partition_id + v_partition_interval) THEN v_max_partition_id := v_min_partition_id + v_partition_interval; ELSE v_max_partition_id := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN EXECUTE 'SELECT max('||v_control||') FROM ONLY '||p_parent_table INTO v_start_control; IF v_start_control IS NULL THEN EXIT; END IF; v_min_partition_id = v_start_control - (v_start_control % v_partition_interval); -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_id := v_min_partition_id + v_partition_interval; v_partition_id := ARRAY[v_min_partition_id]; -- Make sure minimum doesn't underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_id THEN v_min_partition_id = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN v_sql := 'SELECT * FROM ONLY ' || p_parent_table || ' WHERE '||v_control||' >= '||quote_literal(v_min_partition_id)|| ' AND '||v_control||' < '||quote_literal(v_max_partition_id) ||' FOR UPDATE NOWAIT'; EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; PERFORM @extschema@.create_partition_id(p_parent_table, v_partition_id); SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname||'.'||tablename = p_parent_table; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_min_partition_id::text, TRUE); EXECUTE 'WITH partition_data AS ( DELETE FROM ONLY '||p_parent_table||' WHERE '||v_control||' >= '||v_min_partition_id|| ' AND '||v_control||' < '||v_max_partition_id||' RETURNING *) INSERT INTO '||v_current_partition_name||' SELECT * FROM partition_data'; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; PERFORM @extschema@.create_function_id(p_parent_table); RETURN v_total_rows; END $$; /* * Populate the child table(s) of a time-based partition set with old data from the original parent */ CREATE OR REPLACE FUNCTION partition_data_time(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval interval DEFAULT NULL, p_lock_wait numeric DEFAULT 0, p_order text DEFAULT 'ASC') RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_datetime_string text; v_current_partition_name text; v_max_partition_timestamp timestamp; v_last_partition text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_min_partition_timestamp timestamp; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_partition_suffix text; v_partition_timestamp timestamp[]; v_quarter text; v_rowcount bigint; v_sql text; v_start_control timestamp; v_time_position int; v_total_rows bigint := 0; v_type text; v_year text; BEGIN SELECT partition_type , partition_interval::interval , control , datetime_string INTO v_type , v_partition_interval , v_control , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; SELECT show_partitions INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; FOR i IN 1..p_batch_count LOOP IF p_order = 'ASC' THEN EXECUTE 'SELECT min('||v_control||') FROM ONLY '||p_parent_table INTO v_start_control; ELSIF p_order = 'DESC' THEN EXECUTE 'SELECT max('||v_control||') FROM ONLY '||p_parent_table INTO v_start_control; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; IF v_start_control IS NULL THEN EXIT; END IF; IF v_type = 'time' THEN CASE WHEN v_partition_interval = '15 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '15min'::interval * floor(date_part('minute', v_start_control) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '30min'::interval * floor(date_part('minute', v_start_control) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control); WHEN v_partition_interval = '1 day' THEN v_min_partition_timestamp := date_trunc('day', v_start_control); WHEN v_partition_interval = '1 week' THEN v_min_partition_timestamp := date_trunc('week', v_start_control); WHEN v_partition_interval = '1 month' THEN v_min_partition_timestamp := date_trunc('month', v_start_control); WHEN v_partition_interval = '3 months' THEN v_min_partition_timestamp := date_trunc('quarter', v_start_control); WHEN v_partition_interval = '1 year' THEN v_min_partition_timestamp := date_trunc('year', v_start_control); END CASE; ELSIF v_type = 'time-custom' THEN -- Keep going backwards, checking if the time interval encompases the current v_start_control value v_time_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; v_min_partition_timestamp := to_timestamp(substring(v_last_partition from v_time_position), v_datetime_string); v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; LOOP IF v_start_control >= v_min_partition_timestamp AND v_start_control < v_max_partition_timestamp THEN EXIT; ELSE v_max_partition_timestamp := v_min_partition_timestamp; BEGIN v_min_partition_timestamp := v_min_partition_timestamp - v_partition_interval; EXCEPTION WHEN datetime_field_overflow THEN RAISE EXCEPTION 'Attempted partition time interval is outside PostgreSQL''s supported time range. Unable to create partition with interval before timestamp % ', v_min_partition_interval; END; END IF; END LOOP; END IF; v_partition_timestamp := ARRAY[v_min_partition_timestamp]; IF p_order = 'ASC' THEN IF (v_start_control + p_batch_interval) >= (v_min_partition_timestamp + v_partition_interval) THEN v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; ELSE v_max_partition_timestamp := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; -- Make sure minimum doesn't underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_timestamp THEN v_min_partition_timestamp = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN v_sql := 'SELECT * FROM ONLY ' || p_parent_table || ' WHERE '||v_control||' >= '||quote_literal(v_min_partition_timestamp)|| ' AND '||v_control||' < '||quote_literal(v_max_partition_timestamp) ||' FOR UPDATE NOWAIT'; EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; PERFORM @extschema@.create_partition_time(p_parent_table, v_partition_timestamp); -- This suffix generation code is in create_partition_time() as well v_partition_suffix := to_char(v_min_partition_timestamp, 'YYYY'); IF v_partition_interval < '1 year' AND v_partition_interval <> '1 week' THEN v_partition_suffix := v_partition_suffix ||'_'|| to_char(v_min_partition_timestamp, 'MM'); IF v_partition_interval < '1 month' AND v_partition_interval <> '1 week' THEN v_partition_suffix := v_partition_suffix ||'_'|| to_char(v_min_partition_timestamp, 'DD'); IF v_partition_interval < '1 day' THEN v_partition_suffix := v_partition_suffix || '_' || to_char(v_min_partition_timestamp, 'HH24MI'); IF v_partition_interval < '1 minute' THEN v_partition_suffix := v_partition_suffix || to_char(v_min_partition_timestamp, 'SS'); END IF; -- end < minute IF END IF; -- end < day IF END IF; -- end < month IF END IF; -- end < year IF IF v_partition_interval = '1 week' THEN v_partition_suffix := to_char(v_min_partition_timestamp, 'IYYY') || 'w' || to_char(v_min_partition_timestamp, 'IW'); END IF; -- "Q" is ignored in to_timestamp, so handle special case IF v_partition_interval = '3 months' AND (v_type = 'time') THEN v_year := to_char(v_min_partition_timestamp, 'YYYY'); v_quarter := to_char(v_min_partition_timestamp, 'Q'); v_partition_suffix := v_year || 'q' || v_quarter; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname||'.'||tablename = p_parent_table; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_partition_suffix, TRUE); EXECUTE 'WITH partition_data AS ( DELETE FROM ONLY '||p_parent_table||' WHERE '||v_control||' >= '||quote_literal(v_min_partition_timestamp)|| ' AND '||v_control||' < '||quote_literal(v_max_partition_timestamp)||' RETURNING *) INSERT INTO '||v_current_partition_name||' SELECT * FROM partition_data'; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; PERFORM @extschema@.create_function_time(p_parent_table); RETURN v_total_rows; END $$; /* * Function to re-apply ownership & privileges on all child tables in a partition set using parent table as reference */ CREATE OR REPLACE FUNCTION reapply_privileges(p_parent_table text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_child_owner text; v_child_table text; v_child_grant record; v_grant text; v_grantees text[]; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_match boolean; v_old_search_path text; v_parent_owner text; v_owner_sql text; v_revoke text[]; v_parent_grant record; v_sql text; v_step_id bigint; BEGIN SELECT jobmon INTO v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon IS NULL THEN RAISE EXCEPTION 'Given table is not managed by this extention: %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN RE-APPLYING PRIVILEGES TO ALL CHILD TABLES OF: '||p_parent_table); v_step_id := add_step(v_job_id, 'Setting new child table privileges'); END IF; SELECT tableowner INTO v_parent_owner FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; FOR v_child_table IN SELECT n.nspname||'.'||c.relname FROM pg_inherits i join pg_class c ON i.inhrelid = c.oid join pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC LOOP IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'PENDING', 'Currently on child partition in ascending order: '||v_child_table); END IF; v_grantees := NULL; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP -- Compare parent & child grants. Don't re-apply if it already exists v_match := false; FOR v_child_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_child_table GROUP BY grantee LOOP IF v_parent_grant.types = v_child_grant.types AND v_parent_grant.grantee = v_child_grant.grantee THEN v_match := true; END IF; END LOOP; IF v_match = false THEN EXECUTE 'GRANT '||array_to_string(v_parent_grant.types, ',')||' ON '||v_child_table||' TO '||v_parent_grant.grantee; SELECT array_agg(r) INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE '||array_to_string(v_revoke, ',')||' ON '||v_child_table||' FROM '||v_parent_grant.grantee||' CASCADE'; END IF; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN SELECT array_agg(r) INTO v_revoke FROM ( SELECT DISTINCT grantee::text AS r FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_child_table EXCEPT SELECT unnest(v_grantees)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE ALL ON '||v_child_table||' FROM '||array_to_string(v_revoke, ','); END IF; END IF; SELECT tableowner INTO v_child_owner FROM pg_tables WHERE schemaname ||'.'|| tablename = v_child_table; IF v_parent_owner <> v_child_owner THEN EXECUTE 'ALTER TABLE '||v_child_table||' OWNER TO '||v_parent_owner; END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); PERFORM close_job(v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; 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 RE-APPLYING PRIVILEGES TO ALL CHILD TABLES OF: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to manage pre-creation of the next partitions in a set. * Also manages dropping old partitions if the retention option is set. * If p_parent_table is passed, will only run run_maintenance() on that one table (no matter what the configuration table may have set for it) * Otherwise, will run on all tables in the config table with p_run_maintenance() set to true. * For large partition sets, running analyze can cause maintenance to take longer than expected. Can set p_analyze to false to avoid a forced analyze run. * Be aware that constraint exclusion may not work properly until an analyze on the partition set is run. */ CREATE OR REPLACE FUNCTION run_maintenance(p_parent_table text DEFAULT NULL, p_analyze boolean DEFAULT true, p_jobmon boolean DEFAULT true) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_create_count int := 0; v_current_partition text; v_current_partition_id bigint; v_current_partition_timestamp timestamp; v_datetime_string text; v_drop_count int := 0; v_id_position int; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_created boolean; v_last_partition_id bigint; v_last_partition_timestamp timestamp; v_next_partition_id bigint; v_next_partition_timestamp timestamp; v_old_search_path text; v_premade_count int; v_quarter text; v_row record; v_row_max_id record; v_row_sub record; v_step_id bigint; v_step_overflow_id bigint; v_step_serial_id bigint; v_sub_id_max bigint; v_sub_id_min bigint; v_sub_parent text; v_sub_timestamp_max timestamp; v_sub_timestamp_min timestamp; v_tablename text; v_tables_list_sql text; v_time_position int; v_year text; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman maintenance already running.'; RETURN; END IF; 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' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN RUN MAINTENANCE'); v_step_id := add_step(v_job_id, 'Running maintenance loop'); END IF; v_tables_list_sql := 'SELECT parent_table , partition_type , partition_interval , control , premake , datetime_string , undo_in_progress FROM @extschema@.part_config'; IF p_parent_table IS NULL THEN v_tables_list_sql := v_tables_list_sql || ' WHERE use_run_maintenance = true'; ELSE v_tables_list_sql := v_tables_list_sql || format(' WHERE parent_table = %L', p_parent_table); END IF; FOR v_row IN EXECUTE v_tables_list_sql LOOP CONTINUE WHEN v_row.undo_in_progress; SELECT show_partitions INTO v_last_partition FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LIMIT 1; IF v_row.partition_type = 'time' OR v_row.partition_type = 'time-custom' THEN IF v_row.partition_type = 'time' THEN CASE WHEN v_row.partition_interval::interval = '15 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_row.partition_interval::interval = '30 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_row.partition_interval::interval = '1 hour' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_row.partition_interval::interval = '1 day' THEN v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_row.partition_interval::interval = '1 week' THEN v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_row.partition_interval::interval = '1 month' THEN v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_row.partition_interval::interval = '3 months' THEN v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_row.partition_interval::interval = '1 year' THEN v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; ELSIF v_row.partition_type = 'time-custom' THEN SELECT child_table INTO v_current_partition FROM @extschema@.custom_time_partitions WHERE parent_table = v_row.parent_table AND partition_range @> CURRENT_TIMESTAMP; IF v_current_partition IS NULL THEN RAISE EXCEPTION 'Current time partition missing from custom_time_partitions config table for table % and timestamp %', CURRENT_TIMESTAMP, v_row.parent_table; END IF; v_time_position := (length(v_current_partition) - position('p_' in reverse(v_current_partition))) + 2; v_current_partition_timestamp := to_timestamp(substring(v_current_partition from v_time_position), v_row.datetime_string); END IF; -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::timestamp, sub_max::timestamp INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'time'); -- No need to run maintenance if it's outside the bounds of the top parent. IF v_sub_timestamp_min IS NOT NULL THEN IF v_current_partition_timestamp < v_sub_timestamp_min OR v_current_partition_timestamp > v_sub_timestamp_max THEN CONTINUE; END IF; END IF; v_time_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; IF v_row.partition_interval::interval <> '3 months' OR (v_row.partition_interval::interval = '3 months' AND v_row.partition_type = 'time-custom') THEN v_last_partition_timestamp := to_timestamp(substring(v_last_partition from v_time_position), v_row.datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(v_last_partition from v_time_position), 'q', 1); v_quarter := split_part(substring(v_last_partition from v_time_position), 'q', 2); CASE WHEN v_quarter = '1' THEN v_last_partition_timestamp := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_last_partition_timestamp := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_last_partition_timestamp := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_last_partition_timestamp := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; -- Check and see how many premade partitions there are. v_premade_count = round(EXTRACT('epoch' FROM age(v_last_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); v_next_partition_timestamp := v_last_partition_timestamp; -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed. WHILE v_premade_count < v_row.premake LOOP BEGIN v_next_partition_timestamp := v_next_partition_timestamp + v_row.partition_interval::interval; EXCEPTION WHEN datetime_field_overflow THEN v_premade_count := v_row.premake; -- do this so it can exit the premake check loop and continue in the outer for loop IF v_jobmon_schema IS NOT NULL THEN 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 skippd for parent table '||v_partition_time); END IF; RAISE WARNING 'Attempted partition time interval is outside PostgreSQL''s supported time range. Child partition creation skipped for parent table %', v_row.parent_table; CONTINUE; END; v_last_partition_created := @extschema@.create_partition_time(v_row.parent_table, ARRAY[v_next_partition_timestamp], p_analyze); v_create_count := v_create_count + 1; PERFORM @extschema@.create_function_time(v_row.parent_table); -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(v_row.parent_table); v_premade_count = round(EXTRACT('epoch' FROM age(v_next_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); END LOOP; ELSIF v_row.partition_type = 'id' THEN -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT show_partitions FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP EXECUTE 'SELECT '||v_row.control||' - ('||v_row.control||' % '||v_row.partition_interval::int||') FROM '||v_row_max_id.show_partitions||' WHERE '||v_row.control||' = (SELECT max('||v_row.control||') FROM '||v_row_max_id.show_partitions||')' INTO v_current_partition_id; IF v_current_partition_id IS NOT NULL THEN EXIT; END IF; END LOOP; -- Determine if this table is a child of a subpartition parent. If so, get limits to see if run_maintenance even needs to run for it. SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'id'); -- No need to run maintenance if it's outside the bounds of the top parent. IF v_sub_id_min IS NOT NULL THEN IF v_current_partition_id < v_sub_id_min OR v_current_partition_id > v_sub_id_max THEN CONTINUE; END IF; END IF; v_id_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; v_last_partition_id = substring(v_last_partition from v_id_position)::bigint; v_next_partition_id := v_last_partition_id + v_row.partition_interval::bigint; WHILE ((v_next_partition_id - v_current_partition_id) / v_row.partition_interval::bigint) <= v_row.premake LOOP v_last_partition_created := @extschema@.create_partition_id(v_row.parent_table, ARRAY[v_next_partition_id], p_analyze); IF v_last_partition_created THEN PERFORM @extschema@.create_function_id(v_row.parent_table); PERFORM @extschema@.apply_constraints(v_row.parent_table); END IF; v_next_partition_id := v_next_partition_id + v_row.partition_interval::bigint; END LOOP; END IF; -- end main IF check for time or id END LOOP; -- end of creation loop -- Manage dropping old partitions if retention option is set FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND (partition_type = 'time' OR partition_type = 'time-custom') LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); END IF; END IF; END LOOP; FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND partition_type = 'id' LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); END IF; END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Partition maintenance finished. '||v_create_count||' partitions made. '||v_drop_count||' partitions dropped.'); IF v_step_overflow_id IS NOT NULL OR v_step_serial_id IS NOT NULL THEN PERFORM fail_job(v_job_id); ELSE PERFORM close_job(v_job_id); END IF; EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; 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 RUN MAINTENANCE'')', v_jobmon_schema) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to list all child partitions in a set. */ CREATE OR REPLACE FUNCTION show_partitions (p_parent_table text, p_order text DEFAULT 'ASC') RETURNS SETOF text LANGUAGE plpgsql STABLE SECURITY DEFINER AS $$ DECLARE v_datetime_string text; v_partition_interval text; v_type text; BEGIN IF p_order NOT IN ('ASC', 'DESC') THEN RAISE EXCEPTION 'p_order paramter must be one of the following values: ASC, DESC'; END IF; SELECT partition_type , partition_interval , datetime_string INTO v_type , v_partition_interval , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_type IN ('time', 'time-custom') THEN RETURN QUERY EXECUTE ' SELECT n.nspname::text ||''.''|| c.relname::text AS partition_name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = '||quote_literal(p_parent_table)||'::regclass ORDER BY to_timestamp(substring(c.relname from ((length(c.relname) - position(''p_'' in reverse(c.relname))) + 2) ), '||quote_literal(v_datetime_string)||') ' || p_order; ELSIF v_type = 'id' THEN RETURN QUERY EXECUTE ' SELECT n.nspname::text ||''.''|| c.relname::text AS partition_name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = '||quote_literal(p_parent_table)||'::regclass ORDER BY substring(c.relname from ((length(c.relname) - position(''p_'' in reverse(c.relname))) + 2) )::bigint ' || p_order; END IF; END $$; /* * Function to undo partitioning. * Will actually work on any parent/child table set, not just ones created by pg_partman. */ CREATE OR REPLACE FUNCTION undo_partition(p_parent_table text, p_batch_count int DEFAULT 1, p_keep_table boolean DEFAULT true, p_jobmon boolean DEFAULT true, p_lock_wait numeric DEFAULT 0) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_batch_loop_count bigint := 0; v_child_count bigint; v_child_table text; v_copy_sql text; v_function_name text; v_job_id bigint; v_jobmon_schema text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_rowcount bigint; v_step_id bigint; v_total bigint := 0; v_trig_name text; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition already running.'; RETURN 0; END IF; IF p_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Undoing partitioning for table '||p_parent_table); END IF; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables and stop new id partitions from being made. SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, '_part_trig_func', FALSE); SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t WHERE tgname = v_trig_name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE 'LOCK TABLE ONLY '||p_parent_table||' IN ACCESS EXCLUSIVE MODE NOWAIT'; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; RETURN -1; END IF; END IF; -- END p_lock_wait IF EXECUTE 'DROP TRIGGER IF EXISTS '||v_trig_name||' ON '||p_parent_table; END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE 'DROP FUNCTION IF EXISTS '||v_function_name||'()'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); END IF; WHILE v_batch_loop_count < p_batch_count LOOP SELECT n.nspname||'.'||c.relname INTO v_child_table FROM pg_inherits i JOIN pg_class c ON i.inhrelid = c.oid JOIN pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC; EXIT WHEN v_child_table IS NULL; EXECUTE 'SELECT count(*) FROM '||v_child_table INTO v_child_count; IF v_child_count = 0 THEN -- No rows left in this child table. Remove from partition set. -- lockwait timeout for table drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE 'LOCK TABLE ONLY '||v_child_table||' IN ACCESS EXCLUSIVE MODE NOWAIT'; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; RETURN -1; END IF; END IF; -- END p_lock_wait IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF p_keep_table = false THEN EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table DROPPED. Moved '||coalesce(v_rowcount, 0)||' rows to parent'); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table UNINHERITED, not DROPPED. Copied '||coalesce(v_rowcount, 0)||' rows to parent'); END IF; END IF; v_undo_count := v_undo_count + 1; CONTINUE; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing child partition: '||v_child_table); END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE 'SELECT * FROM '|| v_child_table ||' FOR UPDATE NOWAIT'; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; RETURN -1; END IF; END IF; v_copy_sql := 'INSERT INTO '||p_parent_table||' SELECT * FROM '||v_child_table; EXECUTE v_copy_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF p_keep_table = false THEN EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table DROPPED. Moved '||v_rowcount||' rows to parent'); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table UNINHERITED, not DROPPED. Copied '||v_rowcount||' rows to parent'); END IF; END IF; v_batch_loop_count := v_batch_loop_count + 1; v_undo_count := v_undo_count + 1; END LOOP; IF v_undo_count = 0 THEN -- FOR loop never ran, so there's no child tables left. DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman (if it existed)'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) from % child table(s) to the parent: %', v_total, v_undo_count, p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', 'Copied '||v_total||' row(s) from '||v_undo_count||' child table(s) to the parent'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_total; 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 UNDO PARTITIONING: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to undo id-based partitioning created by this extension */ CREATE OR REPLACE FUNCTION undo_partition_id(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval bigint DEFAULT NULL, p_keep_table boolean DEFAULT true, p_lock_wait numeric DEFAULT 0) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_batch_loop_count int := 0; v_child_loop_total bigint := 0; v_child_min bigint; v_child_table text; v_control text; v_exists int; v_function_name text; v_inner_loop_count int; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_move_sql text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_row record; v_rowcount bigint; v_step_id bigint; v_sub_count int; v_trig_name text; v_total bigint := 0; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition_id')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition_id already running.'; RETURN 0; END IF; SELECT partition_interval::bigint , control , jobmon INTO v_partition_interval , v_control , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; -- Check if any child tables are themselves partitioned or part of an inheritance tree. Prevent undo at this level if so. -- Need to either lock child tables at all levels or handle the proper removal of triggers on all child tables first -- before multi-level undo can be performed safely. FOR v_row IN SELECT show_partitions AS child_table FROM @extschema@.show_partitions(p_parent_table) LOOP SELECT count(*) INTO v_sub_count FROM pg_catalog.pg_inherits WHERE inhparent::regclass = v_row.child_table::regclass; IF v_sub_count > 0 THEN RAISE EXCEPTION 'Child table for this parent has child table(s) itself (%). Run undo partitioning on this table or remove inheritance first to ensure all data is properly moved to parent', v_row.child_table; END IF; END LOOP; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Undoing partitioning for table '||p_parent_table); END IF; IF p_batch_interval IS NULL THEN p_batch_interval := v_partition_interval; END IF; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables and stop new id partitions from being made. SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, '_part_trig_func', FALSE); SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t WHERE tgname = v_trig_name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE 'LOCK TABLE ONLY '||p_parent_table||' IN ACCESS EXCLUSIVE MODE NOWAIT'; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; RETURN -1; END IF; END IF; -- END p_lock_wait IF EXECUTE 'DROP TRIGGER IF EXISTS '||v_trig_name||' ON '||p_parent_table; END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE 'DROP FUNCTION IF EXISTS '||v_function_name||'()'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); END IF; <> WHILE v_batch_loop_count < p_batch_count LOOP SELECT n.nspname||'.'||c.relname INTO v_child_table FROM pg_inherits i JOIN pg_class c ON i.inhrelid = c.oid JOIN pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC; EXIT WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing child partition: '||v_child_table); END IF; EXECUTE 'SELECT min('||v_control||') FROM '||v_child_table INTO v_child_min; IF v_child_min IS NULL THEN -- No rows left in this child table. Remove from partition set. -- lockwait timeout for table drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE 'LOCK TABLE ONLY '||v_child_table||' IN ACCESS EXCLUSIVE MODE NOWAIT'; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; RETURN -1; END IF; END IF; -- END p_lock_wait IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF p_keep_table = false THEN EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table UNINHERITED, not DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; END IF; v_undo_count := v_undo_count + 1; CONTINUE outer_child_loop; END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP -- lockwait timeout for row batches IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE 'SELECT * FROM ' || v_child_table || ' WHERE '||v_control||' <= '||quote_literal(v_child_min + (p_batch_interval * v_inner_loop_count)) ||' FOR UPDATE NOWAIT'; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; RETURN -1; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval v_move_sql := 'WITH move_data AS (DELETE FROM '||v_child_table|| ' WHERE '||v_control||' <= '||quote_literal(v_child_min + (p_batch_interval * v_inner_loop_count))||' RETURNING *) INSERT INTO '||p_parent_table||' SELECT * FROM move_data'; EXECUTE v_move_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Moved '||v_child_loop_total||' rows to parent.'); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; IF v_batch_loop_count < p_batch_count THEN -- FOR loop never ran, so there's no child tables left. DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) to the parent. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', 'Copied '||v_total||' row(s) to the parent. Removed '||v_undo_count||' partitions.'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_total; 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 UNDO PARTITIONING: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to undo time-based partitioning created by this extension */ CREATE OR REPLACE FUNCTION undo_partition_time(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval interval DEFAULT NULL, p_keep_table boolean DEFAULT true, p_lock_wait numeric DEFAULT 0) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_batch_loop_count int := 0; v_child_min timestamptz; v_child_loop_total bigint := 0; v_child_table text; v_control text; v_function_name text; v_inner_loop_count int; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_move_sql text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_row record; v_rowcount bigint; v_step_id bigint; v_sub_count int; v_total bigint := 0; v_trig_name text; v_type text; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition_time')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition_time already running.'; RETURN 0; END IF; SELECT partition_type , partition_interval::interval , control , jobmon INTO v_type , v_partition_interval , v_control , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; -- Check if any child tables are themselves partitioned or part of an inheritance tree. Prevent undo at this level if so. -- Need to either lock child tables at all levels or handle the proper removal of triggers on all child tables first -- before multi-level undo can be performed safely. FOR v_row IN SELECT show_partitions AS child_table FROM @extschema@.show_partitions(p_parent_table) LOOP SELECT count(*) INTO v_sub_count FROM pg_catalog.pg_inherits WHERE inhparent::regclass = v_row.child_table::regclass; IF v_sub_count > 0 THEN RAISE EXCEPTION 'Child table for this parent has child table(s) itself (%). Run undo partitioning on this table or remove inheritance first to ensure all data is properly moved to parent', v_row.child_table; END IF; END LOOP; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Undoing partitioning for table '||p_parent_table); END IF; IF p_batch_interval IS NULL THEN p_batch_interval := v_partition_interval; END IF; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables. SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, '_part_trig_func', FALSE); SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t WHERE tgname = v_trig_name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE 'LOCK TABLE ONLY '||p_parent_table||' IN ACCESS EXCLUSIVE MODE NOWAIT'; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; RETURN -1; END IF; END IF; -- END p_lock_wait IF EXECUTE 'DROP TRIGGER IF EXISTS '||v_trig_name||' ON '||p_parent_table; END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE 'DROP FUNCTION IF EXISTS '||v_function_name||'()'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); END IF; <> WHILE v_batch_loop_count < p_batch_count LOOP SELECT n.nspname||'.'||c.relname INTO v_child_table FROM pg_inherits i JOIN pg_class c ON i.inhrelid = c.oid JOIN pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC; EXIT WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing child partition: '||v_child_table); END IF; EXECUTE 'SELECT min('||v_control||') FROM '||v_child_table INTO v_child_min; IF v_child_min IS NULL THEN -- No rows left in this child table. Remove from partition set. -- lockwait timeout for table drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE 'LOCK TABLE ONLY '||v_child_table||' IN ACCESS EXCLUSIVE MODE NOWAIT'; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; RETURN -1; END IF; END IF; -- END p_lock_wait IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF p_keep_table = false THEN EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table UNINHERITED, not DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; END IF; IF v_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_child_table; END IF; v_undo_count := v_undo_count + 1; CONTINUE outer_child_loop; END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE 'SELECT * FROM ' || v_child_table || ' WHERE '||v_control||' <= '||quote_literal(v_child_min + (p_batch_interval * v_inner_loop_count)) ||' FOR UPDATE NOWAIT'; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; RETURN -1; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval v_move_sql := 'WITH move_data AS (DELETE FROM '||v_child_table|| ' WHERE '||v_control||' <= '||quote_literal(v_child_min + (p_batch_interval * v_inner_loop_count))||' RETURNING *) INSERT INTO '||p_parent_table||' SELECT * FROM move_data'; EXECUTE v_move_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Moved '||v_child_loop_total||' rows to parent.'); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; IF v_batch_loop_count < p_batch_count THEN -- FOR loop never ran, so there's no child tables left. DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) to the parent. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', 'Copied '||v_total||' row(s) to the parent. Removed '||v_undo_count||' partitions.'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_total; 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 UNDO PARTITIONING: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Check if parent table is a subpartition of an already existing id based partition set managed by pg_partman * If so, limit what child tables can be created based on parent suffix */ CREATE OR REPLACE FUNCTION check_subpartition_limits(p_parent_table text, p_type text, OUT sub_min text, OUT sub_max text) RETURNS record LANGUAGE plpgsql AS $$ DECLARE v_datetime_string text; v_id_position int; v_partition_interval interval; v_quarter text; v_sub_id_max bigint; v_sub_id_min bigint; v_sub_timestamp_max timestamp; v_sub_timestamp_min timestamp; v_time_position int; v_top_datetime_string text; v_top_interval text; v_top_parent text; v_top_type text; v_year text; BEGIN -- CTE query is done individually for each type (time, id) because it should return NULL if the top parent is not the same type in a subpartition set (id->time or time->id) IF p_type = 'id' THEN WITH top_oid AS ( SELECT i.inhparent AS top_parent_oid FROM pg_catalog.pg_class c JOIN pg_catalog.pg_inherits i ON c.oid = i.inhrelid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname||'.'||c.relname = p_parent_table ) SELECT n.nspname||'.'||c.relname, p.datetime_string, p.partition_interval, p.partition_type INTO v_top_parent, v_top_datetime_string, v_top_interval, v_top_type FROM pg_catalog.pg_class c JOIN top_oid t ON c.oid = t.top_parent_oid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace JOIN @extschema@.part_config p ON p.parent_table = n.nspname||'.'||c.relname WHERE c.oid = t.top_parent_oid AND p.partition_type = 'id'; IF v_top_parent IS NOT NULL THEN v_id_position := (length(p_parent_table) - position('p_' in reverse(p_parent_table))) + 2; v_sub_id_min := substring(p_parent_table from v_id_position)::bigint; v_sub_id_max := (v_sub_id_min + v_top_interval::bigint) - 1; sub_min := v_sub_id_min::text; sub_max := v_sub_id_max::text; END IF; ELSIF p_type = 'time' THEN WITH top_oid AS ( SELECT i.inhparent AS top_parent_oid FROM pg_catalog.pg_class c JOIN pg_catalog.pg_inherits i ON c.oid = i.inhrelid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname||'.'||c.relname = p_parent_table ) SELECT n.nspname||'.'||c.relname, p.datetime_string, p.partition_interval, p.partition_type INTO v_top_parent, v_top_datetime_string, v_top_interval, v_top_type FROM pg_catalog.pg_class c JOIN top_oid t ON c.oid = t.top_parent_oid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace JOIN @extschema@.part_config p ON p.parent_table = n.nspname||'.'||c.relname WHERE c.oid = t.top_parent_oid AND p.partition_type = 'time' OR p.partition_type = 'time-custom'; IF v_top_parent IS NOT NULL THEN v_time_position := (length(p_parent_table) - position('p_' in reverse(p_parent_table))) + 2; IF v_top_interval::interval <> '3 months' OR (v_top_interval::interval = '3 months' AND v_top_type = 'time-custom') THEN v_sub_timestamp_min := to_timestamp(substring(p_parent_table from v_time_position), v_top_datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(p_parent_table from v_time_position), 'q', 1); v_quarter := split_part(substring(p_parent_table from v_time_position), 'q', 2); CASE WHEN v_quarter = '1' THEN v_sub_timestamp_min := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_sub_timestamp_min := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_sub_timestamp_min := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_sub_timestamp_min := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; v_sub_timestamp_max = (v_sub_timestamp_min + v_top_interval::interval) - '1 sec'::interval; sub_min := v_sub_timestamp_min::text; sub_max := v_sub_timestamp_max::text; END IF; ELSE RAISE EXCEPTION 'Invalid type given as parameter to check_subpartition_limits()'; END IF; RETURN; END $$; pg_partman-4.6.0/updates/pg_partman--1.8.8--2.0.0.sql000066400000000000000000005636441412761706100214550ustar00rootroot00000000000000-- ************* CRITICAL MESSAGE ************** -- This file is only a transitional upgrade file to get from 1.8.8 to 2.0.0. See the CHANGELOG file for all relevant changes. -- You MUST upgrade beyond this file/version to the latest version of the 2.x series. DO NOT continue running 2.0.0!!!! -- ************* CRITICAL MESSAGE ************** ALTER TABLE @extschema@.part_config RENAME COLUMN "type" TO partition_type; ALTER TABLE @extschema@.part_config_sub RENAME COLUMN "sub_type" to "sub_partition_type"; ALTER TABLE @extschema@.part_config RENAME COLUMN part_interval TO partition_interval; ALTER TABLE @extschema@.part_config_sub RENAME COLUMN sub_part_interval TO sub_partition_interval; --NOTE This function is in the table sql file /* * Ensure that sub-partitioned tables that are themselves sub-partitions have the same configuration options set when they are part of the same inheritance tree */ CREATE OR REPLACE FUNCTION @extschema@.check_subpart_sameconfig(text) RETURNS boolean LANGUAGE sql STABLE AS $$ WITH child_tables AS ( SELECT n.nspname||'.'||c.relname AS tablename FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent::regclass = $1::regclass ) SELECT CASE WHEN count(*) <= 1 THEN true WHEN count(*) > 1 THEN false END FROM ( 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_use_run_maintenance , sub_jobmon FROM @extschema@.part_config_sub a JOIN child_tables b on a.sub_parent = b.tablename) x; $$; /* * Check function for config table partition types */ CREATE OR REPLACE FUNCTION @extschema@.check_partition_type (p_type text) RETURNS boolean LANGUAGE plpgsql IMMUTABLE SECURITY DEFINER AS $$ DECLARE v_result boolean; BEGIN SELECT p_type IN ('time', 'time-custom', 'id') INTO v_result; RETURN v_result; END $$; UPDATE @extschema@.part_config SET partition_type = 'time' WHERE partition_type = 'time-static' OR partition_type = 'time-dynamic'; UPDATE @extschema@.part_config_sub SET sub_partition_type = 'time' WHERE sub_partition_type = 'time-static' OR sub_partition_type = 'time-dynamic'; UPDATE @extschema@.part_config_sub SET sub_partition_type = 'id' WHERE sub_partition_type = 'id-static' OR sub_partition_type = 'id-dynamic'; /* * Function to turn a table into the parent of a partition set */ CREATE OR REPLACE FUNCTION 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_use_run_maintenance boolean DEFAULT NULL , p_start_partition text DEFAULT NULL , p_inherit_fk boolean DEFAULT true , p_jobmon boolean DEFAULT true , p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_base_timestamp timestamp; v_count int := 1; v_datetime_string text; v_higher_parent text := p_parent_table; v_id_interval bigint; v_id_position int; v_job_id bigint; v_jobmon_schema text; v_last_partition_created boolean; v_max bigint; v_notnull boolean; v_old_search_path text; v_parent_partition_id bigint; v_parent_partition_timestamp timestamp; v_partition_time timestamp; v_partition_time_array timestamp[]; v_partition_id_array bigint[]; v_row record; v_run_maint boolean; v_sql text; v_start_time timestamp; v_starting_partition_id bigint; v_step_id bigint; v_step_overflow_id bigint; v_sub_parent text; v_success boolean := false; v_tablename text; v_time_interval interval; v_time_position int; v_top_datetime_string text; v_top_parent text := p_parent_table; BEGIN IF position('.' in p_parent_table) = 0 THEN RAISE EXCEPTION 'Parent table must be schema qualified'; END IF; SELECT tablename INTO v_tablename FROM pg_tables WHERE schemaname || '.' || tablename = p_parent_table; IF v_tablename IS NULL THEN RAISE EXCEPTION 'Please create given parent table first: %', p_parent_table; END IF; SELECT attnotnull INTO v_notnull FROM pg_attribute WHERE attrelid = p_parent_table::regclass AND attname = p_control; IF v_notnull = false OR v_notnull IS NULL THEN RAISE EXCEPTION 'Control column (%) for parent table (%) must be NOT NULL', p_control, p_parent_table; END IF; IF NOT @extschema@.check_partition_type(p_type) THEN RAISE EXCEPTION '% is not a valid partitioning type', p_type; END IF; EXECUTE 'LOCK TABLE '||p_parent_table||' IN ACCESS EXCLUSIVE MODE'; IF p_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF p_use_run_maintenance IS NOT NULL THEN IF p_use_run_maintenance IS FALSE AND (p_type = 'time' OR p_type = 'time-custom') THEN RAISE EXCEPTION 'p_run_maintenance cannot be set to false for time based partitioning'; END IF; v_run_maint := p_use_run_maintenance; ELSIF p_type = 'time' OR p_type = 'time-custom' THEN v_run_maint := TRUE; ELSIF p_type = 'id' THEN v_run_maint := FALSE; ELSE RAISE EXCEPTION 'use_run_maintenance value cannot be set NULL'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN SETUP PARENT: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating initial partitions on new parent table: '||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_inherits h where h.inhrelid::regclass = p_parent_table::regclass ), sibling_children as ( select i.inhrelid::regclass::text as tablename from pg_inherits i join parent_table p on i.inhparent = p.parent_oid ) 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_use_run_maintenance , sub_jobmon 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_use_run_maintenance , sub_jobmon) 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_use_run_maintenance , v_row.sub_jobmon); END LOOP; IF p_type = 'time' OR p_type = 'time-custom' 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 <> 'time-custom' THEN RAISE EXCEPTION 'Must use a predefined time interval if not using type "time-custom". See documentation.'; 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::timestamp, 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))::timestamp; 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 , control , premake , constraint_cols , datetime_string , use_run_maintenance , inherit_fk , jobmon) VALUES ( p_parent_table , p_type , v_time_interval , p_control , p_premake , p_constraint_cols , v_datetime_string , v_run_maint , p_inherit_fk , p_jobmon); 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 n.nspname||'.'||c.relname = p_parent_table ) SELECT n.nspname||'.'||c.relname, p.datetime_string INTO v_top_parent, v_top_datetime_string 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 IS NOT NULL THEN -- If so create the lowest possible partition that is within the boundary of the parent v_time_position := (length(p_parent_table) - position('p_' in reverse(p_parent_table))) + 2; v_parent_partition_timestamp := to_timestamp(substring(p_parent_table from v_time_position), v_top_datetime_string); 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 -- Currently unknown edge case if code gets here RAISE EXCEPTION 'No child tables created. Unexpected edge case encountered. Please report this error to author with conditions that led to it.'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Time partitions premade: '||p_premake); END IF; END IF; IF p_type = 'id' THEN v_id_interval := p_interval::bigint; IF v_id_interval < 10 THEN RAISE EXCEPTION 'Interval for serial 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 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||'.'||c.relname = v_higher_parent ) SELECT n.nspname||'.'||c.relname INTO v_higher_parent 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 WHERE p.partition_type = 'id'; IF v_higher_parent IS NOT NULL THEN -- v_top_parent initially set in DECLARE v_top_parent := v_higher_parent; 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 v_sql := 'SELECT COALESCE('||quote_nullable(p_start_partition::bigint)||', max('||p_control||')::bigint, 0) FROM '||v_top_parent||' LIMIT 1'; EXECUTE v_sql INTO v_max; 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 , use_run_maintenance , inherit_fk , jobmon) VALUES ( p_parent_table , p_type , v_id_interval , p_control , p_premake , p_constraint_cols , v_run_maint , p_inherit_fk , p_jobmon); 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 n.nspname||'.'||c.relname = p_parent_table ) SELECT n.nspname||'.'||c.relname INTO v_top_parent 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 WHERE p.partition_type = 'id'; IF v_top_parent IS NOT NULL THEN -- Create the lowest possible partition that is within the boundary of the parent v_id_position := (length(p_parent_table) - position('p_' in reverse(p_parent_table))) + 2; v_parent_partition_id = substring(p_parent_table from v_id_position)::bigint; 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 EXCEPTION 'No child tables created. Unexpected edge case encountered. Please report this error to author with conditions that led to it.'; END IF; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating partition function'); END IF; IF p_type = 'time' OR p_type = 'time-custom' THEN PERFORM @extschema@.create_function_time(p_parent_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Time function created'); END IF; ELSIF p_type = 'id' THEN PERFORM @extschema@.create_function_id(p_parent_table); 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); 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; EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; 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 $$; /* * Create the trigger function for the parent table of an id-based partition set */ CREATE OR REPLACE FUNCTION create_function_id(p_parent_table text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_control text; v_count int; v_current_partition_name text; v_current_partition_id bigint; v_datetime_string text; v_final_partition_id bigint; v_function_name text; v_higher_parent text := p_parent_table; v_id_position int; v_job_id bigint; v_jobmon text; v_jobmon_schema text; v_last_partition text; v_max bigint; v_next_partition_id bigint; v_next_partition_name text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_premake int; v_prev_partition_id bigint; v_prev_partition_name text; v_row_max_id record; v_run_maint boolean; v_step_id bigint; v_top_parent text := p_parent_table; v_trig_func text; BEGIN SELECT partition_interval::bigint , control , premake , use_run_maintenance , jobmon INTO v_partition_interval , v_control , v_premake , v_run_maint , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT show_partitions INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE FUNCTION: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating partition function for table '||p_parent_table); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_function_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, '_part_trig_func', FALSE); -- Get the highest level top parent if multi-level partitioned in order to get proper max() value below WHILE v_higher_parent 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||'.'||c.relname = v_higher_parent ) SELECT n.nspname||'.'||c.relname INTO v_higher_parent 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 WHERE p.partition_type = 'id'; IF v_higher_parent IS NOT NULL THEN -- initially set in DECLARE v_top_parent := v_higher_parent; END IF; END LOOP; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT show_partitions FROM @extschema@.show_partitions(v_top_parent, 'DESC') LOOP EXECUTE 'SELECT max('||v_control||') FROM '||v_row_max_id.show_partitions INTO v_max; IF v_max IS NOT NULL THEN EXIT; END IF; END LOOP; IF v_max IS NULL THEN v_max := 0; END IF; v_current_partition_id = v_max - (v_max % v_partition_interval); v_next_partition_id := v_current_partition_id + v_partition_interval; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_current_partition_id::text, TRUE); v_trig_func := 'CREATE OR REPLACE FUNCTION '||v_function_name||'() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_current_partition_id bigint; v_current_partition_name text; v_id_position int; v_last_partition text := '||quote_literal(v_last_partition)||'; v_next_partition_id bigint; v_next_partition_name text; v_partition_created boolean; BEGIN IF TG_OP = ''INSERT'' THEN IF NEW.'||v_control||' >= '||v_current_partition_id||' AND NEW.'||v_control||' < '||v_next_partition_id|| ' THEN '; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_current_partition_name; IF v_count > 0 THEN v_trig_func := v_trig_func || ' INSERT INTO '||v_current_partition_name||' VALUES (NEW.*); '; ELSE v_trig_func := v_trig_func || ' -- Child table for current values does not exist in this partition set, so write to parent RETURN NEW;'; END IF; FOR i IN 1..v_premake LOOP v_prev_partition_id := v_current_partition_id - (v_partition_interval * i); v_next_partition_id := v_current_partition_id + (v_partition_interval * i); v_final_partition_id := v_next_partition_id + v_partition_interval; v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_prev_partition_id::text, TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_next_partition_id::text, TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles edge case of changing premake immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_prev_partition_name; IF v_count > 0 THEN -- Only handle previous partitions if they're starting above zero IF v_prev_partition_id >= 0 THEN v_trig_func := v_trig_func ||' ELSIF NEW.'||v_control||' >= '||v_prev_partition_id||' AND NEW.'||v_control||' < '||v_prev_partition_id + v_partition_interval|| ' THEN INSERT INTO '||v_prev_partition_name||' VALUES (NEW.*); '; END IF; END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_next_partition_name; IF v_count > 0 THEN v_trig_func := v_trig_func ||' ELSIF NEW.'||v_control||' >= '||v_next_partition_id||' AND NEW.'||v_control||' < '||v_final_partition_id|| ' THEN INSERT INTO '||v_next_partition_name||' VALUES (NEW.*);'; END IF; END LOOP; v_trig_func := v_trig_func ||' ELSE v_current_partition_id := NEW.'||v_control||' - (NEW.'||v_control||' % '||v_partition_interval||'); v_current_partition_name := @extschema@.check_name_length('''||v_parent_tablename||''', '''||v_parent_schema||''', v_current_partition_id::text, TRUE); SELECT count(*) INTO v_count FROM pg_tables WHERE schemaname ||''.''|| tablename = v_current_partition_name; IF v_count > 0 THEN EXECUTE ''INSERT INTO ''||v_current_partition_name||'' VALUES($1.*)'' USING NEW; ELSE RETURN NEW; END IF; END IF;'; IF v_run_maint IS FALSE THEN v_trig_func := v_trig_func ||' v_current_partition_id := NEW.'||v_control||' - (NEW.'||v_control||' % '||v_partition_interval||'); IF (NEW.'||v_control||' % '||v_partition_interval||') > ('||v_partition_interval||' / 2) THEN v_id_position := (length(v_last_partition) - position(''p_'' in reverse(v_last_partition))) + 2; v_next_partition_id := (substring(v_last_partition from v_id_position)::bigint) + '||v_partition_interval||'; WHILE ((v_next_partition_id - v_current_partition_id) / '||v_partition_interval||') <= '||v_premake||' LOOP v_partition_created := @extschema@.create_partition_id('||quote_literal(p_parent_table)||', ARRAY[v_next_partition_id]); IF v_partition_created THEN PERFORM @extschema@.create_function_id('||quote_literal(p_parent_table)||'); PERFORM @extschema@.apply_constraints('||quote_literal(p_parent_table)||'); END IF; v_next_partition_id := v_next_partition_id + '||v_partition_interval||'; END LOOP; END IF;'; END IF; v_trig_func := v_trig_func ||' END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for current id interval: '||v_current_partition_id||' to '||v_final_partition_id-1); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; 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 FUNCTION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''Partition function maintenance for table %s 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 $$; /* * Create the trigger function for the parent table of a time-based partition set */ CREATE OR REPLACE FUNCTION create_function_time(p_parent_table text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_control text; v_count int; v_current_partition_name text; v_current_partition_timestamp timestamptz; v_datetime_string text; v_final_partition_timestamp timestamptz; v_function_name text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_new_length int; v_next_partition_name text; v_next_partition_timestamp timestamptz; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_premake int; v_prev_partition_name text; v_prev_partition_timestamp timestamptz; v_step_id bigint; v_trig_func text; v_type text; BEGIN SELECT partition_type , partition_interval::interval , control , premake , datetime_string , jobmon INTO v_type , v_partition_interval , v_control , v_premake , v_datetime_string , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE FUNCTION: '||p_parent_table); v_step_id := add_step(v_job_id, 'Creating partition function for table '||p_parent_table); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_function_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, '_part_trig_func', FALSE); IF v_type = 'time' THEN v_trig_func := 'CREATE OR REPLACE FUNCTION '||v_function_name||'() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_partition_name text; v_partition_timestamp timestamptz; BEGIN IF TG_OP = ''INSERT'' THEN '; CASE WHEN v_partition_interval = '15 mins' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''hour'', NEW.'||v_control||') + ''15min''::interval * floor(date_part(''minute'', NEW.'||v_control||') / 15.0);'; v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''hour'', NEW.'||v_control||') + ''30min''::interval * floor(date_part(''minute'', NEW.'||v_control||') / 30.0);'; v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''hour'', NEW.'||v_control||');'; v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 day' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''day'', NEW.'||v_control||');'; v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 week' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''week'', NEW.'||v_control||');'; v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 month' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''month'', NEW.'||v_control||');'; v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_partition_interval = '3 months' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''quarter'', NEW.'||v_control||');'; v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 year' THEN v_trig_func := v_trig_func||'v_partition_timestamp := date_trunc(''year'', NEW.'||v_control||');'; v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, to_char(v_current_partition_timestamp, v_datetime_string), TRUE); v_next_partition_timestamp := v_current_partition_timestamp + v_partition_interval::interval; v_trig_func := v_trig_func ||' IF NEW.'||v_control||' >= '||quote_literal(v_current_partition_timestamp)||' AND NEW.'||v_control||' < '||quote_literal(v_next_partition_timestamp)|| ' THEN '; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_current_partition_name; IF v_count > 0 THEN v_trig_func := v_trig_func || ' INSERT INTO '||v_current_partition_name||' VALUES (NEW.*); '; ELSE v_trig_func := v_trig_func || ' -- Child table for current values does not exist in this partition set, so write to parent RETURN NEW;'; END IF; FOR i IN 1..v_premake LOOP v_prev_partition_timestamp := v_current_partition_timestamp - (v_partition_interval::interval * i); v_next_partition_timestamp := v_current_partition_timestamp + (v_partition_interval::interval * i); v_final_partition_timestamp := v_next_partition_timestamp + (v_partition_interval::interval); v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, to_char(v_prev_partition_timestamp, v_datetime_string), TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, to_char(v_next_partition_timestamp, v_datetime_string), TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles edge case of changing premake immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_prev_partition_name; IF v_count > 0 THEN v_trig_func := v_trig_func ||' ELSIF NEW.'||v_control||' >= '||quote_literal(v_prev_partition_timestamp)||' AND NEW.'||v_control||' < '|| quote_literal(v_prev_partition_timestamp + v_partition_interval::interval)|| ' THEN INSERT INTO '||v_prev_partition_name||' VALUES (NEW.*);'; END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_next_partition_name; IF v_count > 0 THEN v_trig_func := v_trig_func ||' ELSIF NEW.'||v_control||' >= '||quote_literal(v_next_partition_timestamp)||' AND NEW.'||v_control||' < '|| quote_literal(v_final_partition_timestamp)|| ' THEN INSERT INTO '||v_next_partition_name||' VALUES (NEW.*);'; END IF; END LOOP; v_trig_func := v_trig_func||' ELSE v_partition_name := @extschema@.check_name_length('''||v_parent_tablename||''', '''||v_parent_schema||''', to_char(v_partition_timestamp, '||quote_literal(v_datetime_string)||'), TRUE); SELECT count(*) INTO v_count FROM pg_tables WHERE schemaname ||''.''|| tablename = v_partition_name; IF v_count > 0 THEN EXECUTE ''INSERT INTO ''||v_partition_name||'' VALUES($1.*)'' USING NEW; ELSE RETURN NEW; END IF; END IF;'; v_trig_func := v_trig_func ||' END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for current time interval: '|| v_current_partition_timestamp||' to '||(v_final_partition_timestamp-'1sec'::interval)); END IF; ELSIF v_type = 'time-custom' THEN v_trig_func := 'CREATE OR REPLACE FUNCTION '||v_function_name||'() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_child_table text; v_count int; BEGIN SELECT child_table INTO v_child_table FROM @extschema@.custom_time_partitions WHERE partition_range @> NEW.'||v_control||' AND parent_table = '||quote_literal(p_parent_table)||'; SELECT count(*) INTO v_count FROM pg_tables WHERE schemaname ||''.''|| tablename = v_child_table; IF v_count > 0 THEN EXECUTE ''INSERT INTO ''||v_child_table||'' VALUES ($1.*)'' USING NEW; ELSE RETURN NEW; END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Added function for custom time table: '||p_parent_table); END IF; ELSE RAISE EXCEPTION 'ERROR: Invalid time partitioning type given: %', v_type; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; 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 FUNCTION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''Partition function maintenance for table %s 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 $$; /* * Function to create id partitions */ CREATE OR REPLACE FUNCTION create_partition_id(p_parent_table text, p_partition_ids bigint[], p_analyze boolean DEFAULT true) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_grantees text[]; v_hasoids boolean; v_id bigint; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_parent_tablespace text; v_partition_interval bigint; v_partition_created boolean := false; v_partition_name text; v_revoke text[]; v_row record; v_sql text; v_step_id bigint; v_sub_id_max bigint; v_sub_id_min bigint; v_tablename text; v_unlogged char; BEGIN SELECT control , partition_interval , inherit_fk , jobmon INTO v_control , v_partition_interval , v_inherit_fk , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'id'); SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); END IF; FOREACH v_id IN ARRAY p_partition_ids LOOP -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_id_min IS NOT NULL THEN IF v_id < v_sub_id_min OR v_id > v_sub_id_max THEN CONTINUE; END IF; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_id::text, TRUE); -- If child table already exists, skip creation SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_tablename IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_id||' to '||(v_id + v_partition_interval)-1); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class WHERE oid::regclass = p_parent_table::regclass; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; v_sql := v_sql || ' TABLE '||v_partition_name||' (LIKE '||p_parent_table||' INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)'; SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class WHERE oid::regclass = p_parent_table::regclass; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_parent_tablespace IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_partition_name||' SET TABLESPACE '||v_parent_tablespace; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' ADD CONSTRAINT '||v_tablename||'_partition_check CHECK ('||v_control||'>='||quote_literal(v_id)||' AND '||v_control||'<'||quote_literal(v_id + v_partition_interval)||')'; EXECUTE 'ALTER TABLE '||v_partition_name||' INHERIT '||p_parent_table; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP EXECUTE 'GRANT '||array_to_string(v_parent_grant.types, ',')||' ON '||v_partition_name||' TO '||v_parent_grant.grantee; SELECT array_agg(r) INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE '||array_to_string(v_revoke, ',')||' ON '||v_partition_name||' FROM '||v_parent_grant.grantee||' CASCADE'; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN SELECT array_agg(r) INTO v_revoke FROM ( SELECT DISTINCT grantee::text AS r FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_partition_name EXCEPT SELECT unnest(v_grantees)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE ALL ON '||v_partition_name||' FROM '||array_to_string(v_revoke, ','); END IF; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' OWNER TO '||v_parent_owner; IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(quote_ident(v_parent_schema)||'.'||quote_ident(v_parent_tablename), v_partition_name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_control , sub_partition_type , 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_use_run_maintenance , sub_jobmon FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Subpartitioning '||v_partition_name); END IF; 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_use_run_maintenance := %L , p_inherit_fk := %L , p_jobmon := %L )' , v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_use_run_maintenance , v_row.sub_inherit_fk , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index WHERE parent_table = v_partition_name; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; -- end sub partitioning LOOP v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Analyzing partition set: '||p_parent_table); END IF; EXECUTE 'ANALYZE '||p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, 'No partitions created for partition set: '||p_parent_table); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; PERFORM close_job(v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to create a child table in a time-based partition set */ CREATE OR REPLACE FUNCTION create_partition_time (p_parent_table text, p_partition_times timestamp[], p_analyze boolean DEFAULT true) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_grantees text[]; v_hasoids boolean; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_partition_created boolean := false; v_partition_name text; v_partition_suffix text; v_parent_tablespace text; v_partition_interval interval; v_partition_timestamp_end timestamp; v_partition_timestamp_start timestamp; v_quarter text; v_revoke text[]; v_row record; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_sub_timestamp_max timestamp; v_sub_timestamp_min timestamp; v_tablename text; v_trunc_value text; v_time timestamp; v_type text; v_unlogged char; v_year text; BEGIN SELECT partition_type , control , partition_interval , inherit_fk , jobmon , datetime_string INTO v_type , v_control , v_partition_interval , v_inherit_fk , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'time' OR partition_type = 'time-custom'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::timestamp, sub_max::timestamp INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'time'); SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE TABLE: '||p_parent_table); END IF; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_timestamp_start := v_time; BEGIN v_partition_timestamp_end := v_time + v_partition_interval; 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_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_time||' skipped'); CONTINUE; END; -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_timestamp_min IS NOT NULL THEN IF v_time < v_sub_timestamp_min OR v_time > v_sub_timestamp_max THEN CONTINUE; END IF; END IF; -- This suffix generation code is in partition_data_time() as well v_partition_suffix := to_char(v_time, 'YYYY'); IF v_partition_interval < '1 year' AND v_partition_interval <> '1 week' THEN v_partition_suffix := v_partition_suffix ||'_'|| to_char(v_time, 'MM'); IF v_partition_interval < '1 month' AND v_partition_interval <> '1 week' THEN v_partition_suffix := v_partition_suffix ||'_'|| to_char(v_time, 'DD'); IF v_partition_interval < '1 day' THEN v_partition_suffix := v_partition_suffix || '_' || to_char(v_time, 'HH24MI'); IF v_partition_interval < '1 minute' THEN v_partition_suffix := v_partition_suffix || to_char(v_time, 'SS'); END IF; -- end < minute IF END IF; -- end < day IF END IF; -- end < month IF END IF; -- end < year IF IF v_partition_interval = '1 week' THEN v_partition_suffix := to_char(v_time, 'IYYY') || 'w' || to_char(v_time, 'IW'); END IF; -- "Q" is ignored in to_timestamp, so handle special case IF v_partition_interval = '3 months' AND v_type = 'time' THEN v_year := to_char(v_time, 'YYYY'); v_quarter := to_char(v_time, 'Q'); v_partition_suffix := v_year || 'q' || v_quarter; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_partition_suffix, TRUE); SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_tablename IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_partition_timestamp_start||' to '||(v_partition_timestamp_end-'1sec'::interval)); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class WHERE oid::regclass = p_parent_table::regclass; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; v_sql := v_sql || ' TABLE '||v_partition_name||' (LIKE '||p_parent_table||' INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)'; SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class WHERE oid::regclass = p_parent_table::regclass; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; SELECT tablename INTO v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_partition_name; IF v_parent_tablespace IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_partition_name||' SET TABLESPACE '||v_parent_tablespace; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' ADD CONSTRAINT '||v_tablename||'_partition_check CHECK ('||v_control||'>='||quote_literal(v_partition_timestamp_start)||' AND '||v_control||'<'||quote_literal(v_partition_timestamp_end)||')'; EXECUTE 'ALTER TABLE '||v_partition_name||' INHERIT '||p_parent_table; -- If custom time, set extra config options. IF v_type = 'time-custom' THEN INSERT INTO @extschema@.custom_time_partitions (parent_table, child_table, partition_range) VALUES ( p_parent_table, v_partition_name, tstzrange(v_partition_timestamp_start, v_partition_timestamp_end, '[)') ); END IF; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP EXECUTE 'GRANT '||array_to_string(v_parent_grant.types, ',')||' ON '||v_partition_name||' TO '||v_parent_grant.grantee; SELECT array_agg(r) INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE '||array_to_string(v_revoke, ',')||' ON '||v_partition_name||' FROM '||v_parent_grant.grantee||' CASCADE'; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN SELECT array_agg(r) INTO v_revoke FROM ( SELECT DISTINCT grantee::text AS r FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_partition_name EXCEPT SELECT unnest(v_grantees)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE ALL ON '||v_partition_name||' FROM '||array_to_string(v_revoke, ','); END IF; END IF; EXECUTE 'ALTER TABLE '||v_partition_name||' OWNER TO '||v_parent_owner; IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(quote_ident(v_parent_schema)||'.'||quote_ident(v_parent_tablename), v_partition_name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_control , sub_partition_type , 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_use_run_maintenance , sub_jobmon FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Subpartitioning '||v_partition_name); END IF; 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_use_run_maintenance := %L , p_inherit_fk := %L , p_jobmon := %L )' , v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_use_run_maintenance , v_row.sub_inherit_fk , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index WHERE parent_table = v_partition_name; END LOOP; -- end sub partitioning LOOP v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Analyzing partition set: '||p_parent_table); END IF; EXECUTE 'ANALYZE '||p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, 'No partitions created for partition set: '||p_parent_table); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; 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; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Apply constraints managed by partman extension */ CREATE OR REPLACE FUNCTION apply_constraints(p_parent_table text, p_child_table text DEFAULT NULL, p_analyze boolean DEFAULT FALSE, p_debug boolean DEFAULT FALSE) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_child_table text; v_child_tablename text; v_col text; v_constraint_cols text[]; v_constraint_col_type text; v_constraint_name text; v_datetime_string text; v_existing_constraint_name text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_id int; v_last_partition_timestamp timestamp; v_constraint_values record; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval text; v_partition_suffix text; v_premake int; v_sql text; v_step_id bigint; v_suffix_position int; v_type text; BEGIN SELECT partition_type , partition_interval , premake , datetime_string , constraint_cols , jobmon INTO v_type , v_partition_interval , v_premake , v_datetime_string , v_constraint_cols , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_constraint_cols IS NULL THEN IF p_debug THEN RAISE NOTICE 'Given parent table (%) not set up for constraint management (constraint_cols is NULL)', p_parent_table; END IF; -- Returns silently to allow this function to be simply called by maintenance processes without having to check if config options are set. RETURN; END IF; SELECT show_partitions INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN CREATE CONSTRAINT: '||p_parent_table); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; -- If p_child_table is null, figure out the partition that is the one right before the premake value backwards. IF p_child_table IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Automatically determining most recent child on which to apply constraints'); END IF; v_suffix_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; IF v_type IN ('time', 'time-custom') THEN v_last_partition_timestamp := to_timestamp(substring(v_last_partition from v_suffix_position), v_datetime_string); v_partition_suffix := to_char(v_last_partition_timestamp - (v_partition_interval::interval * ((v_premake * 2)+1) ), v_datetime_string); ELSIF v_type = 'id' THEN v_last_partition_id := substring(v_last_partition from v_suffix_position)::int; v_partition_suffix := (v_last_partition_id - (v_partition_interval::int * ((v_premake * 2)+1) ))::text; END IF; v_child_table := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_partition_suffix, TRUE); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Target child table: '||v_child_table); END IF; ELSE v_child_table := p_child_table; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Checking if target child table exists'); END IF; SELECT tablename INTO v_child_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_child_table; IF v_child_tablename IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', 'Target child table ('||v_child_table||') does not exist. Skipping constraint creation.'); PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; IF p_debug THEN RAISE NOTICE 'Target child table (%) does not exist. Skipping constraint creation.', v_child_table; END IF; RETURN; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; FOREACH v_col IN ARRAY v_constraint_cols LOOP SELECT c.conname INTO v_existing_constraint_name FROM pg_catalog.pg_constraint c JOIN pg_catalog.pg_attribute a ON c.conrelid = a.attrelid WHERE conrelid = v_child_table::regclass AND c.conname LIKE 'partmanconstr_%' AND c.contype = 'c' AND a.attname = v_col AND ARRAY[a.attnum] <@ c.conkey AND a.attisdropped = false; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying new constraint on column: '||v_col); END IF; IF v_existing_constraint_name IS NOT NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', 'Partman managed constraint already exists on this table ('||v_child_table||') and column ('||v_col||'). Skipping creation.'); END IF; RAISE WARNING 'Partman managed constraint already exists on this table (%) and column (%). Skipping creation.', v_child_table, v_col ; CONTINUE; END IF; -- Ensure column name gets put on end of constraint name to help avoid naming conflicts v_constraint_name := @extschema@.check_name_length('partmanconstr_'||v_child_tablename, p_suffix := '_'||v_col); EXECUTE 'SELECT min('||v_col||')::text AS min, max('||v_col||')::text AS max FROM '||v_child_table INTO v_constraint_values; IF v_constraint_values IS NOT NULL THEN v_sql := concat('ALTER TABLE ', v_child_table, ' ADD CONSTRAINT ', v_constraint_name , ' CHECK (', v_col, ' >= ', quote_literal(v_constraint_values.min), ' AND ' , v_col, ' <= ', quote_literal(v_constraint_values.max), ')' ); IF p_debug THEN RAISE NOTICE 'Constraint creation query: %', v_sql; END IF; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'New constraint created: '||v_sql); END IF; ELSE IF p_debug THEN RAISE NOTICE 'Given column (%) contains all NULLs. No constraint created', v_col; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', 'Given column ('||v_col||') contains all NULLs. No constraint created'); END IF; END IF; END LOOP; IF p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Running analyze on partition set: '||p_parent_table); END IF; IF p_debug THEN RAISE NOTICE 'Running analyze on partition set: %', p_parent_table; END IF; EXECUTE 'ANALYZE '||p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; 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 CONSTRAINT: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Apply foreign keys that exist on the given parent to the given child table */ CREATE OR REPLACE FUNCTION apply_foreign_keys(p_parent_table text, p_child_table text, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_job_id bigint; v_jobmon text; v_jobmon_schema text; v_old_search_path text; v_ref_schema text; v_ref_table text; v_row record; v_schemaname text; v_sql text; v_step_id bigint; v_tablename text; BEGIN SELECT jobmon INTO v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN APPLYING FOREIGN KEYS: '||p_parent_table); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Checking if target child table exists'); END IF; SELECT schemaname, tablename INTO v_schemaname, v_tablename FROM pg_catalog.pg_tables WHERE schemaname||'.'||tablename = p_child_table; IF v_tablename IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'CRITICAL', 'Target child table ('||v_child_table||') does not exist.'); PERFORM fail_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RAISE EXCEPTION 'Target child table (%.%) does not exist.', v_schemaname, v_tablename; RETURN; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; FOR v_row IN SELECT n.nspname||'.'||cl.relname AS ref_table , '"'||string_agg(att.attname, '","')||'"' AS ref_column , '"'||string_agg(att2.attname, '","')||'"' AS child_column , keys.condeferred , keys.condeferrable , keys.confupdtype , keys.confdeltype , keys.confmatchtype FROM ( SELECT unnest(con.conkey) as ref , unnest(con.confkey) as child , con.confrelid , con.conrelid , con.condeferred , con.condeferrable , con.confupdtype , con.confdeltype , con.confmatchtype FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN pg_catalog.pg_constraint con ON c.oid = con.conrelid WHERE n.nspname ||'.'|| c.relname = p_parent_table AND con.contype = 'f' ORDER BY con.conkey ) keys JOIN pg_catalog.pg_class cl ON cl.oid = keys.confrelid JOIN pg_catalog.pg_namespace n ON cl.relnamespace = n.oid JOIN pg_catalog.pg_attribute att ON att.attrelid = keys.confrelid AND att.attnum = keys.child JOIN pg_catalog.pg_attribute att2 ON att2.attrelid = keys.conrelid AND att2.attnum = keys.ref GROUP BY n.nspname, cl.relname, keys.condeferred, keys.condeferrable, keys.confupdtype, keys.confdeltype, keys.confmatchtype LOOP SELECT schemaname, tablename INTO v_ref_schema, v_ref_table FROM pg_tables WHERE schemaname||'.'||tablename = v_row.ref_table; v_sql := format('ALTER TABLE %I.%I ADD FOREIGN KEY (%s) REFERENCES %I.%I (%s)', v_schemaname, v_tablename, v_row.child_column, v_ref_schema, v_ref_table, v_row.ref_column); CASE WHEN v_row.confmatchtype = 'f' THEN v_sql := v_sql || ' MATCH FULL '; WHEN v_row.confmatchtype = 's' THEN v_sql := v_sql || ' MATCH SIMPLE '; WHEN v_row.confmatchtype = 'p' THEN v_sql := v_sql || ' MATCH PARTIAL '; END CASE; CASE WHEN v_row.confupdtype = 'a' THEN v_sql := v_sql || ' ON UPDATE NO ACTION '; WHEN v_row.confupdtype = 'r' THEN v_sql := v_sql || ' ON UPDATE RESTRICT '; WHEN v_row.confupdtype = 'c' THEN v_sql := v_sql || ' ON UPDATE CASCADE '; WHEN v_row.confupdtype = 'n' THEN v_sql := v_sql || ' ON UPDATE SET NULL '; WHEN v_row.confupdtype = 'd' THEN v_sql := v_sql || ' ON UPDATE SET DEFAULT '; END CASE; CASE WHEN v_row.confdeltype = 'a' THEN v_sql := v_sql || ' ON DELETE NO ACTION '; WHEN v_row.confdeltype = 'r' THEN v_sql := v_sql || ' ON DELETE RESTRICT '; WHEN v_row.confdeltype = 'c' THEN v_sql := v_sql || ' ON DELETE CASCADE '; WHEN v_row.confdeltype = 'n' THEN v_sql := v_sql || ' ON DELETE SET NULL '; WHEN v_row.confdeltype = 'd' THEN v_sql := v_sql || ' ON DELETE SET DEFAULT '; END CASE; CASE WHEN v_row.condeferrable = true AND v_row.condeferred = true THEN v_sql := v_sql || ' DEFERRABLE INITIALLY DEFERRED '; WHEN v_row.condeferrable = false AND v_row.condeferred = false THEN v_sql := v_sql || ' NOT DEFERRABLE '; WHEN v_row.condeferrable = true AND v_row.condeferred = false THEN v_sql := v_sql || ' DEFERRABLE INITIALLY IMMEDIATE '; END CASE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying FK: '||v_sql); END IF; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'FK applied'); END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; 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 APPLYING FOREIGN KEYS: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to monitor for data getting inserted into parent tables managed by extension */ CREATE OR REPLACE FUNCTION check_parent() RETURNS SETOF @extschema@.check_parent_table LANGUAGE plpgsql STABLE SECURITY DEFINER AS $$ DECLARE v_count bigint = 0; v_sql text; v_tables record; v_trouble @extschema@.check_parent_table%rowtype; BEGIN FOR v_tables IN SELECT DISTINCT parent_table FROM @extschema@.part_config LOOP v_sql := 'SELECT count(1) AS n FROM ONLY '||v_tables.parent_table; EXECUTE v_sql INTO v_count; IF v_count > 0 THEN v_trouble.parent_table := v_tables.parent_table; v_trouble.count := v_count; RETURN NEXT v_trouble; END IF; v_count := 0; END LOOP; RETURN; END $$; /* * 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. * To avoid logical complications and contention issues, ALL subpartitions must be maintained using run_maintenance(). * This means the automatic, trigger based partition creation for serial partitioning will not work if it is a subpartition. */ CREATE OR REPLACE FUNCTION create_sub_parent( p_top_parent text , p_control text , p_type text , p_interval text , p_constraint_cols text[] DEFAULT NULL , p_premake int DEFAULT 4 , p_start_partition text DEFAULT NULL , p_inherit_fk boolean DEFAULT true , p_jobmon boolean DEFAULT true , p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_last_partition text; v_row record; v_row_last_part record; v_run_maint boolean; v_sql text; v_success boolean := false; v_top_type text; BEGIN SELECT use_run_maintenance INTO v_run_maint FROM @extschema@.part_config WHERE parent_table = p_top_parent; IF v_run_maint 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; ELSIF v_run_maint = false THEN RAISE EXCEPTION 'Any parent table that will be part of a sub-partitioned set (on any level) must have use_run_maintenance set to true in part_config table, even for serial partitioning. See documentation for more info.'; END IF; FOR v_row IN -- Loop through all current children to turn them into partitioned tables SELECT show_partitions AS child_table FROM @extschema@.show_partitions(p_top_parent) LOOP -- Just call existing create_parent() function but add the given parameters to the part_config_sub table as well 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_use_run_maintenance := %L , p_start_partition := %L , p_inherit_fk := %L , p_jobmon := %L , p_debug := %L )' , v_row.child_table , p_control , p_type , p_interval , p_constraint_cols , p_premake , true , p_start_partition , p_inherit_fk , p_jobmon , p_debug); EXECUTE v_sql; END LOOP; 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_use_run_maintenance , sub_jobmon) VALUES ( p_top_parent , p_control , p_type , p_interval , p_constraint_cols , p_premake , p_inherit_fk , true , p_jobmon); v_success := true; RETURN v_success; END $$; /* * Drop constraints managed by pg_partman */ CREATE OR REPLACE FUNCTION drop_constraints(p_parent_table text, p_child_table text, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_col text; v_constraint_cols text[]; v_existing_constraint_name text; v_exists boolean := FALSE; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_sql text; v_step_id bigint; BEGIN SELECT constraint_cols , jobmon INTO v_constraint_cols , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_constraint_cols IS NULL THEN RAISE EXCEPTION 'Given parent table (%) not set up for constraint management (constraint_cols is NULL)', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN DROP CONSTRAINT: '||p_parent_table); v_step_id := add_step(v_job_id, 'Entering constraint drop loop'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; FOREACH v_col IN ARRAY v_constraint_cols LOOP SELECT c.conname INTO v_existing_constraint_name FROM pg_catalog.pg_constraint c JOIN pg_catalog.pg_attribute a ON c.conrelid = a.attrelid WHERE conrelid = p_child_table::regclass AND c.conname LIKE 'partmanconstr_%' AND c.contype = 'c' AND a.attname = v_col AND ARRAY[a.attnum] <@ c.conkey AND a.attisdropped = false; IF v_existing_constraint_name IS NOT NULL THEN v_exists := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Dropping constraint on column: '||v_col); END IF; v_sql := 'ALTER TABLE '||p_child_table||' DROP CONSTRAINT '||v_existing_constraint_name; IF p_debug THEN RAISE NOTICE 'Constraint drop query: %', v_sql; END IF; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Drop constraint query: '||v_sql); END IF; END IF; END LOOP; IF v_jobmon_schema IS NOT NULL AND v_exists IS FALSE THEN v_step_id := add_step(v_job_id, 'No constraints found to drop on child table: '||p_child_table); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; 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 DROP CONSTRAINT: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to drop child tables from an id-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ CREATE OR REPLACE FUNCTION drop_partition_id(p_parent_table text, p_retention bigint DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_child_table text; v_control text; v_drop_count int := 0; v_id_position int; v_index record; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_max bigint; v_old_search_path text; v_partition_interval bigint; v_partition_id bigint; v_retention bigint; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_row_max_id record; v_step_id bigint; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman drop_partition_id')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_id already running.'; RETURN 0; END IF; -- Allow override of configuration options IF p_retention IS NULL THEN SELECT partition_interval::bigint , control , retention::bigint , retention_keep_table , retention_keep_index , retention_schema , jobmon INTO v_partition_interval , v_control , v_retention , v_retention_keep_table , v_retention_keep_index , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id' AND retention IS NOT NULL; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE SELECT partition_interval::bigint , control , retention_keep_table , retention_keep_index , retention_schema , jobmon INTO v_partition_interval , v_control , v_retention_keep_table , v_retention_keep_index , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; v_retention := p_retention; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT show_partitions FROM @extschema@.show_partitions(p_parent_table, 'DESC') LOOP EXECUTE 'SELECT max('||v_control||') FROM '||v_row_max_id.show_partitions INTO v_max; IF v_max IS NOT NULL THEN EXIT; END IF; END LOOP; -- Loop through child tables of the given parent FOR v_child_table IN SELECT n.nspname||'.'||c.relname FROM pg_inherits i join pg_class c ON i.inhrelid = c.oid join pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC LOOP v_id_position := (length(v_child_table) - position('p_' in reverse(v_child_table))) + 2; v_partition_id := substring(v_child_table from v_id_position)::bigint; -- Add one interval since partition names contain the start of the constraint period IF v_retention <= (v_max - (v_partition_id + v_partition_interval)) THEN -- Only create a jobmon entry if there's actual retention work done IF v_jobmon_schema IS NOT NULL AND v_job_id IS NULL THEN v_job_id := add_job('PARTMAN DROP ID PARTITION: '|| p_parent_table); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Uninherit table '||v_child_table||' from '||p_parent_table); END IF; EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Drop table '||v_child_table); END IF; EXECUTE 'DROP TABLE '||v_child_table||' CASCADE'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN FOR v_index IN SELECT i.indexrelid::regclass AS name , c.conname FROM pg_catalog.pg_index i LEFT JOIN pg_catalog.pg_constraint c ON i.indexrelid = c.conindid WHERE i.indrelid = v_child_table::regclass LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Drop index '||v_index.name||' from '||v_child_table); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_child_table||' DROP CONSTRAINT '||v_index.conname; ELSE EXECUTE 'DROP INDEX '||v_index.name; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Moving table '||v_child_table||' to schema '||v_retention_schema); END IF; EXECUTE 'ALTER TABLE '||v_child_table||' SET SCHEMA '||v_retention_schema; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if -- If child table is a subpartition, remove it from part_config & part_config_sub (should cascade due to FK) DELETE FROM @extschema@.part_config WHERE parent_table = v_child_table; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', v_drop_count||' partitions dropped.'); PERFORM close_job(v_job_id); END IF; EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_drop_count; 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 DROP ID PARTITION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to drop child tables from a time-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ CREATE OR REPLACE FUNCTION drop_partition_time(p_parent_table text, p_retention interval DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_child_table text; v_datetime_string text; v_drop_count int := 0; v_index record; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_partition_interval interval; v_partition_timestamp timestamp; v_quarter text; v_retention interval; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_step_id bigint; v_time_position int; v_type text; v_year text; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman drop_partition_time')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_time already running.'; RETURN 0; END IF; -- Allow override of configuration options IF p_retention IS NULL THEN SELECT partition_type , partition_interval::interval , retention::interval , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_type , v_partition_interval , v_retention , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom') AND retention IS NOT NULL; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE SELECT partition_type , partition_interval::interval , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_type , v_partition_interval , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); v_retention := p_retention; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; -- Loop through child tables of the given parent FOR v_child_table IN SELECT n.nspname||'.'||c.relname FROM pg_inherits i join pg_class c ON i.inhrelid = c.oid join pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC LOOP -- pull out datetime portion of partition's tablename to make the next one v_time_position := (length(v_child_table) - position('p_' in reverse(v_child_table))) + 2; IF v_partition_interval <> '3 months' OR (v_partition_interval = '3 months' AND v_type = 'time-custom') THEN v_partition_timestamp := to_timestamp(substring(v_child_table from v_time_position), v_datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(v_child_table from v_time_position), 'q', 1); v_quarter := split_part(substring(v_child_table from v_time_position), 'q', 2); CASE WHEN v_quarter = '1' THEN v_partition_timestamp := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_partition_timestamp := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_partition_timestamp := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_partition_timestamp := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; -- Add one interval since partition names contain the start of the constraint period IF v_retention < (CURRENT_TIMESTAMP - (v_partition_timestamp + v_partition_interval)) THEN -- Only create a jobmon entry if there's actual retention work done IF v_jobmon_schema IS NOT NULL AND v_job_id IS NULL THEN v_job_id := add_job('PARTMAN DROP TIME PARTITION: '|| p_parent_table); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Uninherit table '||v_child_table||' from '||p_parent_table); END IF; EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF v_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_child_table; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Drop table '||v_child_table); END IF; EXECUTE 'DROP TABLE '||v_child_table||' CASCADE'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN FOR v_index IN SELECT i.indexrelid::regclass AS name , c.conname FROM pg_catalog.pg_index i LEFT JOIN pg_catalog.pg_constraint c ON i.indexrelid = c.conindid WHERE i.indrelid = v_child_table::regclass LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Drop index '||v_index.name||' from '||v_child_table); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE 'ALTER TABLE '||v_child_table||' DROP CONSTRAINT '||v_index.conname; ELSE EXECUTE 'DROP INDEX '||v_index.name; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Moving table '||v_child_table||' to schema '||v_retention_schema); END IF; EXECUTE 'ALTER TABLE '||v_child_table||' SET SCHEMA '||v_retention_schema; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if -- If child table is a subpartition, remove it from part_config & part_config_sub (should cascade due to FK) DELETE FROM @extschema@.part_config WHERE parent_table = v_child_table; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', v_drop_count||' partitions dropped.'); PERFORM close_job(v_job_id); END IF; EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_drop_count; 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 DROP TIME PARTITION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Populate the child table(s) of an id-based partition set with old data from the original parent */ CREATE OR REPLACE FUNCTION partition_data_id(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval int DEFAULT NULL, p_lock_wait numeric DEFAULT 0, p_order text DEFAULT 'ASC') RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_current_partition_name text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_id bigint; v_min_partition_id bigint; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_partition_id bigint[]; v_rowcount bigint; v_sql text; v_start_control bigint; v_total_rows bigint := 0; BEGIN SELECT partition_interval::bigint , control INTO v_partition_interval , v_control FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; FOR i IN 1..p_batch_count LOOP IF p_order = 'ASC' THEN EXECUTE 'SELECT min('||v_control||') FROM ONLY '||p_parent_table INTO v_start_control; IF v_start_control IS NULL THEN EXIT; END IF; v_min_partition_id = v_start_control - (v_start_control % v_partition_interval); v_partition_id := ARRAY[v_min_partition_id]; -- Check if custom batch interval overflows current partition maximum IF (v_start_control + p_batch_interval) >= (v_min_partition_id + v_partition_interval) THEN v_max_partition_id := v_min_partition_id + v_partition_interval; ELSE v_max_partition_id := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN EXECUTE 'SELECT max('||v_control||') FROM ONLY '||p_parent_table INTO v_start_control; IF v_start_control IS NULL THEN EXIT; END IF; v_min_partition_id = v_start_control - (v_start_control % v_partition_interval); -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_id := v_min_partition_id + v_partition_interval; v_partition_id := ARRAY[v_min_partition_id]; -- Make sure minimum doesn't underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_id THEN v_min_partition_id = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN v_sql := 'SELECT * FROM ONLY ' || p_parent_table || ' WHERE '||v_control||' >= '||quote_literal(v_min_partition_id)|| ' AND '||v_control||' < '||quote_literal(v_max_partition_id) ||' FOR UPDATE NOWAIT'; EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; PERFORM @extschema@.create_partition_id(p_parent_table, v_partition_id); SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname||'.'||tablename = p_parent_table; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_min_partition_id::text, TRUE); EXECUTE 'WITH partition_data AS ( DELETE FROM ONLY '||p_parent_table||' WHERE '||v_control||' >= '||v_min_partition_id|| ' AND '||v_control||' < '||v_max_partition_id||' RETURNING *) INSERT INTO '||v_current_partition_name||' SELECT * FROM partition_data'; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; PERFORM @extschema@.create_function_id(p_parent_table); RETURN v_total_rows; END $$; /* * Populate the child table(s) of a time-based partition set with old data from the original parent */ CREATE OR REPLACE FUNCTION partition_data_time(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval interval DEFAULT NULL, p_lock_wait numeric DEFAULT 0, p_order text DEFAULT 'ASC') RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_datetime_string text; v_current_partition_name text; v_max_partition_timestamp timestamp; v_last_partition text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_min_partition_timestamp timestamp; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_partition_suffix text; v_partition_timestamp timestamp[]; v_quarter text; v_rowcount bigint; v_sql text; v_start_control timestamp; v_time_position int; v_total_rows bigint := 0; v_type text; v_year text; BEGIN SELECT partition_type , partition_interval::interval , control , datetime_string INTO v_type , v_partition_interval , v_control , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; SELECT show_partitions INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; FOR i IN 1..p_batch_count LOOP IF p_order = 'ASC' THEN EXECUTE 'SELECT min('||v_control||') FROM ONLY '||p_parent_table INTO v_start_control; ELSIF p_order = 'DESC' THEN EXECUTE 'SELECT max('||v_control||') FROM ONLY '||p_parent_table INTO v_start_control; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; IF v_start_control IS NULL THEN EXIT; END IF; IF v_type = 'time' THEN CASE WHEN v_partition_interval = '15 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '15min'::interval * floor(date_part('minute', v_start_control) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '30min'::interval * floor(date_part('minute', v_start_control) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control); WHEN v_partition_interval = '1 day' THEN v_min_partition_timestamp := date_trunc('day', v_start_control); WHEN v_partition_interval = '1 week' THEN v_min_partition_timestamp := date_trunc('week', v_start_control); WHEN v_partition_interval = '1 month' THEN v_min_partition_timestamp := date_trunc('month', v_start_control); WHEN v_partition_interval = '3 months' THEN v_min_partition_timestamp := date_trunc('quarter', v_start_control); WHEN v_partition_interval = '1 year' THEN v_min_partition_timestamp := date_trunc('year', v_start_control); END CASE; ELSIF v_type = 'time-custom' THEN -- Keep going backwards, checking if the time interval encompases the current v_start_control value v_time_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; v_min_partition_timestamp := to_timestamp(substring(v_last_partition from v_time_position), v_datetime_string); v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; LOOP IF v_start_control >= v_min_partition_timestamp AND v_start_control < v_max_partition_timestamp THEN EXIT; ELSE v_max_partition_timestamp := v_min_partition_timestamp; BEGIN v_min_partition_timestamp := v_min_partition_timestamp - v_partition_interval; EXCEPTION WHEN datetime_field_overflow THEN RAISE EXCEPTION 'Attempted partition time interval is outside PostgreSQL''s supported time range. Unable to create partition with interval before timestamp % ', v_min_partition_interval; END; END IF; END LOOP; END IF; v_partition_timestamp := ARRAY[v_min_partition_timestamp]; IF p_order = 'ASC' THEN IF (v_start_control + p_batch_interval) >= (v_min_partition_timestamp + v_partition_interval) THEN v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; ELSE v_max_partition_timestamp := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; -- Make sure minimum doesn't underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_timestamp THEN v_min_partition_timestamp = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN v_sql := 'SELECT * FROM ONLY ' || p_parent_table || ' WHERE '||v_control||' >= '||quote_literal(v_min_partition_timestamp)|| ' AND '||v_control||' < '||quote_literal(v_max_partition_timestamp) ||' FOR UPDATE NOWAIT'; EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; PERFORM @extschema@.create_partition_time(p_parent_table, v_partition_timestamp); -- This suffix generation code is in create_partition_time() as well v_partition_suffix := to_char(v_min_partition_timestamp, 'YYYY'); IF v_partition_interval < '1 year' AND v_partition_interval <> '1 week' THEN v_partition_suffix := v_partition_suffix ||'_'|| to_char(v_min_partition_timestamp, 'MM'); IF v_partition_interval < '1 month' AND v_partition_interval <> '1 week' THEN v_partition_suffix := v_partition_suffix ||'_'|| to_char(v_min_partition_timestamp, 'DD'); IF v_partition_interval < '1 day' THEN v_partition_suffix := v_partition_suffix || '_' || to_char(v_min_partition_timestamp, 'HH24MI'); IF v_partition_interval < '1 minute' THEN v_partition_suffix := v_partition_suffix || to_char(v_min_partition_timestamp, 'SS'); END IF; -- end < minute IF END IF; -- end < day IF END IF; -- end < month IF END IF; -- end < year IF IF v_partition_interval = '1 week' THEN v_partition_suffix := to_char(v_min_partition_timestamp, 'IYYY') || 'w' || to_char(v_min_partition_timestamp, 'IW'); END IF; -- "Q" is ignored in to_timestamp, so handle special case IF v_partition_interval = '3 months' AND (v_type = 'time') THEN v_year := to_char(v_min_partition_timestamp, 'YYYY'); v_quarter := to_char(v_min_partition_timestamp, 'Q'); v_partition_suffix := v_year || 'q' || v_quarter; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname||'.'||tablename = p_parent_table; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, v_partition_suffix, TRUE); EXECUTE 'WITH partition_data AS ( DELETE FROM ONLY '||p_parent_table||' WHERE '||v_control||' >= '||quote_literal(v_min_partition_timestamp)|| ' AND '||v_control||' < '||quote_literal(v_max_partition_timestamp)||' RETURNING *) INSERT INTO '||v_current_partition_name||' SELECT * FROM partition_data'; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; PERFORM @extschema@.create_function_time(p_parent_table); RETURN v_total_rows; END $$; /* * Function to re-apply ownership & privileges on all child tables in a partition set using parent table as reference */ CREATE OR REPLACE FUNCTION reapply_privileges(p_parent_table text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_child_owner text; v_child_table text; v_child_grant record; v_grant text; v_grantees text[]; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_match boolean; v_old_search_path text; v_parent_owner text; v_owner_sql text; v_revoke text[]; v_parent_grant record; v_sql text; v_step_id bigint; BEGIN SELECT jobmon INTO v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon IS NULL THEN RAISE EXCEPTION 'Given table is not managed by this extention: %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN RE-APPLYING PRIVILEGES TO ALL CHILD TABLES OF: '||p_parent_table); v_step_id := add_step(v_job_id, 'Setting new child table privileges'); END IF; SELECT tableowner INTO v_parent_owner FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; FOR v_child_table IN SELECT n.nspname||'.'||c.relname FROM pg_inherits i join pg_class c ON i.inhrelid = c.oid join pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC LOOP IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'PENDING', 'Currently on child partition in ascending order: '||v_child_table); END IF; v_grantees := NULL; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP -- Compare parent & child grants. Don't re-apply if it already exists v_match := false; FOR v_child_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_child_table GROUP BY grantee LOOP IF v_parent_grant.types = v_child_grant.types AND v_parent_grant.grantee = v_child_grant.grantee THEN v_match := true; END IF; END LOOP; IF v_match = false THEN EXECUTE 'GRANT '||array_to_string(v_parent_grant.types, ',')||' ON '||v_child_table||' TO '||v_parent_grant.grantee; SELECT array_agg(r) INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE '||array_to_string(v_revoke, ',')||' ON '||v_child_table||' FROM '||v_parent_grant.grantee||' CASCADE'; END IF; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN SELECT array_agg(r) INTO v_revoke FROM ( SELECT DISTINCT grantee::text AS r FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = v_child_table EXCEPT SELECT unnest(v_grantees)) x; IF v_revoke IS NOT NULL THEN EXECUTE 'REVOKE ALL ON '||v_child_table||' FROM '||array_to_string(v_revoke, ','); END IF; END IF; SELECT tableowner INTO v_child_owner FROM pg_tables WHERE schemaname ||'.'|| tablename = v_child_table; IF v_parent_owner <> v_child_owner THEN EXECUTE 'ALTER TABLE '||v_child_table||' OWNER TO '||v_parent_owner; END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); PERFORM close_job(v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; 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 RE-APPLYING PRIVILEGES TO ALL CHILD TABLES OF: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to manage pre-creation of the next partitions in a set. * Also manages dropping old partitions if the retention option is set. * If p_parent_table is passed, will only run run_maintenance() on that one table (no matter what the configuration table may have set for it) * Otherwise, will run on all tables in the config table with p_run_maintenance() set to true. * For large partition sets, running analyze can cause maintenance to take longer than expected. Can set p_analyze to false to avoid a forced analyze run. * Be aware that constraint exclusion may not work properly until an analyze on the partition set is run. */ CREATE OR REPLACE FUNCTION run_maintenance(p_parent_table text DEFAULT NULL, p_analyze boolean DEFAULT true, p_jobmon boolean DEFAULT true) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_create_count int := 0; v_current_partition text; v_current_partition_id bigint; v_current_partition_timestamp timestamp; v_datetime_string text; v_drop_count int := 0; v_id_position int; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_created boolean; v_last_partition_id bigint; v_last_partition_timestamp timestamp; v_next_partition_id bigint; v_next_partition_timestamp timestamp; v_old_search_path text; v_premade_count int; v_quarter text; v_row record; v_row_max_id record; v_row_sub record; v_step_id bigint; v_step_overflow_id bigint; v_step_serial_id bigint; v_sub_id_max bigint; v_sub_id_min bigint; v_sub_parent text; v_sub_timestamp_max timestamp; v_sub_timestamp_min timestamp; v_tablename text; v_tables_list_sql text; v_time_position int; v_year text; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman maintenance already running.'; RETURN; END IF; 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' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN RUN MAINTENANCE'); v_step_id := add_step(v_job_id, 'Running maintenance loop'); END IF; v_tables_list_sql := 'SELECT parent_table , partition_type , partition_interval , control , premake , datetime_string , undo_in_progress FROM @extschema@.part_config'; IF p_parent_table IS NULL THEN v_tables_list_sql := v_tables_list_sql || ' WHERE use_run_maintenance = true'; ELSE v_tables_list_sql := v_tables_list_sql || format(' WHERE parent_table = %L', p_parent_table); END IF; FOR v_row IN EXECUTE v_tables_list_sql LOOP CONTINUE WHEN v_row.undo_in_progress; SELECT show_partitions INTO v_last_partition FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LIMIT 1; IF v_row.partition_type = 'time' OR v_row.partition_type = 'time-custom' THEN IF v_row.partition_type = 'time' THEN CASE WHEN v_row.partition_interval::interval = '15 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_row.partition_interval::interval = '30 mins' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_row.partition_interval::interval = '1 hour' THEN v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_row.partition_interval::interval = '1 day' THEN v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_row.partition_interval::interval = '1 week' THEN v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_row.partition_interval::interval = '1 month' THEN v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_row.partition_interval::interval = '3 months' THEN v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_row.partition_interval::interval = '1 year' THEN v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; ELSIF v_row.partition_type = 'time-custom' THEN SELECT child_table INTO v_current_partition FROM @extschema@.custom_time_partitions WHERE parent_table = v_row.parent_table AND partition_range @> CURRENT_TIMESTAMP; IF v_current_partition IS NULL THEN RAISE EXCEPTION 'Current time partition missing from custom_time_partitions config table for table % and timestamp %', CURRENT_TIMESTAMP, v_row.parent_table; END IF; v_time_position := (length(v_current_partition) - position('p_' in reverse(v_current_partition))) + 2; v_current_partition_timestamp := to_timestamp(substring(v_current_partition from v_time_position), v_row.datetime_string); END IF; -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::timestamp, sub_max::timestamp INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'time'); -- No need to run maintenance if it's outside the bounds of the top parent. IF v_sub_timestamp_min IS NOT NULL THEN IF v_current_partition_timestamp < v_sub_timestamp_min OR v_current_partition_timestamp > v_sub_timestamp_max THEN CONTINUE; END IF; END IF; v_time_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; IF v_row.partition_interval::interval <> '3 months' OR (v_row.partition_interval::interval = '3 months' AND v_row.partition_type = 'time-custom') THEN v_last_partition_timestamp := to_timestamp(substring(v_last_partition from v_time_position), v_row.datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(v_last_partition from v_time_position), 'q', 1); v_quarter := split_part(substring(v_last_partition from v_time_position), 'q', 2); CASE WHEN v_quarter = '1' THEN v_last_partition_timestamp := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_last_partition_timestamp := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_last_partition_timestamp := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_last_partition_timestamp := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; -- Check and see how many premade partitions there are. v_premade_count = round(EXTRACT('epoch' FROM age(v_last_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); v_next_partition_timestamp := v_last_partition_timestamp; -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed. WHILE v_premade_count < v_row.premake LOOP BEGIN v_next_partition_timestamp := v_next_partition_timestamp + v_row.partition_interval::interval; EXCEPTION WHEN datetime_field_overflow THEN v_premade_count := v_row.premake; -- do this so it can exit the premake check loop and continue in the outer for loop IF v_jobmon_schema IS NOT NULL THEN 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 skippd for parent table '||v_partition_time); END IF; RAISE WARNING 'Attempted partition time interval is outside PostgreSQL''s supported time range. Child partition creation skipped for parent table %', v_row.parent_table; CONTINUE; END; v_last_partition_created := @extschema@.create_partition_time(v_row.parent_table, ARRAY[v_next_partition_timestamp], p_analyze); v_create_count := v_create_count + 1; PERFORM @extschema@.create_function_time(v_row.parent_table); -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(v_row.parent_table); v_premade_count = round(EXTRACT('epoch' FROM age(v_next_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); END LOOP; ELSIF v_row.partition_type = 'id' THEN -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT show_partitions FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP EXECUTE 'SELECT '||v_row.control||' - ('||v_row.control||' % '||v_row.partition_interval::int||') FROM '||v_row_max_id.show_partitions||' WHERE '||v_row.control||' = (SELECT max('||v_row.control||') FROM '||v_row_max_id.show_partitions||')' INTO v_current_partition_id; IF v_current_partition_id IS NOT NULL THEN EXIT; END IF; END LOOP; -- Determine if this table is a child of a subpartition parent. If so, get limits to see if run_maintenance even needs to run for it. SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'id'); -- No need to run maintenance if it's outside the bounds of the top parent. IF v_sub_id_min IS NOT NULL THEN IF v_current_partition_id < v_sub_id_min OR v_current_partition_id > v_sub_id_max THEN CONTINUE; END IF; END IF; v_id_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; v_last_partition_id = substring(v_last_partition from v_id_position)::bigint; v_next_partition_id := v_last_partition_id + v_row.partition_interval::bigint; WHILE ((v_next_partition_id - v_current_partition_id) / v_row.partition_interval::bigint) <= v_row.premake LOOP v_last_partition_created := @extschema@.create_partition_id(v_row.parent_table, ARRAY[v_next_partition_id], p_analyze); IF v_last_partition_created THEN PERFORM @extschema@.create_function_id(v_row.parent_table); PERFORM @extschema@.apply_constraints(v_row.parent_table); END IF; v_next_partition_id := v_next_partition_id + v_row.partition_interval::bigint; END LOOP; END IF; -- end main IF check for time or id END LOOP; -- end of creation loop -- Manage dropping old partitions if retention option is set FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND (partition_type = 'time' OR partition_type = 'time-custom') LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); END IF; END IF; END LOOP; FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND partition_type = 'id' LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); END IF; END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Partition maintenance finished. '||v_create_count||' partitions made. '||v_drop_count||' partitions dropped.'); IF v_step_overflow_id IS NOT NULL OR v_step_serial_id IS NOT NULL THEN PERFORM fail_job(v_job_id); ELSE PERFORM close_job(v_job_id); END IF; EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; 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 RUN MAINTENANCE'')', v_jobmon_schema) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to list all child partitions in a set. */ CREATE OR REPLACE FUNCTION show_partitions (p_parent_table text, p_order text DEFAULT 'ASC') RETURNS SETOF text LANGUAGE plpgsql STABLE SECURITY DEFINER AS $$ DECLARE v_datetime_string text; v_partition_interval text; v_type text; BEGIN IF p_order NOT IN ('ASC', 'DESC') THEN RAISE EXCEPTION 'p_order paramter must be one of the following values: ASC, DESC'; END IF; SELECT partition_type , partition_interval , datetime_string INTO v_type , v_partition_interval , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_type IN ('time', 'time-custom') THEN RETURN QUERY EXECUTE ' SELECT n.nspname::text ||''.''|| c.relname::text AS partition_name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = '||quote_literal(p_parent_table)||'::regclass ORDER BY to_timestamp(substring(c.relname from ((length(c.relname) - position(''p_'' in reverse(c.relname))) + 2) ), '||quote_literal(v_datetime_string)||') ' || p_order; ELSIF v_type = 'id' THEN RETURN QUERY EXECUTE ' SELECT n.nspname::text ||''.''|| c.relname::text AS partition_name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = '||quote_literal(p_parent_table)||'::regclass ORDER BY substring(c.relname from ((length(c.relname) - position(''p_'' in reverse(c.relname))) + 2) )::bigint ' || p_order; END IF; END $$; /* * Function to undo partitioning. * Will actually work on any parent/child table set, not just ones created by pg_partman. */ CREATE OR REPLACE FUNCTION undo_partition(p_parent_table text, p_batch_count int DEFAULT 1, p_keep_table boolean DEFAULT true, p_jobmon boolean DEFAULT true, p_lock_wait numeric DEFAULT 0) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_batch_loop_count bigint := 0; v_child_count bigint; v_child_table text; v_copy_sql text; v_function_name text; v_job_id bigint; v_jobmon_schema text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_rowcount bigint; v_step_id bigint; v_total bigint := 0; v_trig_name text; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition already running.'; RETURN 0; END IF; IF p_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Undoing partitioning for table '||p_parent_table); END IF; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables and stop new id partitions from being made. SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, '_part_trig_func', FALSE); SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t WHERE tgname = v_trig_name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE 'LOCK TABLE ONLY '||p_parent_table||' IN ACCESS EXCLUSIVE MODE NOWAIT'; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; RETURN -1; END IF; END IF; -- END p_lock_wait IF EXECUTE 'DROP TRIGGER IF EXISTS '||v_trig_name||' ON '||p_parent_table; END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE 'DROP FUNCTION IF EXISTS '||v_function_name||'()'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); END IF; WHILE v_batch_loop_count < p_batch_count LOOP SELECT n.nspname||'.'||c.relname INTO v_child_table FROM pg_inherits i JOIN pg_class c ON i.inhrelid = c.oid JOIN pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC; EXIT WHEN v_child_table IS NULL; EXECUTE 'SELECT count(*) FROM '||v_child_table INTO v_child_count; IF v_child_count = 0 THEN -- No rows left in this child table. Remove from partition set. -- lockwait timeout for table drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE 'LOCK TABLE ONLY '||v_child_table||' IN ACCESS EXCLUSIVE MODE NOWAIT'; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; RETURN -1; END IF; END IF; -- END p_lock_wait IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF p_keep_table = false THEN EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table DROPPED. Moved '||coalesce(v_rowcount, 0)||' rows to parent'); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table UNINHERITED, not DROPPED. Copied '||coalesce(v_rowcount, 0)||' rows to parent'); END IF; END IF; v_undo_count := v_undo_count + 1; CONTINUE; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing child partition: '||v_child_table); END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE 'SELECT * FROM '|| v_child_table ||' FOR UPDATE NOWAIT'; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; RETURN -1; END IF; END IF; v_copy_sql := 'INSERT INTO '||p_parent_table||' SELECT * FROM '||v_child_table; EXECUTE v_copy_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF p_keep_table = false THEN EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table DROPPED. Moved '||v_rowcount||' rows to parent'); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table UNINHERITED, not DROPPED. Copied '||v_rowcount||' rows to parent'); END IF; END IF; v_batch_loop_count := v_batch_loop_count + 1; v_undo_count := v_undo_count + 1; END LOOP; IF v_undo_count = 0 THEN -- FOR loop never ran, so there's no child tables left. DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman (if it existed)'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) from % child table(s) to the parent: %', v_total, v_undo_count, p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', 'Copied '||v_total||' row(s) from '||v_undo_count||' child table(s) to the parent'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_total; 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 UNDO PARTITIONING: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to undo id-based partitioning created by this extension */ CREATE OR REPLACE FUNCTION undo_partition_id(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval bigint DEFAULT NULL, p_keep_table boolean DEFAULT true, p_lock_wait numeric DEFAULT 0) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_batch_loop_count int := 0; v_child_loop_total bigint := 0; v_child_min bigint; v_child_table text; v_control text; v_exists int; v_function_name text; v_inner_loop_count int; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_move_sql text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_row record; v_rowcount bigint; v_step_id bigint; v_sub_count int; v_trig_name text; v_total bigint := 0; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition_id')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition_id already running.'; RETURN 0; END IF; SELECT partition_interval::bigint , control , jobmon INTO v_partition_interval , v_control , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; -- Check if any child tables are themselves partitioned or part of an inheritance tree. Prevent undo at this level if so. -- Need to either lock child tables at all levels or handle the proper removal of triggers on all child tables first -- before multi-level undo can be performed safely. FOR v_row IN SELECT show_partitions AS child_table FROM @extschema@.show_partitions(p_parent_table) LOOP SELECT count(*) INTO v_sub_count FROM pg_catalog.pg_inherits WHERE inhparent::regclass = v_row.child_table::regclass; IF v_sub_count > 0 THEN RAISE EXCEPTION 'Child table for this parent has child table(s) itself (%). Run undo partitioning on this table or remove inheritance first to ensure all data is properly moved to parent', v_row.child_table; END IF; END LOOP; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Undoing partitioning for table '||p_parent_table); END IF; IF p_batch_interval IS NULL THEN p_batch_interval := v_partition_interval; END IF; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables and stop new id partitions from being made. SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, '_part_trig_func', FALSE); SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t WHERE tgname = v_trig_name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE 'LOCK TABLE ONLY '||p_parent_table||' IN ACCESS EXCLUSIVE MODE NOWAIT'; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; RETURN -1; END IF; END IF; -- END p_lock_wait IF EXECUTE 'DROP TRIGGER IF EXISTS '||v_trig_name||' ON '||p_parent_table; END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE 'DROP FUNCTION IF EXISTS '||v_function_name||'()'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); END IF; <> WHILE v_batch_loop_count < p_batch_count LOOP SELECT n.nspname||'.'||c.relname INTO v_child_table FROM pg_inherits i JOIN pg_class c ON i.inhrelid = c.oid JOIN pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC; EXIT WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing child partition: '||v_child_table); END IF; EXECUTE 'SELECT min('||v_control||') FROM '||v_child_table INTO v_child_min; IF v_child_min IS NULL THEN -- No rows left in this child table. Remove from partition set. -- lockwait timeout for table drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE 'LOCK TABLE ONLY '||v_child_table||' IN ACCESS EXCLUSIVE MODE NOWAIT'; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; RETURN -1; END IF; END IF; -- END p_lock_wait IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF p_keep_table = false THEN EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table UNINHERITED, not DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; END IF; v_undo_count := v_undo_count + 1; CONTINUE outer_child_loop; END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP -- lockwait timeout for row batches IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE 'SELECT * FROM ' || v_child_table || ' WHERE '||v_control||' <= '||quote_literal(v_child_min + (p_batch_interval * v_inner_loop_count)) ||' FOR UPDATE NOWAIT'; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; RETURN -1; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval v_move_sql := 'WITH move_data AS (DELETE FROM '||v_child_table|| ' WHERE '||v_control||' <= '||quote_literal(v_child_min + (p_batch_interval * v_inner_loop_count))||' RETURNING *) INSERT INTO '||p_parent_table||' SELECT * FROM move_data'; EXECUTE v_move_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Moved '||v_child_loop_total||' rows to parent.'); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; IF v_batch_loop_count < p_batch_count THEN -- FOR loop never ran, so there's no child tables left. DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) to the parent. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', 'Copied '||v_total||' row(s) to the parent. Removed '||v_undo_count||' partitions.'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_total; 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 UNDO PARTITIONING: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to undo time-based partitioning created by this extension */ CREATE OR REPLACE FUNCTION undo_partition_time(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval interval DEFAULT NULL, p_keep_table boolean DEFAULT true, p_lock_wait numeric DEFAULT 0) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_batch_loop_count int := 0; v_child_min timestamptz; v_child_loop_total bigint := 0; v_child_table text; v_control text; v_function_name text; v_inner_loop_count int; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_move_sql text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_row record; v_rowcount bigint; v_step_id bigint; v_sub_count int; v_total bigint := 0; v_trig_name text; v_type text; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition_time')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition_time already running.'; RETURN 0; END IF; SELECT partition_type , partition_interval::interval , control , jobmon INTO v_type , v_partition_interval , v_control , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; -- Check if any child tables are themselves partitioned or part of an inheritance tree. Prevent undo at this level if so. -- Need to either lock child tables at all levels or handle the proper removal of triggers on all child tables first -- before multi-level undo can be performed safely. FOR v_row IN SELECT show_partitions AS child_table FROM @extschema@.show_partitions(p_parent_table) LOOP SELECT count(*) INTO v_sub_count FROM pg_catalog.pg_inherits WHERE inhparent::regclass = v_row.child_table::regclass; IF v_sub_count > 0 THEN RAISE EXCEPTION 'Child table for this parent has child table(s) itself (%). Run undo partitioning on this table or remove inheritance first to ensure all data is properly moved to parent', v_row.child_table; END IF; END LOOP; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE 'SELECT set_config(''search_path'',''@extschema@,'||v_jobmon_schema||''',''false'')'; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN UNDO PARTITIONING: '||p_parent_table); v_step_id := add_step(v_job_id, 'Undoing partitioning for table '||p_parent_table); END IF; IF p_batch_interval IS NULL THEN p_batch_interval := v_partition_interval; END IF; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables. SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, v_parent_schema, '_part_trig_func', FALSE); SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t WHERE tgname = v_trig_name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE 'LOCK TABLE ONLY '||p_parent_table||' IN ACCESS EXCLUSIVE MODE NOWAIT'; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; RETURN -1; END IF; END IF; -- END p_lock_wait IF EXECUTE 'DROP TRIGGER IF EXISTS '||v_trig_name||' ON '||p_parent_table; END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE 'DROP FUNCTION IF EXISTS '||v_function_name||'()'; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); END IF; <> WHILE v_batch_loop_count < p_batch_count LOOP SELECT n.nspname||'.'||c.relname INTO v_child_table FROM pg_inherits i JOIN pg_class c ON i.inhrelid = c.oid JOIN pg_namespace n ON c.relnamespace = n.oid WHERE i.inhparent::regclass = p_parent_table::regclass ORDER BY i.inhrelid ASC; EXIT WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing child partition: '||v_child_table); END IF; EXECUTE 'SELECT min('||v_control||') FROM '||v_child_table INTO v_child_min; IF v_child_min IS NULL THEN -- No rows left in this child table. Remove from partition set. -- lockwait timeout for table drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE 'LOCK TABLE ONLY '||v_child_table||' IN ACCESS EXCLUSIVE MODE NOWAIT'; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; RETURN -1; END IF; END IF; -- END p_lock_wait IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE 'ALTER TABLE '||v_child_table||' NO INHERIT ' || p_parent_table; IF p_keep_table = false THEN EXECUTE 'DROP TABLE '||v_child_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Child table UNINHERITED, not DROPPED. Moved '||v_child_loop_total||' rows to parent'); END IF; END IF; IF v_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_child_table; END IF; v_undo_count := v_undo_count + 1; CONTINUE outer_child_loop; END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE 'SELECT * FROM ' || v_child_table || ' WHERE '||v_control||' <= '||quote_literal(v_child_min + (p_batch_interval * v_inner_loop_count)) ||' FOR UPDATE NOWAIT'; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; RETURN -1; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval v_move_sql := 'WITH move_data AS (DELETE FROM '||v_child_table|| ' WHERE '||v_control||' <= '||quote_literal(v_child_min + (p_batch_interval * v_inner_loop_count))||' RETURNING *) INSERT INTO '||p_parent_table||' SELECT * FROM move_data'; EXECUTE v_move_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Moved '||v_child_loop_total||' rows to parent.'); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; IF v_batch_loop_count < p_batch_count THEN -- FOR loop never ran, so there's no child tables left. DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) to the parent. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', 'Copied '||v_total||' row(s) to the parent. Removed '||v_undo_count||' partitions.'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE 'SELECT set_config(''search_path'','''||v_old_search_path||''',''false'')'; END IF; RETURN v_total; 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 UNDO PARTITIONING: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Check if parent table is a subpartition of an already existing id based partition set managed by pg_partman * If so, limit what child tables can be created based on parent suffix */ CREATE OR REPLACE FUNCTION check_subpartition_limits(p_parent_table text, p_type text, OUT sub_min text, OUT sub_max text) RETURNS record LANGUAGE plpgsql AS $$ DECLARE v_datetime_string text; v_id_position int; v_partition_interval interval; v_quarter text; v_sub_id_max bigint; v_sub_id_min bigint; v_sub_timestamp_max timestamp; v_sub_timestamp_min timestamp; v_time_position int; v_top_datetime_string text; v_top_interval text; v_top_parent text; v_top_type text; v_year text; BEGIN -- CTE query is done individually for each type (time, id) because it should return NULL if the top parent is not the same type in a subpartition set (id->time or time->id) IF p_type = 'id' THEN WITH top_oid AS ( SELECT i.inhparent AS top_parent_oid FROM pg_catalog.pg_class c JOIN pg_catalog.pg_inherits i ON c.oid = i.inhrelid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname||'.'||c.relname = p_parent_table ) SELECT n.nspname||'.'||c.relname, p.datetime_string, p.partition_interval, p.partition_type INTO v_top_parent, v_top_datetime_string, v_top_interval, v_top_type FROM pg_catalog.pg_class c JOIN top_oid t ON c.oid = t.top_parent_oid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace JOIN @extschema@.part_config p ON p.parent_table = n.nspname||'.'||c.relname WHERE c.oid = t.top_parent_oid AND p.partition_type = 'id'; IF v_top_parent IS NOT NULL THEN v_id_position := (length(p_parent_table) - position('p_' in reverse(p_parent_table))) + 2; v_sub_id_min := substring(p_parent_table from v_id_position)::bigint; v_sub_id_max := (v_sub_id_min + v_top_interval::bigint) - 1; sub_min := v_sub_id_min::text; sub_max := v_sub_id_max::text; END IF; ELSIF p_type = 'time' THEN WITH top_oid AS ( SELECT i.inhparent AS top_parent_oid FROM pg_catalog.pg_class c JOIN pg_catalog.pg_inherits i ON c.oid = i.inhrelid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname||'.'||c.relname = p_parent_table ) SELECT n.nspname||'.'||c.relname, p.datetime_string, p.partition_interval, p.partition_type INTO v_top_parent, v_top_datetime_string, v_top_interval, v_top_type FROM pg_catalog.pg_class c JOIN top_oid t ON c.oid = t.top_parent_oid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace JOIN @extschema@.part_config p ON p.parent_table = n.nspname||'.'||c.relname WHERE c.oid = t.top_parent_oid AND p.partition_type = 'time' OR p.partition_type = 'time-custom'; IF v_top_parent IS NOT NULL THEN v_time_position := (length(p_parent_table) - position('p_' in reverse(p_parent_table))) + 2; IF v_top_interval::interval <> '3 months' OR (v_top_interval::interval = '3 months' AND v_top_type = 'time-custom') THEN v_sub_timestamp_min := to_timestamp(substring(p_parent_table from v_time_position), v_top_datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(p_parent_table from v_time_position), 'q', 1); v_quarter := split_part(substring(p_parent_table from v_time_position), 'q', 2); CASE WHEN v_quarter = '1' THEN v_sub_timestamp_min := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_sub_timestamp_min := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_sub_timestamp_min := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_sub_timestamp_min := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; v_sub_timestamp_max = (v_sub_timestamp_min + v_top_interval::interval) - '1 sec'::interval; sub_min := v_sub_timestamp_min::text; sub_max := v_sub_timestamp_max::text; END IF; ELSE RAISE EXCEPTION 'Invalid type given as parameter to check_subpartition_limits()'; END IF; RETURN; END $$; pg_partman-4.6.0/updates/pg_partman--2.0.0--2.1.0.sql000066400000000000000000006314131412761706100214250ustar00rootroot00000000000000-- Object names (tables, roles, etc) with special characters & mixed case are now supported. -- Fixed bug in apply_foreign_keys() that was causing it to fail if there were multiple FKs to the same foreign column. Also fixed a bug that allowed the child name parameter to not be passed and didn't handle it if it wasn't. Thanks to Andrew Dunstan for the bug fix and tremendously simplified code. Backpatched to 1.8.8. (Github Issue #64). -- Ensure trigger function is recreated if a partition is dropped as part of retention. This makes sure the tables explicitily listed actually exist. Backpatched to 1.8.8 (Github Issue #62). -- Fixed bug in check_unique_constraint.py to properly inspect all data from an entire inheritance tree. If you were using this before to check for duplicates across children in an inheritance set, it is highly recommended you run it again as it may not have caught all dupes. -- New function 'show_partition_name()' can tell you the child table name that a given value would exist in given a parent table that pg_partman manages. The name will always be returned whether the child table exists or not. Another boolean column is returned that tells you whether the child table actually does exist. It also returns the raw value (timestamp or integer) for the suffix of the returned partition name (used internally, but could be useful elsewhere too). Thanks to Corey Huinker for idea & assistance. -- show_partitions() function now returns the schema and table name as two separate fields. -- New column in part_config to denote when a sub-partition set has had its final child partition made (sub_partition_set_full). Allows run_maintenance() to skip over it and run more efficiently when managing many sub-partition sets. -- Updated all python scripts to handle mixed-case & special characters. -- Bumped all minimum pg_partman version requirements for python scripts to 2.0.0. May still work on the older versions, but not officially supported or guaranteed anymore. -- New --nonpartman option to the reapply_indexes.py and reapply_foreign_keys.py scripts to allow them to work better with partition sets not managed by pg_partman and to allow those that are to work more efficiently. -- Greatly reduced number of individual pg_jobmon jobs generated. Steps such as creating partition functions, applying foreign keys, and applying additional constraints have been combined into the job log entries for creating partitions or general maintenance when applicable. -- Internal function check_name_length() no longer takes a schemaname argument, nor returns the schema as part of the modified object name. -- Simplified internal code for handling time suffix name generation -- Much more extensive pgTAP testing suites to handle more edge cases -- NOTE: If you are running version 1.8.7 and need to update to 1.8.8 do the following steps: -- Download the latest version as normal. -- Copy the file "updates/pg_partman--1.8.7--1.8.8.sql" to the folder where your extension SQL files are kept (depends on your OS or where you manually installed postgres). -- If you're running from less than 1.8.7, copy whichever prior update files you need to get from your version to 1.8.8. They are all kept in the updates folder. -- While logged into postgres run: ALTER EXTENSION pg_partman UPDATE TO '1.8.8'; -- The version number at the end of the above command is important. Otherwise it may try and update you to the latest 2.x version if you copied more update files then necessary or ran "make install". ALTER TABLE @extschema@.part_config ADD COLUMN sub_partition_set_full boolean NOT NULL DEFAULT false; CREATE TEMP TABLE partman_preserve_privs_temp (statement text); INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.apply_foreign_keys(text, text, bigint, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'apply_foreign_keys'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.show_partitions(text, text) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'show_partitions'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.check_name_length(text, text, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'check_name_length'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_function_id(text, bigint) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_function_id'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_function_time(text, bigint) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_function_time'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.apply_constraints(text, text, boolean, bigint, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'apply_constraints'; DROP FUNCTION @extschema@.apply_foreign_keys(text, text, boolean); DROP FUNCTION @extschema@.show_partitions (text, text); DROP FUNCTION @extschema@.check_name_length (text, text, text, boolean); DROP FUNCTION @extschema@.create_function_id(text); DROP FUNCTION @extschema@.create_function_time(text); DROP FUNCTION @extschema@.apply_constraints(text, text, boolean, boolean); -- This function goes in table.sql file /* * Ensure that sub-partitioned tables that are themselves sub-partitions have the same configuration options set when they are part of the same inheritance tree */ CREATE OR REPLACE FUNCTION @extschema@.check_subpart_sameconfig(text) RETURNS boolean LANGUAGE sql STABLE AS $$ WITH parent_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE n1.nspname||'.'||c1.relname = $1 ) , child_tables AS ( SELECT n.nspname||'.'||c.relname AS tablename FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN parent_info pi ON h.inhparent = pi.oid ) SELECT CASE WHEN count(*) <= 1 THEN true WHEN count(*) > 1 THEN false END FROM ( 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_use_run_maintenance , sub_jobmon FROM @extschema@.part_config_sub a JOIN child_tables b on a.sub_parent = b.tablename) x; $$; /* * Apply constraints managed by partman extension */ CREATE OR REPLACE FUNCTION apply_constraints(p_parent_table text, p_child_table text DEFAULT NULL, p_analyze boolean DEFAULT FALSE, p_job_id bigint DEFAULT NULL, p_debug boolean DEFAULT FALSE) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_child_table text; v_child_tablename text; v_col text; v_constraint_cols text[]; v_constraint_col_type text; v_constraint_name text; v_constraint_values record; v_datetime_string text; v_existing_constraint_name text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_id int; v_last_partition_timestamp timestamp; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval text; v_partition_suffix text; v_premake int; v_sql text; v_step_id bigint; v_suffix_position int; v_type text; BEGIN SELECT partition_type , partition_interval , premake , datetime_string , constraint_cols , jobmon INTO v_type , v_partition_interval , v_premake , v_datetime_string , v_constraint_cols , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_constraint_cols IS NULL THEN IF p_debug THEN RAISE NOTICE 'Given parent table (%) not set up for constraint management (constraint_cols is NULL)', p_parent_table; END IF; -- Returns silently to allow this function to be simply called by maintenance processes without having to check if config options are set. RETURN; END IF; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE CONSTRAINT: %s', p_parent_table)); ELSE v_job_id = p_job_id; END IF; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; -- If p_child_table is null, figure out the partition that is the one right before the premake value backwards. IF p_child_table IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying additional constraints: Automatically determining most recent child on which to apply constraints'); END IF; v_suffix_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; IF v_type IN ('time', 'time-custom') THEN v_last_partition_timestamp := to_timestamp(substring(v_last_partition from v_suffix_position), v_datetime_string); v_partition_suffix := to_char(v_last_partition_timestamp - (v_partition_interval::interval * ((v_premake * 2)+1) ), v_datetime_string); ELSIF v_type = 'id' THEN v_last_partition_id := substring(v_last_partition from v_suffix_position)::int; v_partition_suffix := (v_last_partition_id - (v_partition_interval::int * ((v_premake * 2)+1) ))::text; END IF; v_child_table := v_parent_schema ||'.'|| @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Target child table: %s.%s', v_parent_schema, v_child_table)); END IF; ELSE v_child_table := p_child_table; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying additional constraints: Checking if target child table exists'); END IF; SELECT tablename INTO v_child_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_child_table; IF v_child_tablename IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Target child table (%s) does not exist. Skipping constraint creation.', v_child_table)); PERFORM close_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; IF p_debug THEN RAISE NOTICE 'Target child table (%) does not exist. Skipping constraint creation.', v_child_table; END IF; RETURN; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; FOREACH v_col IN ARRAY v_constraint_cols LOOP SELECT con.conname INTO v_existing_constraint_name FROM pg_catalog.pg_constraint con JOIN pg_class c ON c.oid = con.conrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN pg_catalog.pg_attribute a ON con.conrelid = a.attrelid WHERE c.relname = v_child_tablename AND n.nspname = v_parent_schema AND con.conname LIKE 'partmanconstr_%' AND con.contype = 'c' AND a.attname = v_col AND ARRAY[a.attnum] <@ con.conkey AND a.attisdropped = false; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Applying additional constraints: Applying new constraint on column: %s', v_col)); END IF; IF v_existing_constraint_name IS NOT NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Partman managed constraint already exists on this table (%s) and column (%s). Skipping creation.', v_child_table, v_col)); END IF; RAISE WARNING 'Partman managed constraint already exists on this table (%) and column (%). Skipping creation.', v_child_table, v_col ; CONTINUE; END IF; -- Ensure column name gets put on end of constraint name to help avoid naming conflicts v_constraint_name := @extschema@.check_name_length('partmanconstr_'||v_child_tablename, p_suffix := '_'||v_col); EXECUTE format('SELECT min(%I)::text AS min, max(%I)::text AS max FROM %I.%I', v_col, v_col, v_parent_schema, v_child_tablename) INTO v_constraint_values; IF v_constraint_values IS NOT NULL THEN v_sql := format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I <= %L)' , v_parent_schema , v_child_tablename , v_constraint_name , v_col , v_constraint_values.min , v_col , v_constraint_values.max); IF p_debug THEN RAISE NOTICE 'Constraint creation query: %', v_sql; END IF; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('New constraint created: %s', v_sql)); END IF; ELSE IF p_debug THEN RAISE NOTICE 'Given column (%) contains all NULLs. No constraint created', v_col; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Given column (%s) contains all NULLs. No constraint created', v_col)); END IF; END IF; END LOOP; IF p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Applying additional constraints: Running analyze on partition set: %s', p_parent_table)); END IF; IF p_debug THEN RAISE NOTICE 'Running analyze on partition set: %', p_parent_table; END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; 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 CONSTRAINT: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Apply foreign keys that exist on the given parent to the given child table */ CREATE OR REPLACE FUNCTION apply_foreign_keys(p_parent_table text, p_child_table text, p_job_id bigint DEFAULT NULL, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_count int := 0; v_job_id bigint; v_jobmon text; v_jobmon_schema text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_ref_schema text; v_ref_table text; v_row record; v_schemaname text; v_sql text; v_step_id bigint; v_tablename text; BEGIN SELECT jobmon INTO v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN APPLYING FOREIGN KEYS: %s', p_parent_table)); ELSE -- Don't create a new job, add steps into given job v_job_id := p_job_id; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Applying foreign keys to %s if they exist on parent', p_child_table)); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname||'.'||tablename = p_parent_table; SELECT schemaname, tablename INTO v_schemaname, v_tablename FROM pg_catalog.pg_tables WHERE schemaname||'.'||tablename = p_child_table; IF v_tablename IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'CRITICAL', format('Target child table (%s) does not exist.', p_child_table)); PERFORM fail_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RAISE EXCEPTION 'Target child table (%) does not exist.', p_child_table; RETURN; END IF; FOR v_row 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 JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_parent_tablename AND n.nspname = v_parent_schema AND contype = 'f' LOOP v_sql := format('ALTER TABLE %I.%I ADD %s' , v_schemaname , v_tablename , v_row.constraint_def); IF p_debug THEN RAISE NOTICE 'Constraint creation query: %', v_sql; END IF; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'FK applied'); END IF; v_count := v_count + 1; END LOOP; IF v_count = 0 AND v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'No FKs found on parent'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; 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 APPLYING FOREIGN KEYS: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * 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 schema given if it doesn't require truncation */ CREATE FUNCTION check_name_length (p_object_name text, p_suffix text DEFAULT NULL, p_table_partition boolean DEFAULT FALSE) RETURNS text LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_new_length int; v_new_name text; BEGIN 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 $$; /* * Function to monitor for data getting inserted into parent tables managed by extension */ CREATE OR REPLACE FUNCTION check_parent() RETURNS SETOF @extschema@.check_parent_table LANGUAGE plpgsql STABLE SECURITY DEFINER AS $$ DECLARE v_count bigint = 0; v_row record; v_schemaname text; v_tablename text; v_sql text; v_trouble @extschema@.check_parent_table%rowtype; BEGIN FOR v_row IN SELECT DISTINCT parent_table FROM @extschema@.part_config LOOP SELECT schemaname, tablename INTO v_schemaname, v_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_row.parent_table; v_sql := format('SELECT count(1) AS n FROM ONLY %I.%I', v_schemaname, v_tablename); EXECUTE v_sql INTO v_count; IF v_count > 0 THEN v_trouble.parent_table := v_schemaname ||'.'|| v_tablename; v_trouble.count := v_count; RETURN NEXT v_trouble; END IF; v_count := 0; END LOOP; RETURN; END $$; /* * Create the trigger function for the parent table of an id-based partition set */ CREATE OR REPLACE FUNCTION create_function_id(p_parent_table text, p_job_id bigint DEFAULT NULL) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_control text; v_count int; v_current_partition_name text; v_current_partition_id bigint; v_datetime_string text; v_final_partition_id bigint; v_function_name text; v_higher_parent text := p_parent_table; v_id_position int; v_job_id bigint; v_jobmon text; v_jobmon_schema text; v_last_partition text; v_max bigint; v_next_partition_id bigint; v_next_partition_name text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_premake int; v_prev_partition_id bigint; v_prev_partition_name text; v_row_max_id record; v_run_maint boolean; v_step_id bigint; v_top_parent text := p_parent_table; v_trig_func text; BEGIN SELECT partition_interval::bigint , control , premake , use_run_maintenance , jobmon INTO v_partition_interval , v_control , v_premake , v_run_maint , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE FUNCTION: %s', p_parent_table)); ELSE v_job_id = p_job_id; END IF; v_step_id := add_step(v_job_id, format('Creating partition function for table %s', p_parent_table)); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); -- Get the highest level top parent if multi-level partitioned in order to get proper max() value below WHILE v_higher_parent 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||'.'||c.relname = v_higher_parent ) SELECT n.nspname||'.'||c.relname INTO v_higher_parent 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 WHERE p.partition_type = 'id'; IF v_higher_parent IS NOT NULL THEN -- initially set in DECLARE v_top_parent := v_higher_parent; END IF; END LOOP; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_top_parent, 'DESC') LOOP EXECUTE format('SELECT max(%I) FROM %I.%I', v_control, v_row_max_id.partition_schemaname, v_row_max_id.partition_tablename) INTO v_max; IF v_max IS NOT NULL THEN EXIT; END IF; END LOOP; IF v_max IS NULL THEN v_max := 0; END IF; v_current_partition_id = v_max - (v_max % v_partition_interval); v_next_partition_id := v_current_partition_id + v_partition_interval; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_current_partition_id::text, TRUE); v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_current_partition_id bigint; v_current_partition_name text; v_id_position int; v_last_partition text := %L; v_next_partition_id bigint; v_next_partition_name text; v_partition_created boolean; BEGIN IF TG_OP = ''INSERT'' THEN IF NEW.%I >= %s AND NEW.%I < %s THEN ' , v_parent_schema , v_function_name , v_last_partition , v_control , v_current_partition_id , v_control , v_next_partition_id ); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema AND tablename = v_current_partition_name; IF v_count > 0 THEN v_trig_func := v_trig_func || format(' INSERT INTO %I.%I VALUES (NEW.*); ', v_parent_schema, v_current_partition_name); ELSE v_trig_func := v_trig_func || ' -- Child table for current values does not exist in this partition set, so write to parent RETURN NEW;'; END IF; FOR i IN 1..v_premake LOOP v_prev_partition_id := v_current_partition_id - (v_partition_interval * i); v_next_partition_id := v_current_partition_id + (v_partition_interval * i); v_final_partition_id := v_next_partition_id + v_partition_interval; v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, v_prev_partition_id::text, TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, v_next_partition_id::text, TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles edge case of changing premake immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema AND tablename = v_prev_partition_name; IF v_count > 0 THEN -- Only handle previous partitions if they're starting above zero IF v_prev_partition_id >= 0 THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %s AND NEW.%I < %s THEN INSERT INTO %I.%I VALUES (NEW.*); ' , v_control , v_prev_partition_id , v_control , v_prev_partition_id + v_partition_interval , v_parent_schema , v_prev_partition_name ); END IF; END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_next_partition_name; IF v_count > 0 THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %s AND NEW.%I < %s THEN INSERT INTO %I.%I VALUES (NEW.*);' , v_control , v_next_partition_id , v_control , v_final_partition_id , v_parent_schema , v_next_partition_name ); END IF; END LOOP; v_trig_func := v_trig_func ||format(' ELSE v_current_partition_id := NEW.%I - (NEW.%I %% %s); v_current_partition_name := @extschema@.check_name_length(%L, v_current_partition_id::text, TRUE); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = %L AND tablename = v_current_partition_name; IF v_count > 0 THEN EXECUTE format(''INSERT INTO %%I.%%I VALUES($1.*)'', %L, v_current_partition_name) USING NEW; ELSE RETURN NEW; END IF; END IF;' , v_control , v_control , v_partition_interval , v_parent_tablename , v_parent_schema , v_parent_schema ); IF v_run_maint IS FALSE THEN v_trig_func := v_trig_func ||format(' v_current_partition_id := NEW.%I - (NEW.%I %% %s); IF (NEW.%I %% %s) > (%s / 2) THEN v_id_position := (length(v_last_partition) - position(''p_'' in reverse(v_last_partition))) + 2; v_next_partition_id := (substring(v_last_partition from v_id_position)::bigint) + %s; WHILE ((v_next_partition_id - v_current_partition_id) / %s) <= %s LOOP v_partition_created := @extschema@.create_partition_id(%L, ARRAY[v_next_partition_id]); IF v_partition_created THEN PERFORM @extschema@.create_function_id(%L); PERFORM @extschema@.apply_constraints(%L); END IF; v_next_partition_id := v_next_partition_id + %s; END LOOP; END IF;' , v_control , v_control , v_partition_interval , v_control , v_partition_interval , v_partition_interval , v_partition_interval , v_partition_interval , v_premake , p_parent_table , p_parent_table , p_parent_table , v_partition_interval ); END IF; v_trig_func := v_trig_func ||' END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for current id interval: %s to %s', v_current_partition_id, v_final_partition_id-1)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; 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 FUNCTION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''Partition function maintenance for table %s 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 $$; /* * Create the trigger function for the parent table of a time-based partition set */ CREATE OR REPLACE FUNCTION create_function_time(p_parent_table text, p_job_id bigint DEFAULT NULL) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_control text; v_count int; v_current_partition_name text; v_current_partition_timestamp timestamptz; v_datetime_string text; v_final_partition_timestamp timestamptz; v_function_name text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_new_length int; v_next_partition_name text; v_next_partition_timestamp timestamptz; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_premake int; v_prev_partition_name text; v_prev_partition_timestamp timestamptz; v_step_id bigint; v_trig_func text; v_type text; BEGIN SELECT partition_type , partition_interval::interval , control , premake , datetime_string , jobmon INTO v_type , v_partition_interval , v_control , v_premake , v_datetime_string , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE FUNCTION: %s', p_parent_table)); ELSE v_job_id = p_job_id; END IF; v_step_id := add_step(v_job_id, format('Creating partition function for table %s', p_parent_table)); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); IF v_type = 'time' THEN v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_partition_name text; v_partition_timestamp timestamptz; BEGIN IF TG_OP = ''INSERT'' THEN ' , v_parent_schema , v_function_name); CASE WHEN v_partition_interval = '15 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', NEW.%I) + ''15min''::interval * floor(date_part(''minute'', NEW.%I) / 15.0);' , v_control , v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', NEW.%I) + ''30min''::interval * floor(date_part(''minute'', NEW.%I) / 30.0);' , v_control , v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 day' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''day'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 week' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''week'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 month' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''month'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_partition_interval = '3 months' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''quarter'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 year' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''year'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, to_char(v_current_partition_timestamp, v_datetime_string), TRUE); v_next_partition_timestamp := v_current_partition_timestamp + v_partition_interval::interval; v_trig_func := v_trig_func ||format(' IF NEW.%I >= %L AND NEW.%I < %L THEN ' , v_control , v_current_partition_timestamp , v_control , v_next_partition_timestamp); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema AND tablename = v_current_partition_name; IF v_count > 0 THEN v_trig_func := v_trig_func || format(' INSERT INTO %I.%I VALUES (NEW.*); ', v_parent_schema, v_current_partition_name); ELSE v_trig_func := v_trig_func || ' -- Child table for current values does not exist in this partition set, so write to parent RETURN NEW;'; END IF; FOR i IN 1..v_premake LOOP v_prev_partition_timestamp := v_current_partition_timestamp - (v_partition_interval::interval * i); v_next_partition_timestamp := v_current_partition_timestamp + (v_partition_interval::interval * i); v_final_partition_timestamp := v_next_partition_timestamp + (v_partition_interval::interval); v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, to_char(v_prev_partition_timestamp, v_datetime_string), TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, to_char(v_next_partition_timestamp, v_datetime_string), TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles edge case of changing premake immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema AND tablename = v_prev_partition_name; IF v_count > 0 THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %L AND NEW.%I < %L THEN INSERT INTO %I.%I VALUES (NEW.*);' , v_control , v_prev_partition_timestamp , v_control , v_prev_partition_timestamp + v_partition_interval::interval , v_parent_schema , v_prev_partition_name); END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema AND tablename = v_next_partition_name; IF v_count > 0 THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %L AND NEW.%I < %L THEN INSERT INTO %I.%I VALUES (NEW.*);' , v_control , v_next_partition_timestamp , v_control , v_final_partition_timestamp , v_parent_schema , v_next_partition_name); END IF; END LOOP; v_trig_func := v_trig_func||format(' ELSE v_partition_name := @extschema@.check_name_length(%L, to_char(v_partition_timestamp, %L), TRUE); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = %L AND tablename = v_partition_name; IF v_count > 0 THEN EXECUTE format(''INSERT INTO %%I.%%I VALUES($1.*)'', %L, v_partition_name) USING NEW; ELSE RETURN NEW; END IF; END IF;' , v_parent_tablename , v_datetime_string , v_parent_schema , v_parent_schema); v_trig_func := v_trig_func ||' END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for current time interval: %s to %s' , v_current_partition_timestamp , v_final_partition_timestamp-'1sec'::interval)); END IF; ELSIF v_type = 'time-custom' THEN v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_child_schemaname text; v_child_table text; v_child_tablename text; BEGIN SELECT child_table INTO v_child_table FROM @extschema@.custom_time_partitions WHERE partition_range @> NEW.%I AND parent_table = %L; SELECT schemaname, tablename INTO v_child_schemaname, v_child_tablename FROM pg_catalog.pg_tables WHERE schemaname ||''.''|| tablename = v_child_table; IF v_child_schemaname IS NOT NULL AND v_child_tablename IS NOT NULL THEN EXECUTE format(''INSERT INTO %%I.%%I VALUES ($1.*)'', v_child_schemaname, v_child_tablename) USING NEW; ELSE RETURN NEW; END IF; RETURN NULL; END $t$;' , v_parent_schema , v_function_name , v_control , v_parent_schema||'.'||v_parent_tablename); EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for custom time table: %s', p_parent_table)); END IF; ELSE RAISE EXCEPTION 'ERROR: Invalid time partitioning type given: %', v_type; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; 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 FUNCTION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''Partition function maintenance for table %s 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 $$; /* * Function to turn a table into the parent of a partition set */ CREATE OR REPLACE FUNCTION 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_use_run_maintenance boolean DEFAULT NULL , p_start_partition text DEFAULT NULL , p_inherit_fk boolean DEFAULT true , p_jobmon boolean DEFAULT true , p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_base_timestamp timestamp; v_count int := 1; v_datetime_string text; v_higher_parent text := p_parent_table; v_id_interval bigint; v_id_position int; v_job_id bigint; v_jobmon_schema text; v_last_partition_created boolean; v_max bigint; v_notnull boolean; v_old_search_path text; v_parent_partition_id bigint; v_parent_partition_timestamp timestamp; v_parent_schema text; v_parent_tablename text; v_partition_time timestamp; v_partition_time_array timestamp[]; v_partition_id_array bigint[]; v_row record; v_run_maint boolean; v_sql text; v_start_time timestamp; v_starting_partition_id bigint; v_step_id bigint; v_step_overflow_id bigint; v_sub_parent text; v_success boolean := false; v_time_interval interval; v_time_position int; v_top_datetime_string text; v_top_parent text := p_parent_table; v_top_schemaname text; v_top_tablename text; BEGIN IF position('.' in p_parent_table) = 0 THEN RAISE EXCEPTION 'Parent table must be schema qualified'; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname || '.' || tablename = p_parent_table; IF v_parent_tablename IS NULL THEN RAISE EXCEPTION 'Please create given 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 AND n.nspname = v_parent_schema AND a.attname = p_control; IF 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; IF NOT @extschema@.check_partition_type(p_type) THEN RAISE EXCEPTION '% is not a valid partitioning type', p_type; END IF; EXECUTE format('LOCK TABLE %I.%I IN ACCESS EXCLUSIVE MODE', v_parent_schema, v_parent_tablename); IF p_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF p_use_run_maintenance IS NOT NULL THEN IF p_use_run_maintenance IS FALSE AND (p_type = 'time' OR p_type = 'time-custom') THEN RAISE EXCEPTION 'p_run_maintenance cannot be set to false for time based partitioning'; END IF; v_run_maint := p_use_run_maintenance; ELSIF p_type = 'time' OR p_type = 'time-custom' THEN v_run_maint := TRUE; ELSIF p_type = 'id' THEN v_run_maint := FALSE; ELSE RAISE EXCEPTION 'use_run_maintenance value cannot be set NULL'; END IF; 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 AND n.nspname = v_parent_schema ), sibling_children AS ( SELECT i.inhrelid::regclass::text AS tablename FROM pg_inherits i JOIN parent_table p ON i.inhparent = p.parent_oid ) 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_use_run_maintenance , sub_jobmon 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_use_run_maintenance , sub_jobmon) 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_use_run_maintenance , v_row.sub_jobmon); END LOOP; IF p_type = 'time' OR p_type = 'time-custom' 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 <> 'time-custom' THEN RAISE EXCEPTION 'Must use a predefined time interval if not using type "time-custom". See documentation.'; 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::timestamp, 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))::timestamp; 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 , control , premake , constraint_cols , datetime_string , use_run_maintenance , inherit_fk , jobmon) VALUES ( p_parent_table , p_type , v_time_interval , p_control , p_premake , p_constraint_cols , v_datetime_string , v_run_maint , p_inherit_fk , p_jobmon); 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 AND n.nspname = v_parent_schema ) SELECT n.nspname||'.'||c.relname, p.datetime_string INTO v_top_parent, v_top_datetime_string 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 IS NOT NULL THEN -- If so create the lowest possible partition that is within the boundary of the parent v_time_position := (length(p_parent_table) - position('p_' in reverse(p_parent_table))) + 2; v_parent_partition_timestamp := to_timestamp(substring(p_parent_table from v_time_position), v_top_datetime_string); 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 -- Currently unknown edge case if code gets here RAISE EXCEPTION 'No child tables created. Unexpected edge case encountered. Please report this error to author with conditions that led to it.'; END IF; END 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 p_type = 'id' THEN v_id_interval := p_interval::bigint; IF v_id_interval < 10 THEN RAISE EXCEPTION 'Interval for serial 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 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||'.'||c.relname = v_higher_parent ) SELECT n.nspname||'.'||c.relname INTO v_higher_parent 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 WHERE p.partition_type = 'id'; IF v_higher_parent IS NOT NULL THEN -- v_top_parent initially set in DECLARE v_top_parent := v_higher_parent; 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 SELECT schemaname, tablename INTO v_top_schemaname, v_top_tablename FROM pg_catalog.pg_tables WHERE schemaname||'.'||tablename = v_top_parent; v_sql := format('SELECT COALESCE(%L, max(%I)::bigint, 0) FROM %I.%I LIMIT 1' , p_start_partition::bigint , p_control , v_top_schemaname , v_top_tablename); EXECUTE v_sql INTO v_max; 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 , use_run_maintenance , inherit_fk , jobmon) VALUES ( p_parent_table , p_type , v_id_interval , p_control , p_premake , p_constraint_cols , v_run_maint , p_inherit_fk , p_jobmon); 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 AND n.nspname = v_parent_schema ) SELECT n.nspname||'.'||c.relname INTO v_top_parent 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 WHERE p.partition_type = 'id'; IF v_top_parent IS NOT NULL THEN -- Create the lowest possible partition that is within the boundary of the parent v_id_position := (length(p_parent_table) - position('p_' in reverse(p_parent_table))) + 2; v_parent_partition_id = substring(p_parent_table from v_id_position)::bigint; 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 EXCEPTION 'No child tables created. Unexpected edge case encountered. Please report this error to author with conditions that led to it.'; END IF; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating partition function'); END IF; IF p_type = 'time' OR p_type = 'time-custom' 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 p_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); 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; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; 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 $$; /* * Function to create id partitions */ CREATE OR REPLACE FUNCTION create_partition_id(p_parent_table text, p_partition_ids bigint[], p_analyze boolean DEFAULT true) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_exists text; v_grantees text[]; v_hasoids boolean; v_id bigint; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_parent_tablespace text; v_partition_interval bigint; v_partition_created boolean := false; v_partition_name text; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_sub_id_max bigint; v_sub_id_min bigint; v_unlogged char; BEGIN SELECT control , partition_interval , inherit_fk , jobmon INTO v_control , v_partition_interval , v_inherit_fk , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'id'); SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; FOREACH v_id IN ARRAY p_partition_ids LOOP -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_id_min IS NOT NULL THEN IF v_id < v_sub_id_min OR v_id > v_sub_id_max THEN CONTINUE; END IF; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_id::text, TRUE); -- If child table already exists, skip creation SELECT tablename INTO v_exists FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema AND tablename = v_partition_name; IF v_exists IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_id||' to '||(v_id + v_partition_interval)-1); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_parent_tablename AND n.nspname = v_parent_schema; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_parent_tablename AND n.nspname = v_parent_schema; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %s AND %I < %s )' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_control , v_id , v_control , v_id + v_partition_interval); EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I', v_parent_schema, v_partition_name, v_parent_schema, v_parent_tablename); FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP EXECUTE format('GRANT %s ON %I.%I TO %I' , array_to_string(v_parent_grant.types, ',') , v_parent_schema , v_partition_name , v_parent_grant.grantee); SELECT string_agg(r, ',') INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE format('REVOKE %s ON %I.%I FROM %I CASCADE' , v_revoke , v_parent_schema , v_partition_name , v_parent_grant.grantee); END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN FOR v_row IN SELECT role FROM ( SELECT DISTINCT grantee::text AS role FROM information_schema.table_privileges WHERE table_schema = v_parent_schema AND table_name = v_partition_name EXCEPT SELECT unnest(v_grantees)) x LOOP IF v_row.role IS NOT NULL THEN EXECUTE format('REVOKE ALL ON %I.%I FROM %I' , v_parent_schema , v_partition_name , v_row.role); END IF; END LOOP; END IF; EXECUTE format('ALTER TABLE %I.%I OWNER TO %I', v_parent_schema, v_partition_name, v_parent_owner); IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_control , sub_partition_type , 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_use_run_maintenance , sub_jobmon FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Subpartitioning '||v_partition_name); END IF; 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_use_run_maintenance := %L , p_inherit_fk := %L , p_jobmon := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_use_run_maintenance , v_row.sub_inherit_fk , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index WHERE parent_table = v_partition_name; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; -- end sub partitioning LOOP v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s', p_parent_table)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; PERFORM close_job(v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RETURN v_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to create a child table in a time-based partition set */ CREATE OR REPLACE FUNCTION create_partition_time (p_parent_table text, p_partition_times timestamp[], p_analyze boolean DEFAULT true) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_datetime_string text; v_exists text; v_grantees text[]; v_hasoids boolean; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_partition_created boolean := false; v_partition_name text; v_partition_suffix text; v_parent_tablespace text; v_partition_interval interval; v_partition_timestamp_end timestamp; v_partition_timestamp_start timestamp; v_quarter text; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_sub_timestamp_max timestamp; v_sub_timestamp_min timestamp; v_trunc_value text; v_time timestamp; v_type text; v_unlogged char; v_year text; BEGIN SELECT partition_type , control , partition_interval , inherit_fk , jobmon , datetime_string INTO v_type , v_control , v_partition_interval , v_inherit_fk , v_jobmon , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'time' OR partition_type = 'time-custom'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::timestamp, sub_max::timestamp INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'time'); SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_timestamp_start := v_time; BEGIN v_partition_timestamp_end := v_time + v_partition_interval; 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_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_time||' skipped'); CONTINUE; END; -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_timestamp_min IS NOT NULL THEN IF v_time < v_sub_timestamp_min OR v_time > v_sub_timestamp_max THEN CONTINUE; END IF; END IF; -- This suffix generation code is in partition_data_time() as well v_partition_suffix := to_char(v_time, v_datetime_string); v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); SELECT tablename INTO v_exists FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema AND tablename = v_partition_name; IF v_exists IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Creating new partition %s.%s with interval from %s to %s' , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end-'1sec'::interval)); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_parent_tablename AND n.nspname = v_parent_schema; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_parent_tablename AND n.nspname = v_parent_schema; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_control , v_partition_timestamp_start , v_control , v_partition_timestamp_end); EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); -- If custom time, set extra config options. IF v_type = 'time-custom' THEN INSERT INTO @extschema@.custom_time_partitions (parent_table, child_table, partition_range) VALUES ( p_parent_table, v_parent_schema||'.'||v_partition_name, tstzrange(v_partition_timestamp_start, v_partition_timestamp_end, '[)') ); END IF; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP EXECUTE format('GRANT %s ON %I.%I TO %I' , array_to_string(v_parent_grant.types, ',') , v_parent_schema , v_partition_name , v_parent_grant.grantee); SELECT string_agg(r, ',') INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE format('REVOKE %s ON %I.%I FROM %I CASCADE' , v_revoke , v_parent_schema , v_partition_name , v_parent_grant.grantee); END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN FOR v_row IN SELECT role FROM ( SELECT DISTINCT grantee::text AS role FROM information_schema.table_privileges WHERE table_schema = v_parent_schema AND table_name = v_partition_name EXCEPT SELECT unnest(v_grantees)) x LOOP IF v_row.role IS NOT NULL THEN EXECUTE format('REVOKE ALL ON %I.%I FROM %I' , v_parent_schema , v_partition_name , v_row.role); END IF; END LOOP; END IF; EXECUTE format('ALTER TABLE %I.%I OWNER TO %I', v_parent_schema, v_partition_name, v_parent_owner); IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_control , sub_partition_type , 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_use_run_maintenance , sub_jobmon FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Subpartitioning %s.%s', v_parent_schema, v_partition_name)); END IF; 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_use_run_maintenance := %L , p_inherit_fk := %L , p_jobmon := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_use_run_maintenance , v_row.sub_inherit_fk , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index WHERE parent_table = v_partition_name; END LOOP; -- end sub partitioning LOOP v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s. Attempted intervals: %s', p_parent_table, p_partition_times)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; 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; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RETURN v_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * 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. * To avoid logical complications and contention issues, ALL subpartitions must be maintained using run_maintenance(). * This means the automatic, trigger based partition creation for serial partitioning will not work if it is a subpartition. */ CREATE OR REPLACE FUNCTION create_sub_parent( p_top_parent text , p_control text , p_type text , p_interval text , p_constraint_cols text[] DEFAULT NULL , p_premake int DEFAULT 4 , p_start_partition text DEFAULT NULL , p_inherit_fk boolean DEFAULT true , p_jobmon boolean DEFAULT true , p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_last_partition text; v_row record; v_row_last_part record; v_run_maint boolean; v_sql text; v_success boolean := false; v_top_type text; BEGIN SELECT use_run_maintenance INTO v_run_maint FROM @extschema@.part_config WHERE parent_table = p_top_parent; IF v_run_maint 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; ELSIF v_run_maint = false THEN RAISE EXCEPTION 'Any parent table that will be part of a sub-partitioned set (on any level) must have use_run_maintenance set to true in part_config table, even for serial partitioning. See documentation for more info.'; END IF; FOR v_row IN -- Loop through all current children to turn them into partitioned tables SELECT partition_schemaname||'.'||partition_tablename AS child_table FROM @extschema@.show_partitions(p_top_parent) LOOP -- Just call existing create_parent() function but add the given parameters to the part_config_sub table as well 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_use_run_maintenance := %L , p_start_partition := %L , p_inherit_fk := %L , p_jobmon := %L , p_debug := %L )' , v_row.child_table , p_control , p_type , p_interval , p_constraint_cols , p_premake , true , p_start_partition , p_inherit_fk , p_jobmon , p_debug); EXECUTE v_sql; END LOOP; 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_use_run_maintenance , sub_jobmon) VALUES ( p_top_parent , p_control , p_type , p_interval , p_constraint_cols , p_premake , p_inherit_fk , true , p_jobmon); v_success := true; RETURN v_success; END $$; CREATE OR REPLACE FUNCTION create_trigger(p_parent_table text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_function_name text; v_new_length int; v_parent_schema text; v_parent_tablename text; v_trig_name text; v_trig_sql text; BEGIN SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); -- Ensure function name matches the naming pattern v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); v_trig_sql := format('CREATE TRIGGER %I BEFORE INSERT ON %I.%I FOR EACH ROW EXECUTE PROCEDURE %I.%I()' , v_trig_name , v_parent_schema , v_parent_tablename , v_parent_schema , v_function_name); EXECUTE v_trig_sql; END $$; /* * Drop constraints managed by pg_partman */ CREATE OR REPLACE FUNCTION drop_constraints(p_parent_table text, p_child_table text, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_child_schemaname text; v_child_tablename text; v_col text; v_constraint_cols text[]; v_existing_constraint_name text; v_exists boolean := FALSE; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_sql text; v_step_id bigint; BEGIN SELECT constraint_cols , jobmon INTO v_constraint_cols , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_constraint_cols IS NULL THEN RAISE EXCEPTION 'Given parent table (%) not set up for constraint management (constraint_cols is NULL)', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; SELECT schemaname, tablename INTO v_child_schemaname, v_child_tablename FROM pg_catalog.pg_tables WHERE schemaname||'.'||tablename = p_child_table; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN DROP CONSTRAINT: %s', p_parent_table)); v_step_id := add_step(v_job_id, 'Entering constraint drop loop'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; FOREACH v_col IN ARRAY v_constraint_cols LOOP SELECT con.conname INTO v_existing_constraint_name FROM pg_catalog.pg_constraint con JOIN pg_class c ON c.oid = con.conrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN pg_catalog.pg_attribute a ON con.conrelid = a.attrelid WHERE c.relname = v_child_tablename AND n.nspname = v_child_schemaname AND con.conname LIKE 'partmanconstr_%' AND con.contype = 'c' AND a.attname = v_col AND ARRAY[a.attnum] <@ con.conkey AND a.attisdropped = false; IF v_existing_constraint_name IS NOT NULL THEN v_exists := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Dropping constraint on column: %s', v_col)); END IF; v_sql := format('ALTER TABLE %I.%I DROP CONSTRAINT %I', v_child_schemaname, v_child_tablename, v_existing_constraint_name); IF p_debug THEN RAISE NOTICE 'Constraint drop query: %', v_sql; END IF; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Drop constraint query: %s', v_sql)); END IF; END IF; END LOOP; IF v_jobmon_schema IS NOT NULL AND v_exists IS FALSE THEN v_step_id := add_step(v_job_id, format('No constraints found to drop on child table: %s', p_child_table)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; 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 DROP CONSTRAINT: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to drop child tables from an id-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ CREATE OR REPLACE FUNCTION drop_partition_id(p_parent_table text, p_retention bigint DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_control text; v_drop_count int := 0; v_id_position int; v_index record; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_max bigint; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_partition_id bigint; v_retention bigint; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_row record; v_row_max_id record; v_step_id bigint; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman drop_partition_id')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_id already running.'; RETURN 0; END IF; -- Allow override of configuration options IF p_retention IS NULL THEN SELECT partition_interval::bigint , control , retention::bigint , retention_keep_table , retention_keep_index , retention_schema , jobmon INTO v_partition_interval , v_control , v_retention , v_retention_keep_table , v_retention_keep_index , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id' AND retention IS NOT NULL; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE SELECT partition_interval::bigint , control , retention_keep_table , retention_keep_index , retention_schema , jobmon INTO v_partition_interval , v_control , v_retention_keep_table , v_retention_keep_index , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; v_retention := p_retention; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%I) FROM %I.%I', v_control, v_row_max_id.partition_schemaname, v_row_max_id.partition_tablename) INTO v_max; IF v_max IS NOT NULL THEN EXIT; END IF; END LOOP; -- Loop through child tables of the given parent FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'ASC') LOOP v_id_position := (length(v_row.partition_tablename) - position('p_' in reverse(v_row.partition_tablename))) + 2; v_partition_id := substring(v_row.partition_tablename from v_id_position)::bigint; -- Add one interval since partition names contain the start of the constraint period IF v_retention <= (v_max - (v_partition_id + v_partition_interval)) THEN -- Only create a jobmon entry if there's actual retention work done IF v_jobmon_schema IS NOT NULL AND v_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN DROP ID PARTITION: %s', p_parent_table)); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Uninherit table %s.%s from %s', v_row.partition_schemaname, v_row.partition_tablename, p_parent_table)); END IF; EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_row.partition_schemaname , v_row.partition_tablename , v_parent_schema , v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop table %s.%s', v_row.partition_schemaname, v_row.partition_tablename)); END IF; EXECUTE format('DROP TABLE %I.%I CASCADE', v_row.partition_schemaname, v_row.partition_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN FOR v_index IN WITH child_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE c1.relname = v_row.partition_tablename AND n1.nspname = v_row.partition_schema ) SELECT c.relname as name , con.conname FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid LEFT JOIN pg_catalog.pg_constraint con ON i.indexrelid = con.conindid JOIN child_info ON i.indrelid = child_info.oid LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop index %s from %s.%s' , v_index.name , v_row.partition_schemaname , v_row.partition_tablename)); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I DROP CONSTRAINT %I', v_row.partition_schemaname, v_row.partition_tablename, v_index.conname); ELSE EXECUTE format('DROP INDEX %I.%I', v_row.partition_schemaname, v_index.name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Moving table %s.%s to schema %s' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema)); END IF; EXECUTE format('ALTER TABLE %I.%I SET SCHEMA %I' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if -- If child table is a subpartition, remove it from part_config & part_config_sub (should cascade due to FK) DELETE FROM @extschema@.part_config WHERE parent_table = v_row.partition_schemaname ||'.'||v_row.partition_tablename; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', format('%s partitions dropped.', v_drop_count)); PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RETURN v_drop_count; 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 DROP ID PARTITION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to drop child tables from a time-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ CREATE OR REPLACE FUNCTION drop_partition_time(p_parent_table text, p_retention interval DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_datetime_string text; v_drop_count int := 0; v_index record; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_partition_timestamp timestamp; v_quarter text; v_retention interval; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_row record; v_step_id bigint; v_time_position int; v_type text; v_year text; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman drop_partition_time')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_time already running.'; RETURN 0; END IF; -- Allow override of configuration options IF p_retention IS NULL THEN SELECT partition_type , partition_interval::interval , retention::interval , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_type , v_partition_interval , v_retention , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom') AND retention IS NOT NULL; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE SELECT partition_type , partition_interval::interval , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_type , v_partition_interval , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); v_retention := p_retention; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; -- Loop through child tables of the given parent FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'DESC') LOOP -- pull out datetime portion of partition's tablename to make the next one v_time_position := (length(v_row.partition_tablename) - position('p_' in reverse(v_row.partition_tablename))) + 2; IF v_partition_interval <> '3 months' OR (v_partition_interval = '3 months' AND v_type = 'time-custom') THEN v_partition_timestamp := to_timestamp(substring(v_row.partition_tablename from v_time_position), v_datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(v_row.partition_tablename from v_time_position), 'q', 1); v_quarter := split_part(substring(v_row.partition_tablename from v_time_position), 'q', 2); CASE WHEN v_quarter = '1' THEN v_partition_timestamp := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_partition_timestamp := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_partition_timestamp := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_partition_timestamp := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; -- Add one interval since partition names contain the start of the constraint period IF v_retention < (CURRENT_TIMESTAMP - (v_partition_timestamp + v_partition_interval)) THEN -- Only create a jobmon entry if there's actual retention work done IF v_jobmon_schema IS NOT NULL AND v_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN DROP TIME PARTITION: %s', p_parent_table)); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Uninherit table %s.%s from %s' , v_row.partition_schemaname , v_row.partition_tablename , p_parent_table)); END IF; EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_row.partition_schemaname , v_row.partition_tablename , v_parent_schema , v_parent_tablename); IF v_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_row.partition_schemaname||'.'||v_row.partition_tablename; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop table %s.%s', v_row.partition_schemaname, v_row.partition_tablename)); END IF; EXECUTE format('DROP TABLE %I.%I CASCADE', v_row.partition_schemaname, v_row.partition_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN FOR v_index IN WITH child_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE c1.relname = v_row.partition_tablename AND n1.nspname = v_row.partition_schemaname ) SELECT c.relname as name , con.conname FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid LEFT JOIN pg_catalog.pg_constraint con ON i.indexrelid = con.conindid JOIN child_info ON i.indrelid = child_info.oid LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop index %s from %s.%s' , v_index.name , v_row.partition_schemaname , v_row.partition_tablename)); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I DROP CONSTRAINT %I' , v_row.partition_schemaname , v_row.partition_tablename , v_index.conname); ELSE EXECUTE format('DROP INDEX %I.%I', v_parent_schema, v_index.name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Moving table %s.%s to schema %s' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema)); END IF; EXECUTE format('ALTER TABLE %I.%I SET SCHEMA %I', v_row.partition_schemaname, v_row.partition_tablename, v_retention_schema); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if -- If child table is a subpartition, remove it from part_config & part_config_sub (should cascade due to FK) DELETE FROM @extschema@.part_config WHERE parent_table = v_row.partition_schemaname||'.'||v_row.partition_tablename; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', format('%s partitions dropped.', v_drop_count)); PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RETURN v_drop_count; 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 DROP TIME PARTITION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Populate the child table(s) of an id-based partition set with old data from the original parent */ CREATE OR REPLACE FUNCTION partition_data_id(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval int DEFAULT NULL, p_lock_wait numeric DEFAULT 0, p_order text DEFAULT 'ASC') RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_current_partition_name text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_id bigint; v_min_partition_id bigint; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_partition_id bigint[]; v_rowcount bigint; v_sql text; v_start_control bigint; v_total_rows bigint := 0; BEGIN SELECT partition_interval::bigint , control INTO v_partition_interval , v_control FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; FOR i IN 1..p_batch_count LOOP IF p_order = 'ASC' THEN EXECUTE format('SELECT min(%I) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; IF v_start_control IS NULL THEN EXIT; END IF; v_min_partition_id = v_start_control - (v_start_control % v_partition_interval); v_partition_id := ARRAY[v_min_partition_id]; -- Check if custom batch interval overflows current partition maximum IF (v_start_control + p_batch_interval) >= (v_min_partition_id + v_partition_interval) THEN v_max_partition_id := v_min_partition_id + v_partition_interval; ELSE v_max_partition_id := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN EXECUTE 'SELECT max('||v_control||') FROM ONLY '||p_parent_table INTO v_start_control; IF v_start_control IS NULL THEN EXIT; END IF; v_min_partition_id = v_start_control - (v_start_control % v_partition_interval); -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_id := v_min_partition_id + v_partition_interval; v_partition_id := ARRAY[v_min_partition_id]; -- Make sure minimum doesn't underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_id THEN v_min_partition_id = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN v_sql := format('SELECT * FROM ONLY %I.%I WHERE %I >= %s AND %I < %s FOR UPDATE NOWAIT' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_id , v_control , v_max_partition_id); EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; PERFORM @extschema@.create_partition_id(p_parent_table, v_partition_id); v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_min_partition_id::text, TRUE); EXECUTE format('WITH partition_data AS ( DELETE FROM ONLY %I.%I WHERE %I >= %s AND %I < %s RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_id , v_control , v_max_partition_id , v_parent_schema , v_current_partition_name); GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; PERFORM @extschema@.create_function_id(p_parent_table); RETURN v_total_rows; END $$; /* * Populate the child table(s) of a time-based partition set with old data from the original parent */ CREATE OR REPLACE FUNCTION partition_data_time(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval interval DEFAULT NULL, p_lock_wait numeric DEFAULT 0, p_order text DEFAULT 'ASC') RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_datetime_string text; v_current_partition_name text; v_max_partition_timestamp timestamp; v_last_partition text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_min_partition_timestamp timestamp; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_partition_suffix text; v_partition_timestamp timestamp[]; v_quarter text; v_rowcount bigint; v_sql text; v_start_control timestamp; v_time_position int; v_total_rows bigint := 0; v_type text; v_year text; BEGIN SELECT partition_type , partition_interval::interval , control , datetime_string INTO v_type , v_partition_interval , v_control , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; FOR i IN 1..p_batch_count LOOP IF p_order = 'ASC' THEN EXECUTE format('SELECT min(%I) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSIF p_order = 'DESC' THEN EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; IF v_start_control IS NULL THEN EXIT; END IF; IF v_type = 'time' THEN CASE WHEN v_partition_interval = '15 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '15min'::interval * floor(date_part('minute', v_start_control) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '30min'::interval * floor(date_part('minute', v_start_control) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control); WHEN v_partition_interval = '1 day' THEN v_min_partition_timestamp := date_trunc('day', v_start_control); WHEN v_partition_interval = '1 week' THEN v_min_partition_timestamp := date_trunc('week', v_start_control); WHEN v_partition_interval = '1 month' THEN v_min_partition_timestamp := date_trunc('month', v_start_control); WHEN v_partition_interval = '3 months' THEN v_min_partition_timestamp := date_trunc('quarter', v_start_control); WHEN v_partition_interval = '1 year' THEN v_min_partition_timestamp := date_trunc('year', v_start_control); END CASE; ELSIF v_type = 'time-custom' THEN -- Keep going backwards, checking if the time interval encompases the current v_start_control value v_time_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; v_min_partition_timestamp := to_timestamp(substring(v_last_partition from v_time_position), v_datetime_string); v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; LOOP IF v_start_control >= v_min_partition_timestamp AND v_start_control < v_max_partition_timestamp THEN EXIT; ELSE v_max_partition_timestamp := v_min_partition_timestamp; BEGIN v_min_partition_timestamp := v_min_partition_timestamp - v_partition_interval; EXCEPTION WHEN datetime_field_overflow THEN RAISE EXCEPTION 'Attempted partition time interval is outside PostgreSQL''s supported time range. Unable to create partition with interval before timestamp % ', v_min_partition_interval; END; END IF; END LOOP; END IF; v_partition_timestamp := ARRAY[v_min_partition_timestamp]; IF p_order = 'ASC' THEN IF (v_start_control + p_batch_interval) >= (v_min_partition_timestamp + v_partition_interval) THEN v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; ELSE v_max_partition_timestamp := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; -- Make sure minimum doesn't underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_timestamp THEN v_min_partition_timestamp = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN v_sql := format('SELECT * FROM ONLY %I.%I WHERE %I >= %L AND %I < %L FOR UPDATE NOWAIT' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_timestamp , v_control , v_max_partition_timestamp); EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; PERFORM @extschema@.create_partition_time(p_parent_table, v_partition_timestamp); -- This suffix generation code is in create_partition_time() as well v_partition_suffix := to_char(v_min_partition_timestamp, v_datetime_string); v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); v_sql := format('WITH partition_data AS ( DELETE FROM ONLY %I.%I WHERE %I >= %L AND %I < %L RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_timestamp , v_control , v_max_partition_timestamp , v_parent_schema , v_current_partition_name); EXECUTE v_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; PERFORM @extschema@.create_function_time(p_parent_table); RETURN v_total_rows; END $$; /* * Function to re-apply ownership & privileges on all child tables in a partition set using parent table as reference */ CREATE OR REPLACE FUNCTION reapply_privileges(p_parent_table text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_child_owner text; v_child_grant record; v_grant text; v_grantees text[]; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_match boolean; v_old_search_path text; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_owner_sql text; v_revoke text; v_row record; v_row_revoke record; v_parent_grant record; v_sql text; v_step_id bigint; BEGIN SELECT jobmon INTO v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon IS NULL THEN RAISE EXCEPTION 'Given table is not managed by this extention: %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN RE-APPLYING PRIVILEGES TO ALL CHILD TABLES OF: %s', p_parent_table)); v_step_id := add_step(v_job_id, 'Setting new child table privileges'); END IF; SELECT schemaname, tablename, tableowner INTO v_parent_schema, v_parent_tablename, v_parent_owner FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'ASC') LOOP IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'PENDING', format('Currently on child partition in ascending order: %s.%s' , v_row.partition_schemaname , v_row.partition_tablename)); END IF; v_grantees := NULL; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types , grantee FROM information_schema.table_privileges WHERE table_schema = v_parent_schema AND table_name = v_parent_tablename GROUP BY grantee LOOP -- Compare parent & child grants. Don't re-apply if it already exists v_match := false; FOR v_child_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types , grantee FROM information_schema.table_privileges WHERE table_schema = v_row.partition_schemaname AND table_name = v_row.partition_tablename GROUP BY grantee LOOP IF v_parent_grant.types = v_child_grant.types AND v_parent_grant.grantee = v_child_grant.grantee THEN v_match := true; END IF; END LOOP; IF v_match = false THEN EXECUTE format('GRANT %s ON %I.%I TO %I' , array_to_string(v_parent_grant.types, ',') , v_row.partition_schemaname , v_row.partition_tablename , v_parent_grant.grantee); SELECT string_agg(r, ',') INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE format('REVOKE %s ON %I.%I FROM %I CASCADE' , v_revoke , v_row.partition_schemaname , v_row.partition_tablename , v_parent_grant.grantee); END IF; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN FOR v_row_revoke IN SELECT role FROM ( SELECT DISTINCT grantee::text AS role FROM information_schema.table_privileges WHERE table_schema = v_row.partition_schemaname AND table_name = v_row.partition_tablename EXCEPT SELECT unnest(v_grantees)) x LOOP IF v_row_revoke.role IS NOT NULL THEN EXECUTE format('REVOKE ALL ON %I.%I FROM %I' , v_row.partition_schemaname , v_row.partition_tablename , v_row_revoke.role); END IF; END LOOP; END IF; SELECT tableowner INTO v_child_owner FROM pg_tables WHERE schemaname = v_row.partition_schemaname AND tablename = v_row.partition_tablename; IF v_parent_owner <> v_child_owner THEN EXECUTE format('ALTER TABLE %I.%I OWNER TO %I' , v_row.partition_schemaname , v_row.partition_tablename , v_parent_owner); END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); PERFORM close_job(v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; 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 RE-APPLYING PRIVILEGES TO ALL CHILD TABLES OF: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to manage pre-creation of the next partitions in a set. * Also manages dropping old partitions if the retention option is set. * If p_parent_table is passed, will only run run_maintenance() on that one table (no matter what the configuration table may have set for it) * Otherwise, will run on all tables in the config table with p_run_maintenance() set to true. * For large partition sets, running analyze can cause maintenance to take longer than expected. Can set p_analyze to false to avoid a forced analyze run. * Be aware that constraint exclusion may not work properly until an analyze on the partition set is run. */ CREATE OR REPLACE FUNCTION run_maintenance(p_parent_table text DEFAULT NULL, p_analyze boolean DEFAULT true, p_jobmon boolean DEFAULT true) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_create_count int := 0; v_current_partition text; v_current_partition_id bigint; v_current_partition_timestamp timestamp; v_datetime_string text; v_drop_count int := 0; v_id_position int; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_created boolean; v_last_partition_id bigint; v_last_partition_timestamp timestamp; v_next_partition_id bigint; v_next_partition_timestamp timestamp; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_premade_count int; v_premake_id_max bigint; v_premake_id_min bigint; v_premake_timestamp_min timestamp; v_premake_timestamp_max timestamp; v_quarter text; v_row record; v_row_max_id record; v_row_sub record; v_skip_maint boolean; v_step_id bigint; v_step_overflow_id bigint; v_step_serial_id bigint; v_sub_id_max bigint; v_sub_id_max_suffix bigint; v_sub_id_min bigint; v_sub_parent text; v_sub_timestamp_max timestamp; v_sub_timestamp_max_suffix timestamp; v_sub_timestamp_min timestamp; v_tablename text; v_tables_list_sql text; v_time_position int; v_year text; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman maintenance already running.'; RETURN; END IF; 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' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN RUN MAINTENANCE'); v_step_id := add_step(v_job_id, 'Running maintenance loop'); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_tables_list_sql := 'SELECT parent_table , partition_type , partition_interval , control , premake , datetime_string , undo_in_progress FROM @extschema@.part_config WHERE sub_partition_set_full = false'; IF p_parent_table IS NULL THEN v_tables_list_sql := v_tables_list_sql || ' AND use_run_maintenance = true'; ELSE v_tables_list_sql := v_tables_list_sql || format(' AND parent_table = %L', p_parent_table); END IF; FOR v_row IN EXECUTE v_tables_list_sql LOOP CONTINUE WHEN v_row.undo_in_progress; v_skip_maint := true; -- reset every loop SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LIMIT 1; IF v_row.partition_type = 'time' OR v_row.partition_type = 'time-custom' THEN v_time_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; IF v_row.partition_interval::interval <> '3 months' OR (v_row.partition_interval::interval = '3 months' AND v_row.partition_type = 'time-custom') THEN v_last_partition_timestamp := to_timestamp(substring(v_last_partition from v_time_position), v_row.datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(v_last_partition FROM v_time_position), 'q', 1); v_quarter := split_part(substring(v_last_partition FROM v_time_position), 'q', 2); CASE WHEN v_quarter = '1' THEN v_last_partition_timestamp := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_last_partition_timestamp := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_last_partition_timestamp := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_last_partition_timestamp := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; -- If this is a subpartition, determine if the last child table has been made. If so, mark it as full so future maintenance runs can skip it SELECT sub_min::timestamp, sub_max::timestamp INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'time'); IF v_sub_timestamp_max IS NOT NULL THEN SELECT suffix_timestamp INTO v_sub_timestamp_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_timestamp_max::text); IF v_sub_timestamp_max_suffix = v_last_partition_timestamp THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; SELECT suffix_timestamp INTO v_current_partition_timestamp FROM @extschema@.show_partition_name(v_row.parent_table, CURRENT_TIMESTAMP::text); -- Check and see how many premade partitions there are. v_premade_count = round(EXTRACT('epoch' FROM age(v_last_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); v_next_partition_timestamp := v_last_partition_timestamp; -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed. -- premake_count can be negative with subpartitioning when running against old or future sub-partition sets. -- If so, do not run to avoid recreating dropped partitions due to retention or pre-creating before they're needed WHILE (v_premade_count >= 0) AND (v_premade_count < v_row.premake) LOOP IF v_next_partition_timestamp < v_sub_timestamp_min OR v_next_partition_timestamp > v_sub_timestamp_max THEN -- With subpartitioning, no need to run if the timestamp is not in the parent table's range EXIT; END IF; BEGIN v_next_partition_timestamp := v_next_partition_timestamp + v_row.partition_interval::interval; EXCEPTION WHEN datetime_field_overflow THEN v_premade_count := v_row.premake; -- do this so it can exit the premake check loop and continue in the outer for loop IF v_jobmon_schema IS NOT NULL THEN 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 skippd for parent table '||v_partition_time); END IF; RAISE WARNING 'Attempted partition time interval is outside PostgreSQL''s supported time range. Child partition creation skipped for parent table %', v_row.parent_table; CONTINUE; END; v_last_partition_created := @extschema@.create_partition_time(v_row.parent_table, ARRAY[v_next_partition_timestamp], p_analyze); IF v_last_partition_created THEN v_create_count := v_create_count + 1; PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table := v_row.parent_table, p_job_id := v_job_id); END IF; v_premade_count = round(EXTRACT('epoch' FROM age(v_next_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); END LOOP; ELSIF v_row.partition_type = 'id' THEN -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP EXECUTE format('SELECT %I - (%I %% %L) FROM %I.%I WHERE %I = (SELECT max(%I) FROM %I.%I)' , v_row.control , v_row.control , v_row.partition_interval::int , v_row_max_id.partition_schemaname , v_row_max_id.partition_tablename , v_row.control , v_row.control , v_row_max_id.partition_schemaname , v_row_max_id.partition_tablename ) INTO v_current_partition_id; IF v_current_partition_id IS NOT NULL THEN EXIT; END IF; END LOOP; v_id_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; v_last_partition_id = substring(v_last_partition from v_id_position)::bigint; -- Determine if this table is a child of a subpartition parent. If so, get limits to see if run_maintenance even needs to run for it. SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'id'); IF v_sub_id_max IS NOT NULL THEN SELECT suffix_id INTO v_sub_id_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_id_max::text); IF v_sub_id_max_suffix = v_last_partition_id THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; v_next_partition_id := v_last_partition_id; v_premade_count := ((v_last_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed. -- premake_count can be negative with subpartitioning when running against old or future sub-partition sets. -- If so, do not run to avoid recreating dropped partitions due to retention or pre-creating before they're needed WHILE (v_premade_count >= 0) AND (v_premade_count < v_row.premake) LOOP IF v_next_partition_id < v_sub_id_min OR v_next_partition_id > v_sub_id_max THEN -- With subpartitioning, no need to run if the id is not in the parent table's range EXIT; END IF; v_next_partition_id := v_next_partition_id + v_row.partition_interval::bigint; v_last_partition_created := @extschema@.create_partition_id(v_row.parent_table, ARRAY[v_next_partition_id], p_analyze); IF v_last_partition_created THEN v_create_count := v_create_count + 1; PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); PERFORM @extschema@.apply_constraints(p_parent_table := v_row.parent_table, p_job_id := v_job_id); END IF; v_premade_count := ((v_next_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); END LOOP; END IF; -- end main IF check for time or id END LOOP; -- end of creation loop -- Manage dropping old partitions if retention option is set FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND (partition_type = 'time' OR partition_type = 'time-custom') LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); END IF; END IF; IF v_drop_count > 0 THEN PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); END IF; END LOOP; FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND partition_type = 'id' LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); END IF; END IF; IF v_drop_count > 0 THEN PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Partition maintenance finished. %s partitions made. %s partitions dropped.', v_create_count, v_drop_count)); IF v_step_overflow_id IS NOT NULL OR v_step_serial_id IS NOT NULL THEN PERFORM fail_job(v_job_id); ELSE PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; 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 RUN MAINTENANCE'')', v_jobmon_schema) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Given a parent table and partition value, return the name of the child partition it would go in. * Also returns just the suffix value and true if the child table exists or false if it does not */ CREATE FUNCTION show_partition_name(p_parent_table text, p_value text, OUT partition_table text, OUT suffix_timestamp timestamp, OUT suffix_id bigint, OUT table_exists boolean) RETURNS record LANGUAGE plpgsql STABLE AS $$ DECLARE v_child_exists text; v_datetime_string text; v_max_range timestamptz; v_min_range timestamptz; v_parent_schema text; v_parent_tablename text; v_partition_interval text; v_time_position int; v_type text; BEGIN SELECT partition_type , partition_interval , datetime_string INTO v_type , v_partition_interval , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_type IS NULL THEN RAISE EXCEPTION 'Parent table given is not managed by pg_partman (%)', p_parent_table; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; IF v_parent_tablename IS NULL THEN RAISE EXCEPTION 'Parent table given does not exist (%)', p_parent_table; END IF; IF v_type = 'time' THEN CASE WHEN v_partition_interval::interval = '15 mins' THEN suffix_timestamp := date_trunc('hour', p_value::timestamp) + '15min'::interval * floor(date_part('minute', p_value::timestamp) / 15.0); WHEN v_partition_interval::interval = '30 mins' THEN suffix_timestamp := date_trunc('hour', p_value::timestamp) + '30min'::interval * floor(date_part('minute', p_value::timestamp) / 30.0); WHEN v_partition_interval::interval = '1 hour' THEN suffix_timestamp := date_trunc('hour', p_value::timestamp); WHEN v_partition_interval::interval = '1 day' THEN suffix_timestamp := date_trunc('day', p_value::timestamp); WHEN v_partition_interval::interval = '1 week' THEN suffix_timestamp := date_trunc('week', p_value::timestamp); WHEN v_partition_interval::interval = '1 month' THEN suffix_timestamp := date_trunc('month', p_value::timestamp); WHEN v_partition_interval::interval = '3 months' THEN suffix_timestamp := date_trunc('quarter', p_value::timestamp); WHEN v_partition_interval::interval = '1 year' THEN suffix_timestamp := date_trunc('year', p_value::timestamp); END CASE; partition_table := v_parent_schema||'.'||@extschema@.check_name_length(v_parent_tablename, to_char(suffix_timestamp, v_datetime_string), TRUE); ELSIF v_type = 'id' THEN suffix_id := (p_value::bigint - (p_value::bigint % v_partition_interval::bigint)); partition_table := v_parent_schema||'.'||@extschema@.check_name_length(v_parent_tablename, suffix_id::text, TRUE); ELSIF v_type = 'time-custom' THEN SELECT child_table, lower(partition_range) INTO partition_table, suffix_timestamp FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND partition_range @> p_value::timestamptz; IF partition_table IS NULL THEN SELECT max(upper(partition_range)) INTO v_max_range FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table; SELECT min(lower(partition_range)) INTO v_min_range FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table; IF p_value::timestamp >= v_max_range THEN suffix_timestamp := v_max_range; LOOP -- Keep incrementing higher until given value is below the upper range suffix_timestamp := suffix_timestamp + v_partition_interval::interval; IF p_value::timestamp < suffix_timestamp THEN -- Have to subtract one interval because the value would actually be in the partition previous -- to this partition timestamp since the partition names contain the lower boundary suffix_timestamp := suffix_timestamp - v_partition_interval::interval; EXIT; END IF; END LOOP; ELSIF p_value::timestamp < v_min_range THEN suffix_timestamp := v_min_range; LOOP -- Keep decrementing lower until given value is below or equal to the lower range suffix_timestamp := suffix_timestamp - v_partition_interval::interval; IF p_value::timestamp >= suffix_timestamp THEN EXIT; END IF; END LOOP; ELSE RAISE EXCEPTION 'Unable to determine a valid child table for the given parent table and value'; END IF; partition_table := v_parent_schema||'.'||@extschema@.check_name_length(v_parent_tablename, to_char(suffix_timestamp, v_datetime_string), TRUE); END IF; END IF; SELECT schemaname, tablename INTO v_child_exists FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = partition_table; IF v_child_exists IS NOT NULL THEN table_exists := true; ELSE table_exists := false; END IF; RETURN; END $$; /* * Function to list all child partitions in a set. */ CREATE FUNCTION show_partitions (p_parent_table text, p_order text DEFAULT 'ASC') RETURNS TABLE (partition_schemaname text, partition_tablename text) LANGUAGE plpgsql STABLE SECURITY DEFINER AS $$ DECLARE v_datetime_string text; v_parent_schema text; v_parent_tablename text; v_partition_interval text; v_type text; BEGIN IF p_order NOT IN ('ASC', 'DESC') THEN RAISE EXCEPTION 'p_order paramter must be one of the following values: ASC, DESC'; END IF; SELECT partition_type , partition_interval , datetime_string INTO v_type , v_partition_interval , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname||'.'||tablename = p_parent_table; IF v_type IN ('time', 'time-custom') THEN RETURN QUERY EXECUTE format('SELECT n.nspname::text AS partition_schemaname, c.relname::text AS partition_name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = ''%I.%I''::regclass ORDER BY to_timestamp(substring(c.relname from ((length(c.relname) - position(''p_'' in reverse(c.relname))) + 2) ), %L) %s' , v_parent_schema , v_parent_tablename , v_datetime_string , p_order); ELSIF v_type = 'id' THEN RETURN QUERY EXECUTE format('SELECT n.nspname::text AS partition_schemaname, c.relname::text AS partition_name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = ''%I.%I''::regclass ORDER BY substring(c.relname from ((length(c.relname) - position(''p_'' in reverse(c.relname))) + 2) )::bigint %s' , v_parent_schema , v_parent_tablename , p_order); END IF; END $$; /* * Function to undo partitioning. * Will actually work on any parent/child table set, not just ones created by pg_partman. */ CREATE OR REPLACE FUNCTION undo_partition(p_parent_table text, p_batch_count int DEFAULT 1, p_keep_table boolean DEFAULT true, p_jobmon boolean DEFAULT true, p_lock_wait numeric DEFAULT 0) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_batch_loop_count bigint := 0; v_child_count bigint; v_child_table text; v_copy_sql text; v_function_name text; v_job_id bigint; v_jobmon_schema text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_rowcount bigint; v_step_id bigint; v_total bigint := 0; v_trig_name text; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition already running.'; RETURN 0; END IF; IF p_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN UNDO PARTITIONING: ', p_parent_table)); v_step_id := add_step(v_job_id, format('Undoing partitioning for table ', p_parent_table)); END IF; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables and stop new id partitions from being made. SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t WHERE tgname = v_trig_name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_parent_tablename); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; RETURN -1; END IF; END IF; -- END p_lock_wait IF EXECUTE format('DROP TRIGGER IF EXISTS %I ON %I.%I', v_trig_name, v_parent_schema, v_parent_tablename); END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE format('DROP FUNCTION IF EXISTS %I.%I()', v_parent_schema, v_function_name); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); END IF; WHILE v_batch_loop_count < p_batch_count LOOP -- Get ordered list of child table in set. Store in variable one at a time per loop until none are left. WITH parent_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE c1.relname = v_parent_tablename AND n1.nspname = v_parent_schema ) SELECT c.relname INTO v_child_table FROM pg_catalog.pg_inherits i JOIN pg_catalog.pg_class c ON i.inhrelid = c.oid JOIN parent_info p ON i.inhparent = p.oid ORDER BY i.inhrelid ASC; EXIT WHEN v_child_table IS NULL; EXECUTE format('SELECT count(*) FROM %I.%I', v_parent_schema, v_child_table) INTO v_child_count; IF v_child_count = 0 THEN -- No rows left in this child table. Remove from partition set. -- lockwait timeout for table drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_child_table); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; RETURN -1; END IF; END IF; -- END p_lock_wait IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I', v_parent_schema, v_child_table, v_parent_schema, v_parent_tablename); IF p_keep_table = false THEN EXECUTE format('DROP TABLE %I.%I', v_parent_schema, v_child_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table DROPPED. Moved %s rows to parent', COALESCE(v_rowcount, 0))); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table UNINHERITED, not DROPPED. Copied %s rows to parent', COALESCE(v_rowcount, 0))); END IF; END IF; v_undo_count := v_undo_count + 1; CONTINUE; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Removing child partition: %s.%s', v_parent_schema, v_child_table)); END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM %I.%I FOR UPDATE NOWAIT', v_parent_schema, v_child_table); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; RETURN -1; END IF; END IF; v_copy_sql := format('INSERT INTO %I.%I SELECT * FROM %I.%I' , v_parent_schema , v_parent_tablename , v_parent_schema , v_child_table); EXECUTE v_copy_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_parent_schema , v_child_table , v_parent_schema , v_parent_tablename); IF p_keep_table = false THEN EXECUTE format('DROP TABLE %I.%I', v_parent_schema, v_child_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table DROPPED. Moved %s rows to parent', v_rowcount)); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table UNINHERITED, not DROPPED. Copied %s rows to parent', v_rowcount)); END IF; END IF; v_batch_loop_count := v_batch_loop_count + 1; v_undo_count := v_undo_count + 1; END LOOP; IF v_undo_count = 0 THEN -- FOR loop never ran, so there's no child tables left. DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman (if it existed)'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) from % child table(s) to the parent: %', v_total, v_undo_count, p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', format('Copied %s row(s) from %s child table(s) to the parent', v_total, v_undo_count)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RETURN v_total; 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 UNDO PARTITIONING: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to undo id-based partitioning created by this extension */ CREATE OR REPLACE FUNCTION undo_partition_id(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval bigint DEFAULT NULL, p_keep_table boolean DEFAULT true, p_lock_wait numeric DEFAULT 0) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_batch_loop_count int := 0; v_child_loop_total bigint := 0; v_child_min bigint; v_child_table text; v_control text; v_exists int; v_function_name text; v_inner_loop_count int; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_move_sql text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_row record; v_rowcount bigint; v_step_id bigint; v_sub_count int; v_trig_name text; v_total bigint := 0; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition_id')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition_id already running.'; RETURN 0; END IF; SELECT partition_interval::bigint , control , jobmon INTO v_partition_interval , v_control , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; -- Check if any child tables are themselves partitioned or part of an inheritance tree. Prevent undo at this level if so. -- Need to either lock child tables at all levels or handle the proper removal of triggers on all child tables first -- before multi-level undo can be performed safely. FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table) LOOP SELECT count(*) INTO v_sub_count FROM pg_catalog.pg_inherits i JOIN pg_catalog.pg_class c ON i.inhparent = c.oid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_row.partition_tablename AND n.nspname = v_row.partition_schemaname; IF v_sub_count > 0 THEN RAISE EXCEPTION 'Child table for this parent has child table(s) itself (%). Run undo partitioning on this table or remove inheritance first to ensure all data is properly moved to parent', v_row.partition_schemaname||'.'||v_row.partition_tablename; END IF; END LOOP; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN UNDO PARTITIONING: %s', p_parent_table)); v_step_id := add_step(v_job_id, format('Undoing partitioning for table %s', p_parent_table)); END IF; IF p_batch_interval IS NULL THEN p_batch_interval := v_partition_interval; END IF; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables and stop new id partitions from being made. SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t WHERE tgname = v_trig_name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_parent_tablename); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; RETURN -1; END IF; END IF; -- END p_lock_wait IF EXECUTE format('DROP TRIGGER IF EXISTS %I ON %I.%I', v_trig_name, v_parent_schema, v_parent_tablename); END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE format('DROP FUNCTION IF EXISTS %I.%I()', v_parent_schema, v_function_name); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); END IF; <> WHILE v_batch_loop_count < p_batch_count LOOP -- Get ordered list of child table in set. Store in variable one at a time per loop until none are left. WITH parent_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE c1.relname = v_parent_tablename AND n1.nspname = v_parent_schema ) SELECT c.relname INTO v_child_table FROM pg_catalog.pg_inherits i JOIN pg_catalog.pg_class c ON i.inhrelid = c.oid JOIN parent_info p ON i.inhparent = p.oid ORDER BY i.inhrelid ASC; EXIT WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Removing child partition: %s.%s', v_parent_schema, v_child_table)); END IF; EXECUTE format('SELECT min(%I) FROM %I.%I', v_control, v_parent_schema, v_child_table) INTO v_child_min; IF v_child_min IS NULL THEN -- No rows left in this child table. Remove from partition set. -- lockwait timeout for table drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_child_table); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; RETURN -1; END IF; END IF; -- END p_lock_wait IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_parent_schema , v_child_table , v_parent_schema , v_parent_tablename); IF p_keep_table = false THEN EXECUTE format('DROP TABLE %I.%I', v_parent_schema, v_child_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table DROPPED. Moved %s rows to parent', v_child_loop_total)); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table UNINHERITED, not DROPPED. Moved %s rows to parent', v_child_loop_total)); END IF; END IF; v_undo_count := v_undo_count + 1; CONTINUE outer_child_loop; END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP -- lockwait timeout for row batches IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM %I.%I WHERE %I <= %s FOR UPDATE NOWAIT' , v_parent_schema , v_child_table , v_control , v_child_min + (p_batch_interval * v_inner_loop_count)); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; RETURN -1; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval v_move_sql := format('WITH move_data AS ( DELETE FROM %I.%I WHERE %I <= %s RETURNING *) INSERT INTO %I.%I SELECT * FROM move_data' , v_parent_schema , v_child_table , v_control , v_child_min + (p_batch_interval * v_inner_loop_count) , v_parent_schema , v_parent_tablename); EXECUTE v_move_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Moved %s rows to parent.', v_child_loop_total)); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; IF v_batch_loop_count < p_batch_count THEN -- FOR loop never ran, so there's no child tables left. DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) to the parent. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', format('Copied %s row(s) to the parent. Removed %s partitions.', v_total, v_undo_count)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RETURN v_total; 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 UNDO PARTITIONING: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to undo time-based partitioning created by this extension */ CREATE OR REPLACE FUNCTION undo_partition_time(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval interval DEFAULT NULL, p_keep_table boolean DEFAULT true, p_lock_wait numeric DEFAULT 0) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_batch_loop_count int := 0; v_child_min timestamptz; v_child_loop_total bigint := 0; v_child_table text; v_control text; v_function_name text; v_inner_loop_count int; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_move_sql text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_row record; v_rowcount bigint; v_step_id bigint; v_sub_count int; v_total bigint := 0; v_trig_name text; v_type text; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition_time')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition_time already running.'; RETURN 0; END IF; SELECT partition_type , partition_interval::interval , control , jobmon INTO v_type , v_partition_interval , v_control , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; -- Check if any child tables are themselves partitioned or part of an inheritance tree. Prevent undo at this level if so. -- Need to either lock child tables at all levels or handle the proper removal of triggers on all child tables first -- before multi-level undo can be performed safely. FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table) LOOP SELECT count(*) INTO v_sub_count FROM pg_catalog.pg_inherits i JOIN pg_catalog.pg_class c ON i.inhparent = c.oid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_row.partition_tablename AND n.nspname = v_row.partition_schemaname; IF v_sub_count > 0 THEN RAISE EXCEPTION 'Child table for this parent has child table(s) itself (%). Run undo partitioning on this table or remove inheritance first to ensure all data is properly moved to parent', v_row.partition_schemaname||'.'||v_row.partition_tablename; END IF; END LOOP; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN UNDO PARTITIONING: %s', p_parent_table)); v_step_id := add_step(v_job_id, format('Undoing partitioning for table %s', p_parent_table)); END IF; IF p_batch_interval IS NULL THEN p_batch_interval := v_partition_interval; END IF; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables. SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t WHERE tgname = v_trig_name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_parent_tablename); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; RETURN -1; END IF; END IF; -- END p_lock_wait IF EXECUTE format('DROP TRIGGER IF EXISTS %I ON %I.%I', v_trig_name, v_parent_schema, v_parent_tablename); END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE format('DROP FUNCTION IF EXISTS %I.%I()', v_parent_schema, v_function_name); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); END IF; <> WHILE v_batch_loop_count < p_batch_count LOOP -- Get ordered list of child table in set. Store in variable one at a time per loop until none are left. WITH parent_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE c1.relname = v_parent_tablename AND n1.nspname = v_parent_schema ) SELECT c.relname INTO v_child_table FROM pg_catalog.pg_inherits i JOIN pg_catalog.pg_class c ON i.inhrelid = c.oid JOIN parent_info p ON i.inhparent = p.oid ORDER BY i.inhrelid ASC; EXIT WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Removing child partition: %s.%s', v_parent_schema, v_child_table)); END IF; EXECUTE format('SELECT min(%I) FROM %I.%I', v_control, v_parent_schema, v_child_table) INTO v_child_min; IF v_child_min IS NULL THEN -- No rows left in this child table. Remove from partition set. -- lockwait timeout for table drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_child_table); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; RETURN -1; END IF; END IF; -- END p_lock_wait IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_parent_schema , v_child_table , v_parent_schema , v_parent_tablename); IF p_keep_table = false THEN EXECUTE format('DROP TABLE %I.%I', v_parent_schema, v_child_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table DROPPED. Moved %s rows to parent', v_child_loop_total)); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table UNINHERITED, not DROPPED. Moved %s rows to parent', v_child_loop_total)); END IF; END IF; IF v_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_parent_schema||'.'||v_child_table; END IF; v_undo_count := v_undo_count + 1; CONTINUE outer_child_loop; END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM %I.%I WHERE %I <= %L FOR UPDATE NOWAIT' , v_parent_schema , v_child_table , v_control , v_child_min + (p_batch_interval * v_inner_loop_count)); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; RETURN -1; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval v_move_sql := format('WITH move_data AS ( DELETE FROM %I.%I WHERE %I <= %L RETURNING *) INSERT INTO %I.%I SELECT * FROM move_data' , v_parent_schema , v_child_table , v_control , v_child_min + (p_batch_interval * v_inner_loop_count) , v_parent_schema , v_parent_tablename); EXECUTE v_move_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Moved %s rows to parent.', v_child_loop_total)); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; IF v_batch_loop_count < p_batch_count THEN -- FOR loop never ran, so there's no child tables left. DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) to the parent. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', format('Copied %s row(s) to the parent. Removed %s partitions.', v_total, v_undo_count)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RETURN v_total; 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 UNDO PARTITIONING: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; -- Restore dropped object privileges DO $$ DECLARE v_row record; BEGIN FOR v_row IN SELECT statement FROM partman_preserve_privs_temp LOOP IF v_row.statement IS NOT NULL THEN EXECUTE v_row.statement; END IF; END LOOP; END $$; DROP TABLE IF EXISTS partman_preserve_privs_temp; pg_partman-4.6.0/updates/pg_partman--2.1.0--2.2.0.sql000066400000000000000000004563711412761706100214370ustar00rootroot00000000000000-- Added new optimization & constraint configuration options to part_config & part_config_sub (optimize_trigger, optimize_constraint) (Github Issue #62) -- The premake config value now only controls what its name suggests (the number of future partitions to premake) -- For existing partition sets, the values for these columns will be set equal to the current premake value -- Default value for optimize_trigger is same as default for premake (4) -- Default value for optimize_constraint is 30 (30 days, 30 months, etc) -- Not added as parameters to create_parent() or create_sub_parent() --These can be changed later and normal maintenance will take care of updating the current partition set to match. -- Time epoch partitioning is now possible (Github Issue #44) -- Control column can be an integer type but trigger, partition constraints and partition names will be based on a time interval. -- New boolean parameter to the create_parent()/create_sub_parent() functions. Ensure all of your current calls to this function account for it! -- Fixed bug introduced in v2.1.0 that caused run_maintenance() to not catch up if partition creation fell behind. (Github Issue #67) -- The fix for this issue also introduces new (improved) behavior for time-based partition maintenance. -- Previously, the premade tables were determined by the current time at the time maintenance ran. -- It is now instead based off of the maximum value in the partition set. -- This is essentially the same behavior that serial partitioning has always had. -- This means that if no new data is being inserted, once the premake value is met, no new partitions will be made. Previously they would be created no matter what, indefinitely. -- Resumption of data insertion after a large gap in data and maintenance runs may cause data to go into the parent until run_maintenance() is called again. -- New data should then go to new children, but data will then have to be cleaned out of the parent. -- This also means partitioning should work more predictably if data with a future timestamp is inserted. Previously this was not accounted for and could cause maintenance to work inconsistently. -- KNOWN BUGS: If you insert "future" data in a time-based, sub-partitioned set, maintenance may skip child tables in some partition sets depending how that data relates to the subpartition interval. -- Working on a fix for this, but didn't want to hold this release up anymore. To work around this, -- Increase the premake and/or optimize_trigger values appropriately to account for your normal data insertion window. -- Monitor for data going into parent tables and fix that as documented. -- Fixed bug in show_partitions() when used against a quarterly partitioned set. Wouldn't return all tables in expected order. -- Fixed bugs in reapply_constraints.py instroduced in v2.1.0 that prevented it from running. Increased minimum version requirement of pg_partman to 2.2.0. -- Made creation of additional column constraints more efficient (if used) and less spammy in the run_maintenance() jobmon logs. -- Added debug option to run_maintenance(). -- Fixed bug introduced in 2.1.0 that wasn't setting the retention config columns in part_config properly for multi-level subpartitions -- You may encounter an error during maintenance or during the upgrade related to inconsistent data in the part_config_sub table. There was a constraint in place before that tried to prevent this happening, but it was not doing it properly. There is now a new function in place that is part of routine maintenance that checks for this consistency. This checks to ensure that all sub-partition parents in part_config_sub that are themselves part of a sub-partition set have the same configuration values. You can run the function "check_subpart_sameconfig('parent_table')" for all sub-partitioned table sets to see which ones have inconsistent data. The function should only return a single row. If more than one row is returned then just update the config entries that are mismatched and this should clear the error. If you're still having problems, please open an Issue on Github with the error you receive and the full contents of your part_config & part_config_sub tables. -- If you encounter the error during the upgrade, run the following query using the parent table returned in the error for <<>> and setting <<>> to the schema you installed pg_partman to. Only a single row should be returned. If not then fix the mismatched data. /* WITH parent_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE n1.nspname||'.'||c1.relname = '<<>>' ) , child_tables AS ( SELECT n.nspname||'.'||c.relname AS tablename FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN parent_info pi ON h.inhparent = pi.oid ) SELECT DISTINCT a.sub_partition_type , a.sub_control , a.sub_partition_interval , a.sub_constraint_cols , a.sub_premake , a.sub_inherit_fk , a.sub_retention , a.sub_retention_schema , a.sub_retention_keep_table , a.sub_retention_keep_index , a.sub_use_run_maintenance , a.sub_jobmon FROM <<>>.part_config_sub a JOIN child_tables b on a.sub_parent = b.tablename; */ CREATE TEMP TABLE partman_preserve_privs_temp (statement text); INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.run_maintenance(text, boolean, boolean, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'run_maintenance'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_parent(text, text, text, text, text[], int, boolean, text, boolean, boolean, boolean, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_parent'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_sub_parent(text, text, text, text, text[], int, text, boolean, boolean, boolean, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_sub_parent'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.check_subpart_sameconfig(text) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'check_subpart_sameconfig'; DROP FUNCTION run_maintenance(text, boolean, boolean); DROP FUNCTION @extschema@.create_parent(text, text, text, text, text[], int, boolean, text, boolean, boolean, boolean); DROP FUNCTION @extschema@.create_sub_parent(text, text, text, text, text[], int, text, boolean, boolean, boolean); ALTER TABLE @extschema@.part_config_sub DROP CONSTRAINT subpart_sameconfig_chk; DROP FUNCTION @extschema@.check_subpart_sameconfig(text); ALTER TABLE @extschema@.part_config ADD COLUMN epoch boolean NOT NULL DEFAULT false; ALTER TABLE @extschema@.part_config ADD COLUMN optimize_trigger int NOT NULL DEFAULT 4; ALTER TABLE @extschema@.part_config ADD COLUMN optimize_constraint int NOT NULL DEFAULT 30; UPDATE @extschema@.part_config SET optimize_trigger = premake; UPDATE @extschema@.part_config SET optimize_constraint = premake; ALTER TABLE @extschema@.part_config_sub ADD COLUMN sub_epoch boolean NOT NULL DEFAULT false; ALTER TABLE @extschema@.part_config_sub ADD COLUMN sub_optimize_trigger int NOT NULL DEFAULT 4; ALTER TABLE @extschema@.part_config_sub ADD COLUMN sub_optimize_constraint int NOT NULL DEFAULT 30; UPDATE @extschema@.part_config_sub SET sub_optimize_trigger = sub_premake; UPDATE @extschema@.part_config_sub SET sub_optimize_constraint = sub_premake; /* * Check for consistent data in part_config_sub table. Was unable to get this working properly as either a constraint or trigger. * Would either delay raising an error until the next write (which I cannot predict) or disallow future edits to update a sub-partition set's configuration. * This is called by run_maintainance() and at least provides a consistent way to check that I know will run. * If anyone can get a working constraint/trigger, please help! */ CREATE FUNCTION @extschema@.check_subpart_sameconfig(p_parent_table text) RETURNS TABLE (sub_partition_type text , sub_control text , sub_partition_interval text , sub_constraint_cols text[] , sub_premake int , sub_inherit_fk boolean , sub_retention text , sub_retention_schema text , sub_retention_keep_table boolean , sub_retention_keep_index boolean , sub_use_run_maintenance boolean , sub_epoch boolean , sub_optimize_trigger int , sub_optimize_constraint int , sub_jobmon boolean) LANGUAGE sql STABLE SECURITY DEFINER AS $$ WITH parent_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE n1.nspname||'.'||c1.relname = p_parent_table ) , child_tables AS ( SELECT n.nspname||'.'||c.relname AS tablename FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN parent_info pi ON h.inhparent = pi.oid ) -- Column order here must match the RETURNS TABLE definition SELECT DISTINCT a.sub_partition_type , a.sub_control , a.sub_partition_interval , a.sub_constraint_cols , a.sub_premake , a.sub_inherit_fk , a.sub_retention , a.sub_retention_schema , a.sub_retention_keep_table , a.sub_retention_keep_index , a.sub_use_run_maintenance , a.sub_epoch , a.sub_optimize_trigger , a.sub_optimize_constraint , a.sub_jobmon FROM @extschema@.part_config_sub a JOIN child_tables b on a.sub_parent = b.tablename; $$; -- Do a check here to ensure configuration is consistent using above new function DO $$ DECLARE v_count int; v_row record; BEGIN FOR v_row IN SELECT sub_parent FROM @extschema@.part_config_sub LOOP SELECT count(*) INTO v_count FROM @extschema@.check_subpart_sameconfig(v_row.sub_parent); IF v_count > 1 THEN RAISE EXCEPTION 'UPDATE ABORTED: Inconsistent data found in part_config_sub table for parent table %. Please see 2.2.0 release notes for why this occurred and how to fix it.', v_row.sub_parent; END IF; END LOOP; END $$; /* * Create the trigger function for the parent table of an id-based partition set */ CREATE OR REPLACE FUNCTION create_function_id(p_parent_table text, p_job_id bigint DEFAULT NULL) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_control text; v_count int; v_current_partition_name text; v_current_partition_id bigint; v_datetime_string text; v_final_partition_id bigint; v_function_name text; v_higher_parent text := p_parent_table; v_id_position int; v_job_id bigint; v_jobmon text; v_jobmon_schema text; v_last_partition text; v_max bigint; v_next_partition_id bigint; v_next_partition_name text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_premake int; v_prev_partition_id bigint; v_prev_partition_name text; v_row_max_id record; v_run_maint boolean; v_step_id bigint; v_top_parent text := p_parent_table; v_trig_func text; v_optimize_trigger int; BEGIN SELECT partition_interval::bigint , control , premake , optimize_trigger , use_run_maintenance , jobmon INTO v_partition_interval , v_control , v_premake , v_optimize_trigger , v_run_maint , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE FUNCTION: %s', p_parent_table)); ELSE v_job_id = p_job_id; END IF; v_step_id := add_step(v_job_id, format('Creating partition function for table %s', p_parent_table)); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); -- Get the highest level top parent if multi-level partitioned in order to get proper max() value below WHILE v_higher_parent 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||'.'||c.relname = v_higher_parent ) SELECT n.nspname||'.'||c.relname INTO v_higher_parent 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 WHERE p.partition_type = 'id'; IF v_higher_parent IS NOT NULL THEN -- initially set in DECLARE v_top_parent := v_higher_parent; END IF; END LOOP; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_top_parent, 'DESC') LOOP EXECUTE format('SELECT max(%I) FROM %I.%I', v_control, v_row_max_id.partition_schemaname, v_row_max_id.partition_tablename) INTO v_max; IF v_max IS NOT NULL THEN EXIT; END IF; END LOOP; IF v_max IS NULL THEN v_max := 0; END IF; v_current_partition_id = v_max - (v_max % v_partition_interval); v_next_partition_id := v_current_partition_id + v_partition_interval; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_current_partition_id::text, TRUE); v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_current_partition_id bigint; v_current_partition_name text; v_id_position int; v_last_partition text := %L; v_next_partition_id bigint; v_next_partition_name text; v_partition_created boolean; BEGIN IF TG_OP = ''INSERT'' THEN IF NEW.%I >= %s AND NEW.%I < %s THEN ' , v_parent_schema , v_function_name , v_last_partition , v_control , v_current_partition_id , v_control , v_next_partition_id ); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema AND tablename = v_current_partition_name; IF v_count > 0 THEN v_trig_func := v_trig_func || format(' INSERT INTO %I.%I VALUES (NEW.*); ', v_parent_schema, v_current_partition_name); ELSE v_trig_func := v_trig_func || ' -- Child table for current values does not exist in this partition set, so write to parent RETURN NEW;'; END IF; FOR i IN 1..v_optimize_trigger LOOP v_prev_partition_id := v_current_partition_id - (v_partition_interval * i); v_next_partition_id := v_current_partition_id + (v_partition_interval * i); v_final_partition_id := v_next_partition_id + v_partition_interval; v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, v_prev_partition_id::text, TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, v_next_partition_id::text, TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles optimize_trigger being larger than premake (to go back in time further) and edge case of changing optimize_trigger immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema AND tablename = v_prev_partition_name; IF v_count > 0 THEN -- Only handle previous partitions if they're starting above zero IF v_prev_partition_id >= 0 THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %s AND NEW.%I < %s THEN INSERT INTO %I.%I VALUES (NEW.*); ' , v_control , v_prev_partition_id , v_control , v_prev_partition_id + v_partition_interval , v_parent_schema , v_prev_partition_name ); END IF; END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname ||'.'||tablename = v_next_partition_name; IF v_count > 0 THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %s AND NEW.%I < %s THEN INSERT INTO %I.%I VALUES (NEW.*);' , v_control , v_next_partition_id , v_control , v_final_partition_id , v_parent_schema , v_next_partition_name ); END IF; END LOOP; v_trig_func := v_trig_func ||format(' ELSE v_current_partition_id := NEW.%I - (NEW.%I %% %s); v_current_partition_name := @extschema@.check_name_length(%L, v_current_partition_id::text, TRUE); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = %L AND tablename = v_current_partition_name; IF v_count > 0 THEN EXECUTE format(''INSERT INTO %%I.%%I VALUES($1.*)'', %L, v_current_partition_name) USING NEW; ELSE RETURN NEW; END IF; END IF;' , v_control , v_control , v_partition_interval , v_parent_tablename , v_parent_schema , v_parent_schema ); IF v_run_maint IS FALSE THEN v_trig_func := v_trig_func ||format(' v_current_partition_id := NEW.%I - (NEW.%I %% %s); IF (NEW.%I %% %s) > (%s / 2) THEN v_id_position := (length(v_last_partition) - position(''p_'' in reverse(v_last_partition))) + 2; v_next_partition_id := (substring(v_last_partition from v_id_position)::bigint) + %s; WHILE ((v_next_partition_id - v_current_partition_id) / %s) <= %s LOOP v_partition_created := @extschema@.create_partition_id(%L, ARRAY[v_next_partition_id]); IF v_partition_created THEN PERFORM @extschema@.create_function_id(%L); PERFORM @extschema@.apply_constraints(%L); END IF; v_next_partition_id := v_next_partition_id + %s; END LOOP; END IF;' , v_control , v_control , v_partition_interval , v_control , v_partition_interval , v_partition_interval , v_partition_interval , v_partition_interval , v_premake , p_parent_table , p_parent_table , p_parent_table , v_partition_interval ); END IF; v_trig_func := v_trig_func ||' END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for current id interval: %s to %s', v_current_partition_id, v_final_partition_id-1)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; 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 FUNCTION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''Partition function maintenance for table %s 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 $$; /* * Create the trigger function for the parent table of a time-based partition set */ CREATE OR REPLACE FUNCTION create_function_time(p_parent_table text, p_job_id bigint DEFAULT NULL) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_control text; v_count int; v_current_partition_name text; v_current_partition_timestamp timestamptz; v_datetime_string text; v_epoch boolean; v_final_partition_timestamp timestamptz; v_function_name text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_new_length int; v_next_partition_name text; v_next_partition_timestamp timestamptz; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_prev_partition_name text; v_prev_partition_timestamp timestamptz; v_step_id bigint; v_trig_func text; v_optimize_trigger int; v_type text; BEGIN SELECT partition_type , partition_interval::interval , epoch , control , optimize_trigger , datetime_string , jobmon INTO v_type , v_partition_interval , v_epoch , v_control , v_optimize_trigger , v_datetime_string , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE FUNCTION: %s', p_parent_table)); ELSE v_job_id = p_job_id; END IF; v_step_id := add_step(v_job_id, format('Creating partition function for table %s', p_parent_table)); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); IF v_type = 'time' THEN v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_partition_name text; v_partition_timestamp timestamptz; BEGIN IF TG_OP = ''INSERT'' THEN ' , v_parent_schema , v_function_name); IF v_epoch = false THEN CASE WHEN v_partition_interval = '15 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', NEW.%I) + ''15min''::interval * floor(date_part(''minute'', NEW.%I) / 15.0);' , v_control , v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', NEW.%I) + ''30min''::interval * floor(date_part(''minute'', NEW.%I) / 30.0);' , v_control , v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 day' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''day'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 week' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''week'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 month' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''month'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_partition_interval = '3 months' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''quarter'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 year' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''year'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; ELSE -- epoch is true CASE WHEN v_partition_interval = '15 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', to_timestamp(NEW.%I)) + ''15min''::interval * floor(date_part(''minute'', NEW.%I) / 15.0);' , v_control , v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', to_timestamp(NEW.%I)) + ''30min''::interval * floor(date_part(''minute'', NEW.%I) / 30.0);' , v_control , v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', to_timestamp(NEW.%I));', v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 day' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''day'', to_timestamp(NEW.%I));', v_control); v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 week' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''week'', to_timestamp(NEW.%I));', v_control); v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 month' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''month'', to_timestamp(NEW.%I));', v_control); v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_partition_interval = '3 months' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''quarter'', to_timestamp(NEW.%I));', v_control); v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 year' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''year'', to_timestamp(NEW.%I));', v_control); v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; END IF; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, to_char(v_current_partition_timestamp, v_datetime_string), TRUE); v_next_partition_timestamp := v_current_partition_timestamp + v_partition_interval::interval; IF v_epoch = false THEN v_trig_func := v_trig_func ||format(' IF NEW.%I >= %L AND NEW.%I < %L THEN ' , v_control , v_current_partition_timestamp , v_control , v_next_partition_timestamp); ELSE v_trig_func := v_trig_func ||format(' IF to_timestamp(NEW.%I) >= %L AND to_timestamp(NEW.%I) < %L THEN ' , v_control , v_current_partition_timestamp , v_control , v_next_partition_timestamp); END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema AND tablename = v_current_partition_name; IF v_count > 0 THEN v_trig_func := v_trig_func || format(' INSERT INTO %I.%I VALUES (NEW.*); ', v_parent_schema, v_current_partition_name); ELSE v_trig_func := v_trig_func || ' -- Child table for current values does not exist in this partition set, so write to parent RETURN NEW;'; END IF; FOR i IN 1..v_optimize_trigger LOOP v_prev_partition_timestamp := v_current_partition_timestamp - (v_partition_interval::interval * i); v_next_partition_timestamp := v_current_partition_timestamp + (v_partition_interval::interval * i); v_final_partition_timestamp := v_next_partition_timestamp + (v_partition_interval::interval); v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, to_char(v_prev_partition_timestamp, v_datetime_string), TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, to_char(v_next_partition_timestamp, v_datetime_string), TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles optimize_trigger being larger than premake (to go back in time further) and edge case of changing optimize_trigger immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema AND tablename = v_prev_partition_name; IF v_count > 0 THEN IF v_epoch = false THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %L AND NEW.%I < %L THEN INSERT INTO %I.%I VALUES (NEW.*);' , v_control , v_prev_partition_timestamp , v_control , v_prev_partition_timestamp + v_partition_interval::interval , v_parent_schema , v_prev_partition_name); ELSE v_trig_func := v_trig_func ||format(' ELSIF to_timestamp(NEW.%I) >= %L AND to_timestamp(NEW.%I) < %L THEN INSERT INTO %I.%I VALUES (NEW.*);' , v_control , v_prev_partition_timestamp , v_control , v_prev_partition_timestamp + v_partition_interval::interval , v_parent_schema , v_prev_partition_name); END IF; END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema AND tablename = v_next_partition_name; IF v_count > 0 THEN IF v_epoch = false THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %L AND NEW.%I < %L THEN INSERT INTO %I.%I VALUES (NEW.*);' , v_control , v_next_partition_timestamp , v_control , v_final_partition_timestamp , v_parent_schema , v_next_partition_name); ELSE v_trig_func := v_trig_func ||format(' ELSIF to_timestamp(NEW.%I) >= %L AND to_timestamp(NEW.%I) < %L THEN INSERT INTO %I.%I VALUES (NEW.*);' , v_control , v_next_partition_timestamp , v_control , v_final_partition_timestamp , v_parent_schema , v_next_partition_name); END IF; END IF; END LOOP; v_trig_func := v_trig_func||format(' ELSE v_partition_name := @extschema@.check_name_length(%L, to_char(v_partition_timestamp, %L), TRUE); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = %L AND tablename = v_partition_name; IF v_count > 0 THEN EXECUTE format(''INSERT INTO %%I.%%I VALUES($1.*)'', %L, v_partition_name) USING NEW; ELSE RETURN NEW; END IF; END IF;' , v_parent_tablename , v_datetime_string , v_parent_schema , v_parent_schema); v_trig_func := v_trig_func ||' END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for current time interval: %s to %s' , v_current_partition_timestamp , v_final_partition_timestamp-'1sec'::interval)); END IF; ELSIF v_type = 'time-custom' THEN v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_child_schemaname text; v_child_table text; v_child_tablename text; BEGIN ' , v_parent_schema , v_function_name); IF v_epoch = false THEN v_trig_func := v_trig_func || format(' SELECT child_table INTO v_child_table FROM @extschema@.custom_time_partitions WHERE partition_range @> NEW.%I AND parent_table = %L;' , v_control , v_parent_schema||'.'||v_parent_tablename); ELSE -- epoch true v_trig_func := v_trig_func || format(' SELECT child_table INTO v_child_table FROM @extschema@.custom_time_partitions WHERE partition_range @> to_timestamp(NEW.%I) AND parent_table = %L;' , v_control , v_parent_schema||'.'||v_parent_tablename); END IF; v_trig_func := v_trig_func || ' SELECT schemaname, tablename INTO v_child_schemaname, v_child_tablename FROM pg_catalog.pg_tables WHERE schemaname ||''.''|| tablename = v_child_table; IF v_child_schemaname IS NOT NULL AND v_child_tablename IS NOT NULL THEN EXECUTE format(''INSERT INTO %I.%I VALUES ($1.*)'', v_child_schemaname, v_child_tablename) USING NEW; ELSE RETURN NEW; END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for custom time table: %s', p_parent_table)); END IF; ELSE RAISE EXCEPTION 'ERROR: Invalid time partitioning type given: %', v_type; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; 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 FUNCTION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''Partition function maintenance for table %s 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 $$; /* * Apply constraints managed by partman extension */ CREATE OR REPLACE FUNCTION apply_constraints(p_parent_table text, p_child_table text DEFAULT NULL, p_analyze boolean DEFAULT FALSE, p_job_id bigint DEFAULT NULL, p_debug boolean DEFAULT FALSE) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_child_table text; v_child_tablename text; v_col text; v_constraint_cols text[]; v_constraint_col_type text; v_constraint_name text; v_constraint_values record; v_control text; v_datetime_string text; v_epoch boolean; v_existing_constraint_name text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_id int; v_last_partition_timestamp timestamp; v_max_id bigint; v_max_timestamp timestamp; v_old_search_path text; v_optimize_constraint int; v_parent_schema text; v_parent_tablename text; v_partition_interval text; v_partition_suffix text; v_row_max record; v_sql text; v_step_id bigint; v_suffix_position int; v_type text; BEGIN SELECT partition_type , control , epoch , partition_interval , optimize_constraint , datetime_string , constraint_cols , jobmon INTO v_type , v_control , v_epoch , v_partition_interval , v_optimize_constraint , v_datetime_string , v_constraint_cols , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND constraint_cols IS NOT NULL; IF v_constraint_cols IS NULL THEN IF p_debug THEN RAISE NOTICE 'Given parent table (%) not set up for constraint management (constraint_cols is NULL)', p_parent_table; END IF; -- Returns silently to allow this function to be simply called by maintenance processes without having to check if config options are set. RETURN; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE CONSTRAINT: %s', p_parent_table)); ELSE v_job_id = p_job_id; END IF; END IF; -- If p_child_table is null, figure out the partition that is the one right before the optimize_constraint value backwards. IF p_child_table IS NULL THEN -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'DESC') LOOP IF (v_type = 'time' OR v_type = 'time-custom') THEN IF v_epoch = false THEN EXECUTE format('SELECT max(%I) FROM %I.%I', v_control, v_row_max.partition_schemaname, v_row_max.partition_tablename) INTO v_max_timestamp; ELSE EXECUTE format('SELECT to_timestamp(max(%I)) FROM %I.%I', v_control, v_row_max.partition_schemaname, v_row_max.partition_tablename) INTO v_max_timestamp; END IF; IF v_max_timestamp IS NOT NULL THEN SELECT suffix_timestamp FROM @extschema@.show_partition_name(p_parent_table, v_max_timestamp::text) INTO v_last_partition_timestamp; v_partition_suffix := to_char(v_last_partition_timestamp - (v_partition_interval::interval * (v_optimize_constraint + 1) ), v_datetime_string); EXIT; END IF; ELSIF v_type = 'id' THEN EXECUTE format('SELECT max(%I) FROM %I.%I', v_control, v_row_max.partition_schemaname, v_row_max.partition_tablename) INTO v_max_id; IF v_max_id IS NOT NULL THEN SELECT suffix_id FROM @extschema@.show_partition_name(p_parent_table, v_max_id::text) INTO v_last_partition_id; v_last_partition_id := v_last_partition_id - (v_partition_interval::int * (v_optimize_constraint + 1) ); IF v_last_partition_id < 0 THEN v_last_partition_id := 0; END IF; v_partition_suffix := v_last_partition_id::text; EXIT; END IF; ELSE RAISE EXCEPTION 'Unknown type encountered in apply_constraints()'; END IF; IF p_debug THEN RAISE NOTICE 'apply_constraint: p_parent_table: %, partition_schemaname: %, partition_tablename: %', p_parent_table , v_row_max.partition_schemaname, v_row_max.partition_tablename; END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying additional constraints: Automatically determining most recent child on which to apply constraints'); END IF; IF p_debug THEN RAISE NOTICE 'apply_constraint: v_parent_tablename: % , v_partition_suffix: %', v_parent_tablename, v_partition_suffix; END IF; IF v_partition_suffix IS NULL THEN IF p_debug THEN RAISE NOTICE 'No values for control column found in any child table. Unable to automatically determine which child table to apply additional constraints to'; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'WARNING', 'No values for control column found in any child table. Unable to automatically determine which child table to apply additional constraints to'); IF p_job_id IS NULL THEN -- If not part of a sub-job, fail this one with a warning PERFORM fail_job(v_job_id, 2); END IF; END IF; -- Return cleanly so that if maintenance calls under this condition it produces no errors that will interfere with other partition sets. RETURN; END IF; v_child_table := v_parent_schema ||'.'|| @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Target child table: %s.%s', v_parent_schema, v_child_table)); END IF; ELSE v_child_table := p_child_table; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying additional constraints: Checking if target child table exists'); END IF; SELECT tablename INTO v_child_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_child_table; IF v_child_tablename IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Target child table (%s) does not exist. Skipping constraint creation.', v_child_table)); IF p_job_id IS NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; IF p_debug THEN RAISE NOTICE 'Target child table (%) does not exist. Skipping constraint creation.', v_child_table; END IF; RETURN; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; FOREACH v_col IN ARRAY v_constraint_cols LOOP SELECT con.conname INTO v_existing_constraint_name FROM pg_catalog.pg_constraint con JOIN pg_class c ON c.oid = con.conrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN pg_catalog.pg_attribute a ON con.conrelid = a.attrelid WHERE c.relname = v_child_tablename AND n.nspname = v_parent_schema AND con.conname LIKE 'partmanconstr_%' AND con.contype = 'c' AND a.attname = v_col AND ARRAY[a.attnum] <@ con.conkey AND a.attisdropped = false; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Applying additional constraints: Applying new constraint on column: %s', v_col)); END IF; IF v_existing_constraint_name IS NOT NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Partman managed constraint already exists on this table (%s) and column (%s). Skipping creation.', v_child_table, v_col)); END IF; IF p_debug THEN RAISE NOTICE 'Partman managed constraint already exists on this table (%) and column (%). Skipping creation.', v_child_table, v_col ; END IF; CONTINUE; END IF; -- Ensure column name gets put on end of constraint name to help avoid naming conflicts v_constraint_name := @extschema@.check_name_length('partmanconstr_'||v_child_tablename, p_suffix := '_'||v_col); EXECUTE format('SELECT min(%I)::text AS min, max(%I)::text AS max FROM %I.%I', v_col, v_col, v_parent_schema, v_child_tablename) INTO v_constraint_values; IF v_constraint_values IS NOT NULL THEN v_sql := format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I <= %L)' , v_parent_schema , v_child_tablename , v_constraint_name , v_col , v_constraint_values.min , v_col , v_constraint_values.max); IF p_debug THEN RAISE NOTICE 'Constraint creation query: %', v_sql; END IF; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('New constraint created: %s', v_sql)); END IF; ELSE IF p_debug THEN RAISE NOTICE 'Given column (%) contains all NULLs. No constraint created', v_col; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Given column (%s) contains all NULLs. No constraint created', v_col)); END IF; END IF; END LOOP; IF p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Applying additional constraints: Running analyze on partition set: %s', p_parent_table)); END IF; IF p_debug THEN RAISE NOTICE 'Running analyze on partition set: %', p_parent_table; END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; 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 CONSTRAINT: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to turn a table into the parent of a partition set */ CREATE OR REPLACE FUNCTION 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_use_run_maintenance boolean DEFAULT NULL , p_start_partition text DEFAULT NULL , p_inherit_fk boolean DEFAULT true , p_epoch boolean DEFAULT false , p_jobmon boolean DEFAULT true , p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_base_timestamp timestamp; v_count int := 1; v_datetime_string text; v_higher_parent text := p_parent_table; v_id_interval bigint; v_id_position int; v_job_id bigint; v_jobmon_schema text; v_last_partition_created boolean; v_max bigint; v_notnull boolean; v_old_search_path text; v_parent_partition_id bigint; v_parent_partition_timestamp timestamp; v_parent_schema text; v_parent_tablename text; v_partition_time timestamp; v_partition_time_array timestamp[]; v_partition_id_array bigint[]; v_row record; v_run_maint boolean; v_sql text; v_start_time timestamp; v_starting_partition_id bigint; v_step_id bigint; v_step_overflow_id bigint; v_sub_parent text; v_success boolean := false; v_time_interval interval; v_time_position int; v_top_datetime_string text; v_top_parent text := p_parent_table; v_top_schemaname text; v_top_tablename text; BEGIN IF position('.' in p_parent_table) = 0 THEN RAISE EXCEPTION 'Parent table must be schema qualified'; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname || '.' || tablename = p_parent_table; IF v_parent_tablename IS NULL THEN RAISE EXCEPTION 'Please create given 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 AND n.nspname = v_parent_schema AND a.attname = p_control; IF 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; IF p_type = 'id' AND p_epoch = true THEN RAISE EXCEPTION 'p_epoch can only be used with time-based partitioning'; END IF; IF NOT @extschema@.check_partition_type(p_type) THEN RAISE EXCEPTION '% is not a valid partitioning type', p_type; END IF; EXECUTE format('LOCK TABLE %I.%I IN ACCESS EXCLUSIVE MODE', v_parent_schema, v_parent_tablename); IF p_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF p_use_run_maintenance IS NOT NULL THEN IF p_use_run_maintenance IS FALSE AND (p_type = 'time' OR p_type = 'time-custom') THEN RAISE EXCEPTION 'p_run_maintenance cannot be set to false for time based partitioning'; END IF; v_run_maint := p_use_run_maintenance; ELSIF p_type = 'time' OR p_type = 'time-custom' THEN v_run_maint := TRUE; ELSIF p_type = 'id' THEN v_run_maint := FALSE; ELSE RAISE EXCEPTION 'use_run_maintenance value cannot be set NULL'; END IF; 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 AND n.nspname = v_parent_schema ), sibling_children AS ( SELECT i.inhrelid::regclass::text AS tablename FROM pg_inherits i JOIN parent_table p ON i.inhparent = p.parent_oid ) 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_use_run_maintenance , sub_epoch , sub_optimize_trigger , sub_optimize_constraint , sub_jobmon 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_use_run_maintenance , sub_epoch , sub_optimize_trigger , sub_optimize_constraint , sub_jobmon) 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_use_run_maintenance , v_row.sub_epoch , v_row.sub_optimize_trigger , v_row.sub_optimize_constraint , v_row.sub_jobmon); END LOOP; IF p_type = 'time' OR p_type = 'time-custom' 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 <> 'time-custom' THEN RAISE EXCEPTION 'Must use a predefined time interval if not using type "time-custom". See documentation.'; 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::timestamp, 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))::timestamp; 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 , use_run_maintenance , inherit_fk , jobmon) VALUES ( p_parent_table , p_type , v_time_interval , p_epoch , p_control , p_premake , p_constraint_cols , v_datetime_string , v_run_maint , p_inherit_fk , p_jobmon); 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 AND n.nspname = v_parent_schema ) SELECT n.nspname||'.'||c.relname, p.datetime_string INTO v_top_parent, v_top_datetime_string 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 IS NOT NULL THEN -- If so create the lowest possible partition that is within the boundary of the parent v_time_position := (length(p_parent_table) - position('p_' in reverse(p_parent_table))) + 2; v_parent_partition_timestamp := to_timestamp(substring(p_parent_table from v_time_position), v_top_datetime_string); 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 -- Currently unknown edge case if code gets here RAISE EXCEPTION 'No child tables created. Unexpected edge case encountered. Please report this error to author with conditions that led to it.'; END IF; END 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 p_type = 'id' THEN v_id_interval := p_interval::bigint; IF v_id_interval < 10 THEN RAISE EXCEPTION 'Interval for serial 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 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||'.'||c.relname = v_higher_parent ) SELECT n.nspname||'.'||c.relname INTO v_higher_parent 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 WHERE p.partition_type = 'id'; IF v_higher_parent IS NOT NULL THEN -- v_top_parent initially set in DECLARE v_top_parent := v_higher_parent; 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 SELECT schemaname, tablename INTO v_top_schemaname, v_top_tablename FROM pg_catalog.pg_tables WHERE schemaname||'.'||tablename = v_top_parent; v_sql := format('SELECT COALESCE(%L, max(%I)::bigint, 0) FROM %I.%I LIMIT 1' , p_start_partition::bigint , p_control , v_top_schemaname , v_top_tablename); EXECUTE v_sql INTO v_max; 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 , use_run_maintenance , inherit_fk , jobmon) VALUES ( p_parent_table , p_type , v_id_interval , p_control , p_premake , p_constraint_cols , v_run_maint , p_inherit_fk , p_jobmon); 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 AND n.nspname = v_parent_schema ) SELECT n.nspname||'.'||c.relname INTO v_top_parent 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 WHERE p.partition_type = 'id'; IF v_top_parent IS NOT NULL THEN -- Create the lowest possible partition that is within the boundary of the parent v_id_position := (length(p_parent_table) - position('p_' in reverse(p_parent_table))) + 2; v_parent_partition_id = substring(p_parent_table from v_id_position)::bigint; 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 EXCEPTION 'No child tables created. Unexpected edge case encountered. Please report this error to author with conditions that led to it.'; END IF; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating partition function'); END IF; IF p_type = 'time' OR p_type = 'time-custom' 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 p_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); 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; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; 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 $$; /* * 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. * To avoid logical complications and contention issues, ALL subpartitions must be maintained using run_maintenance(). * This means the automatic, trigger based partition creation for serial partitioning will not work if it is a subpartition. */ CREATE FUNCTION create_sub_parent( p_top_parent text , p_control text , p_type text , p_interval text , 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 boolean DEFAULT false , p_jobmon boolean DEFAULT true , p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_last_partition text; v_row record; v_row_last_part record; v_run_maint boolean; v_sql text; v_success boolean := false; v_top_type text; BEGIN SELECT use_run_maintenance INTO v_run_maint FROM @extschema@.part_config WHERE parent_table = p_top_parent; IF v_run_maint 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; ELSIF v_run_maint = false THEN RAISE EXCEPTION 'Any parent table that will be part of a sub-partitioned set (on any level) must have use_run_maintenance set to true in part_config table, even for serial partitioning. See documentation for more info.'; END IF; FOR v_row IN -- Loop through all current children to turn them into partitioned tables SELECT partition_schemaname||'.'||partition_tablename AS child_table FROM @extschema@.show_partitions(p_top_parent) LOOP -- Just call existing create_parent() function but add the given parameters to the part_config_sub table as well 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_use_run_maintenance := %L , p_start_partition := %L , p_inherit_fk := %L , p_epoch := %L , p_jobmon := %L , p_debug := %L )' , v_row.child_table , p_control , p_type , p_interval , p_constraint_cols , p_premake , true , p_start_partition , p_inherit_fk , p_epoch , p_jobmon , p_debug); EXECUTE v_sql; END LOOP; 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_use_run_maintenance , sub_epoch , sub_jobmon) VALUES ( p_top_parent , p_control , p_type , p_interval , p_constraint_cols , p_premake , p_inherit_fk , true , p_epoch , p_jobmon); v_success := true; RETURN v_success; END $$; /* * Function to create a child table in a time-based partition set */ CREATE OR REPLACE FUNCTION create_partition_time (p_parent_table text, p_partition_times timestamp[], p_analyze boolean DEFAULT true) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_datetime_string text; v_exists text; v_epoch boolean; v_grantees text[]; v_hasoids boolean; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_partition_created boolean := false; v_partition_name text; v_partition_suffix text; v_parent_tablespace text; v_partition_interval interval; v_partition_timestamp_end timestamp; v_partition_timestamp_start timestamp; v_quarter text; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_sub_timestamp_max timestamp; v_sub_timestamp_min timestamp; v_trunc_value text; v_time timestamp; v_type text; v_unlogged char; v_year text; BEGIN SELECT partition_type , control , partition_interval , epoch , inherit_fk , jobmon , datetime_string INTO v_type , v_control , v_partition_interval , v_epoch , v_inherit_fk , v_jobmon , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'time' OR partition_type = 'time-custom'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::timestamp, sub_max::timestamp INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'time'); SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_timestamp_start := v_time; BEGIN v_partition_timestamp_end := v_time + v_partition_interval; 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_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_time||' skipped'); CONTINUE; END; -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_timestamp_min IS NOT NULL THEN IF v_time < v_sub_timestamp_min OR v_time > v_sub_timestamp_max THEN CONTINUE; END IF; END IF; -- This suffix generation code is in partition_data_time() as well v_partition_suffix := to_char(v_time, v_datetime_string); v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); SELECT tablename INTO v_exists FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema AND tablename = v_partition_name; IF v_exists IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Creating new partition %s.%s with interval from %s to %s' , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end-'1sec'::interval)); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_parent_tablename AND n.nspname = v_parent_schema; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_parent_tablename AND n.nspname = v_parent_schema; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; IF v_epoch = false THEN EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_control , v_partition_timestamp_start , v_control , v_partition_timestamp_end); ELSE EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (to_timestamp(%I) >= %L AND to_timestamp(%I) < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_control , v_partition_timestamp_start , v_control , v_partition_timestamp_end); END IF; EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); -- If custom time, set extra config options. IF v_type = 'time-custom' THEN INSERT INTO @extschema@.custom_time_partitions (parent_table, child_table, partition_range) VALUES ( p_parent_table, v_parent_schema||'.'||v_partition_name, tstzrange(v_partition_timestamp_start, v_partition_timestamp_end, '[)') ); END IF; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP EXECUTE format('GRANT %s ON %I.%I TO %I' , array_to_string(v_parent_grant.types, ',') , v_parent_schema , v_partition_name , v_parent_grant.grantee); SELECT string_agg(r, ',') INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE format('REVOKE %s ON %I.%I FROM %I CASCADE' , v_revoke , v_parent_schema , v_partition_name , v_parent_grant.grantee); END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN FOR v_row IN SELECT role FROM ( SELECT DISTINCT grantee::text AS role FROM information_schema.table_privileges WHERE table_schema = v_parent_schema AND table_name = v_partition_name EXCEPT SELECT unnest(v_grantees)) x LOOP IF v_row.role IS NOT NULL THEN EXECUTE format('REVOKE ALL ON %I.%I FROM %I' , v_parent_schema , v_partition_name , v_row.role); END IF; END LOOP; END IF; EXECUTE format('ALTER TABLE %I.%I OWNER TO %I', v_parent_schema, v_partition_name, v_parent_owner); IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_control , sub_partition_type , 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_use_run_maintenance , sub_epoch , sub_optimize_trigger , sub_optimize_constraint , sub_jobmon FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Subpartitioning %s.%s', v_parent_schema, v_partition_name)); END IF; 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_use_run_maintenance := %L , p_inherit_fk := %L , p_epoch := %L , p_jobmon := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_use_run_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint WHERE parent_table = v_parent_schema||'.'||v_partition_name; END LOOP; -- end sub partitioning LOOP v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s. Attempted intervals: %s', p_parent_table, p_partition_times)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; 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; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RETURN v_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to create id partitions */ CREATE OR REPLACE FUNCTION create_partition_id(p_parent_table text, p_partition_ids bigint[], p_analyze boolean DEFAULT true) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_exists text; v_grantees text[]; v_hasoids boolean; v_id bigint; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_parent_tablespace text; v_partition_interval bigint; v_partition_created boolean := false; v_partition_name text; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_sub_id_max bigint; v_sub_id_min bigint; v_unlogged char; BEGIN SELECT control , partition_interval , inherit_fk , jobmon INTO v_control , v_partition_interval , v_inherit_fk , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'id'); SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; FOREACH v_id IN ARRAY p_partition_ids LOOP -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_id_min IS NOT NULL THEN IF v_id < v_sub_id_min OR v_id > v_sub_id_max THEN CONTINUE; END IF; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_id::text, TRUE); -- If child table already exists, skip creation SELECT tablename INTO v_exists FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema AND tablename = v_partition_name; IF v_exists IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_id||' to '||(v_id + v_partition_interval)-1); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_parent_tablename AND n.nspname = v_parent_schema; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_parent_tablename AND n.nspname = v_parent_schema; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %s AND %I < %s )' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_control , v_id , v_control , v_id + v_partition_interval); EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I', v_parent_schema, v_partition_name, v_parent_schema, v_parent_tablename); FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types, grantee FROM information_schema.table_privileges WHERE table_schema ||'.'|| table_name = p_parent_table GROUP BY grantee LOOP EXECUTE format('GRANT %s ON %I.%I TO %I' , array_to_string(v_parent_grant.types, ',') , v_parent_schema , v_partition_name , v_parent_grant.grantee); SELECT string_agg(r, ',') INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN EXECUTE format('REVOKE %s ON %I.%I FROM %I CASCADE' , v_revoke , v_parent_schema , v_partition_name , v_parent_grant.grantee); END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN FOR v_row IN SELECT role FROM ( SELECT DISTINCT grantee::text AS role FROM information_schema.table_privileges WHERE table_schema = v_parent_schema AND table_name = v_partition_name EXCEPT SELECT unnest(v_grantees)) x LOOP IF v_row.role IS NOT NULL THEN EXECUTE format('REVOKE ALL ON %I.%I FROM %I' , v_parent_schema , v_partition_name , v_row.role); END IF; END LOOP; END IF; EXECUTE format('ALTER TABLE %I.%I OWNER TO %I', v_parent_schema, v_partition_name, v_parent_owner); IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_control , sub_partition_type , 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_use_run_maintenance , sub_epoch , sub_optimize_trigger , sub_optimize_constraint , sub_jobmon FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Subpartitioning '||v_partition_name); END IF; 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_use_run_maintenance := %L , p_inherit_fk := %L , p_epoch := %L , p_jobmon := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_use_run_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint WHERE parent_table = v_parent_schema||'.'||v_partition_name; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; -- end sub partitioning LOOP v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s', p_parent_table)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; PERFORM close_job(v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RETURN v_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to manage pre-creation of the next partitions in a set. * Also manages dropping old partitions if the retention option is set. * If p_parent_table is passed, will only run run_maintenance() on that one table (no matter what the configuration table may have set for it) * Otherwise, will run on all tables in the config table with p_run_maintenance() set to true. * For large partition sets, running analyze can cause maintenance to take longer than expected. Can set p_analyze to false to avoid a forced analyze run. * Be aware that constraint exclusion may not work properly until an analyze on the partition set is run. */ CREATE FUNCTION run_maintenance(p_parent_table text DEFAULT NULL, p_analyze boolean DEFAULT true, p_jobmon boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_check_subpart int; v_create_count int := 0; v_current_partition text; v_current_partition_id bigint; v_current_partition_timestamp timestamp; v_datetime_string text; v_drop_count int := 0; v_id_position int; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_created boolean; v_last_partition_id bigint; v_last_partition_timestamp timestamp; v_max_id_parent bigint; v_max_time_parent timestamp; v_next_partition_id bigint; v_next_partition_timestamp timestamp; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_premade_count int; v_premake_id_max bigint; v_premake_id_min bigint; v_premake_timestamp_min timestamp; v_premake_timestamp_max timestamp; v_quarter text; v_row record; v_row_max_id record; v_row_max_time record; v_row_sub record; v_skip_maint boolean; v_step_id bigint; v_step_overflow_id bigint; v_step_serial_id bigint; v_sub_id_max bigint; v_sub_id_max_suffix bigint; v_sub_id_min bigint; v_sub_parent text; v_sub_timestamp_max timestamp; v_sub_timestamp_max_suffix timestamp; v_sub_timestamp_min timestamp; v_tablename text; v_tables_list_sql text; v_time_position int; v_year text; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman maintenance already running.'; RETURN; END IF; 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' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN RUN MAINTENANCE'); v_step_id := add_step(v_job_id, 'Running maintenance loop'); END IF; -- Check for consistent data in part_config_sub table. Was unable to get this working properly as either a constraint or trigger. -- Would either delay raising an error until the next write (which I cannot predict) or disallow future edits to update a sub-partition set's configuration. -- This way at least provides a consistent way to check that I know will run. If anyone can get a working constraint/trigger, please help! -- Don't have to worry about this in the serial trigger maintenance since subpartitioning requires run_maintenance(). FOR v_row IN SELECT sub_parent FROM @extschema@.part_config_sub LOOP SELECT count(*) INTO v_check_subpart FROM @extschema@.check_subpart_sameconfig(v_row.sub_parent); IF v_check_subpart > 1 THEN RAISE EXCEPTION 'Inconsistent data in part_config_sub table. Sub-partition tables that are themselves sub-partitions cannot have differing configuration values among their siblings. Run this query: "SELECT * FROM @extschema@.check_subpart_sameconfig(''%'');" This should only return a single row or nothing. If multiple rows are returned, results are all children of the given parent. Update the differing values to be consistent for your desired values.', v_row.sub_parent; END IF; END LOOP; v_row := NULL; -- Ensure it's reset v_tables_list_sql := 'SELECT parent_table , partition_type , partition_interval , control , premake , datetime_string , undo_in_progress , sub_partition_set_full , epoch FROM @extschema@.part_config WHERE sub_partition_set_full = false'; IF p_parent_table IS NULL THEN v_tables_list_sql := v_tables_list_sql || ' AND use_run_maintenance = true'; ELSE v_tables_list_sql := v_tables_list_sql || format(' AND parent_table = %L', p_parent_table); END IF; FOR v_row IN EXECUTE v_tables_list_sql LOOP CONTINUE WHEN v_row.undo_in_progress; v_skip_maint := true; -- reset every loop SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_row.parent_table; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LIMIT 1; IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_last_partition: %', v_row.parent_table, v_last_partition; END IF; IF v_row.partition_type = 'time' OR v_row.partition_type = 'time-custom' THEN v_time_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; IF v_row.partition_interval::interval <> '3 months' OR (v_row.partition_interval::interval = '3 months' AND v_row.partition_type = 'time-custom') THEN v_last_partition_timestamp := to_timestamp(substring(v_last_partition from v_time_position), v_row.datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(v_last_partition FROM v_time_position), 'q', 1); v_quarter := split_part(substring(v_last_partition FROM v_time_position), 'q', 2); CASE WHEN v_quarter = '1' THEN v_last_partition_timestamp := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_last_partition_timestamp := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_last_partition_timestamp := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_last_partition_timestamp := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_time IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP IF v_row.epoch = false THEN EXECUTE format('SELECT max(%I)::text FROM %I.%I' , v_row.control , v_row_max_time.partition_schemaname , v_row_max_time.partition_tablename ) INTO v_current_partition_timestamp; ELSE EXECUTE format('SELECT to_timestamp(max(%I))::text FROM %I.%I' , v_row.control , v_row_max_time.partition_schemaname , v_row_max_time.partition_tablename ) INTO v_current_partition_timestamp; END IF; IF v_current_partition_timestamp IS NOT NULL THEN SELECT suffix_timestamp INTO v_current_partition_timestamp FROM show_partition_name(v_row.parent_table, v_current_partition_timestamp::text); EXIT; END IF; END LOOP; -- Check for values in the parent table. If they are there and greater than all child values, use that instead -- This allows maintenance to continue working properly if there is a large gap in data insertion. Data will remain in parent, but new tables will be created IF v_row.epoch = false THEN EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_row.control, v_parent_schema, v_parent_tablename) INTO v_max_time_parent; ELSE EXECUTE format('SELECT to_timestamp(max(%I)) FROM ONLY %I.%I', v_row.control, v_parent_schema, v_parent_tablename) INTO v_max_time_parent; END IF; IF p_debug THEN RAISE NOTICE 'run_maint: v_current_partition_timestamp: %, v_max_time_parent: %', v_current_partition_timestamp, v_max_time_parent; END IF; IF v_max_time_parent > v_current_partition_timestamp THEN SELECT suffix_timestamp INTO v_current_partition_timestamp FROM show_partition_name(v_row.parent_table, v_max_time_parent::text); END IF; IF v_current_partition_timestamp IS NULL THEN -- Partition set is completely empty. Nothing to do CONTINUE; END IF; -- If this is a subpartition, determine if the last child table has been made. If so, mark it as full so future maintenance runs can skip it SELECT sub_min::timestamp, sub_max::timestamp INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'time'); IF v_sub_timestamp_max IS NOT NULL THEN SELECT suffix_timestamp INTO v_sub_timestamp_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_timestamp_max::text); IF v_sub_timestamp_max_suffix = v_last_partition_timestamp THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; -- Check and see how many premade partitions there are. v_premade_count = round(EXTRACT('epoch' FROM age(v_last_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); v_next_partition_timestamp := v_last_partition_timestamp; IF p_debug THEN RAISE NOTICE 'run_maint before loop: current_partition_timestamp: %, v_premade_count: %, v_sub_timestamp_min: %, v_sub_timestamp_max: %' , v_current_partition_timestamp , v_premade_count , v_sub_timestamp_min , v_sub_timestamp_max; END IF; -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed WHILE (v_premade_count < v_row.premake) LOOP IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_premade_count: %, v_next_partition_timestamp: %', v_row.parent_table, v_premade_count, v_next_partition_timestamp; END IF; IF v_next_partition_timestamp < v_sub_timestamp_min OR v_next_partition_timestamp > v_sub_timestamp_max THEN -- With subpartitioning, no need to run if the timestamp is not in the parent table's range EXIT; END IF; BEGIN v_next_partition_timestamp := v_next_partition_timestamp + v_row.partition_interval::interval; EXCEPTION WHEN datetime_field_overflow THEN v_premade_count := v_row.premake; -- do this so it can exit the premake check loop and continue in the outer for loop IF v_jobmon_schema IS NOT NULL THEN 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 skippd for parent table '||v_partition_time); END IF; RAISE WARNING 'Attempted partition time interval is outside PostgreSQL''s supported time range. Child partition creation skipped for parent table %', v_row.parent_table; CONTINUE; END; v_last_partition_created := @extschema@.create_partition_time(v_row.parent_table, ARRAY[v_next_partition_timestamp], p_analyze); IF v_last_partition_created THEN v_create_count := v_create_count + 1; PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); END IF; v_premade_count = round(EXTRACT('epoch' FROM age(v_next_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); END LOOP; ELSIF v_row.partition_type = 'id' THEN -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%I)::text FROM %I.%I' , v_row.control , v_row_max_id.partition_schemaname , v_row_max_id.partition_tablename) INTO v_current_partition_id; IF v_current_partition_id IS NOT NULL THEN SELECT suffix_id INTO v_current_partition_id FROM show_partition_name(v_row.parent_table, v_current_partition_id::text); EXIT; END IF; END LOOP; -- Check for values in the parent table. If they are there and greater than all child values, use that instead -- This allows maintenance to continue working properly if there is a large gap in data insertion. Data will remain in parent, but new tables will be created EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_row.control, v_parent_schema, v_parent_tablename) INTO v_max_id_parent; IF v_max_id_parent > v_current_partition_id THEN SELECT suffix_id INTO v_current_partition_id FROM show_partition_name(v_row.parent_table, v_max_id_parent::text); END IF; IF v_current_partition_id IS NULL THEN -- Partition set is completely empty. Nothing to do CONTINUE; END IF; v_id_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; v_last_partition_id = substring(v_last_partition from v_id_position)::bigint; -- Determine if this table is a child of a subpartition parent. If so, get limits to see if run_maintenance even needs to run for it. SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'id'); IF v_sub_id_max IS NOT NULL THEN SELECT suffix_id INTO v_sub_id_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_id_max::text); IF v_sub_id_max_suffix = v_last_partition_id THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; v_next_partition_id := v_last_partition_id; v_premade_count := ((v_last_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed. WHILE (v_premade_count < v_row.premake) LOOP IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_premade_count: %, v_next_partition_id: %', v_row.parent_table, v_premade_count, v_next_partition_id; END IF; IF v_next_partition_id < v_sub_id_min OR v_next_partition_id > v_sub_id_max THEN -- With subpartitioning, no need to run if the id is not in the parent table's range EXIT; END IF; v_next_partition_id := v_next_partition_id + v_row.partition_interval::bigint; v_last_partition_created := @extschema@.create_partition_id(v_row.parent_table, ARRAY[v_next_partition_id], p_analyze); IF v_last_partition_created THEN v_create_count := v_create_count + 1; PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); END IF; v_premade_count := ((v_next_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); END LOOP; END IF; -- end main IF check for time or id -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table := v_row.parent_table, p_job_id := v_job_id, p_debug := p_debug); END LOOP; -- end of creation loop -- Manage dropping old partitions if retention option is set FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND (partition_type = 'time' OR partition_type = 'time-custom') LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); END IF; END IF; IF v_drop_count > 0 THEN PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); END IF; END LOOP; FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND partition_type = 'id' LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); END IF; END IF; IF v_drop_count > 0 THEN PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Partition maintenance finished. %s partitions made. %s partitions dropped.', v_create_count, v_drop_count)); IF v_step_overflow_id IS NOT NULL OR v_step_serial_id IS NOT NULL THEN PERFORM fail_job(v_job_id); ELSE PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; 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 RUN MAINTENANCE'')', v_jobmon_schema) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Populate the child table(s) of a time-based partition set with old data from the original parent */ CREATE OR REPLACE FUNCTION partition_data_time(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval interval DEFAULT NULL, p_lock_wait numeric DEFAULT 0, p_order text DEFAULT 'ASC') RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_datetime_string text; v_current_partition_name text; v_epoch boolean; v_last_partition text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_timestamp timestamp; v_min_partition_timestamp timestamp; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_partition_suffix text; v_partition_timestamp timestamp[]; v_quarter text; v_rowcount bigint; v_sql text; v_start_control timestamp; v_time_position int; v_total_rows bigint := 0; v_type text; v_year text; BEGIN SELECT partition_type , partition_interval::interval , control , datetime_string , epoch INTO v_type , v_partition_interval , v_control , v_datetime_string , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; FOR i IN 1..p_batch_count LOOP IF v_epoch = false THEN IF p_order = 'ASC' THEN EXECUTE format('SELECT min(%I) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSIF p_order = 'DESC' THEN EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; ELSE IF p_order = 'ASC' THEN EXECUTE format('SELECT to_timestamp(min(%I)) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSIF p_order = 'DESC' THEN EXECUTE format('SELECT to_timestamp(max(%I)) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; END IF; IF v_start_control IS NULL THEN EXIT; END IF; IF v_type = 'time' THEN CASE WHEN v_partition_interval = '15 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '15min'::interval * floor(date_part('minute', v_start_control) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '30min'::interval * floor(date_part('minute', v_start_control) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control); WHEN v_partition_interval = '1 day' THEN v_min_partition_timestamp := date_trunc('day', v_start_control); WHEN v_partition_interval = '1 week' THEN v_min_partition_timestamp := date_trunc('week', v_start_control); WHEN v_partition_interval = '1 month' THEN v_min_partition_timestamp := date_trunc('month', v_start_control); WHEN v_partition_interval = '3 months' THEN v_min_partition_timestamp := date_trunc('quarter', v_start_control); WHEN v_partition_interval = '1 year' THEN v_min_partition_timestamp := date_trunc('year', v_start_control); END CASE; ELSIF v_type = 'time-custom' THEN -- Keep going backwards, checking if the time interval encompases the current v_start_control value v_time_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; v_min_partition_timestamp := to_timestamp(substring(v_last_partition from v_time_position), v_datetime_string); v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; LOOP IF v_start_control >= v_min_partition_timestamp AND v_start_control < v_max_partition_timestamp THEN EXIT; ELSE v_max_partition_timestamp := v_min_partition_timestamp; BEGIN v_min_partition_timestamp := v_min_partition_timestamp - v_partition_interval; EXCEPTION WHEN datetime_field_overflow THEN RAISE EXCEPTION 'Attempted partition time interval is outside PostgreSQL''s supported time range. Unable to create partition with interval before timestamp % ', v_min_partition_interval; END; END IF; END LOOP; END IF; v_partition_timestamp := ARRAY[v_min_partition_timestamp]; IF p_order = 'ASC' THEN IF (v_start_control + p_batch_interval) >= (v_min_partition_timestamp + v_partition_interval) THEN v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; ELSE v_max_partition_timestamp := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; -- Make sure minimum doesn't underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_timestamp THEN v_min_partition_timestamp = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN IF v_epoch = false THEN v_sql := format('SELECT * FROM ONLY %I.%I WHERE %I >= %L AND %I < %L FOR UPDATE NOWAIT' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_timestamp , v_control , v_max_partition_timestamp); ELSE v_sql := format('SELECT * FROM ONLY %I.%I WHERE to_timestamp(%I) >= %L AND to_timestamp(%I) < %L FOR UPDATE NOWAIT' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_timestamp , v_control , v_max_partition_timestamp); END IF; EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; PERFORM @extschema@.create_partition_time(p_parent_table, v_partition_timestamp); -- This suffix generation code is in create_partition_time() as well v_partition_suffix := to_char(v_min_partition_timestamp, v_datetime_string); v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); IF v_epoch = false THEN v_sql := format('WITH partition_data AS ( DELETE FROM ONLY %I.%I WHERE %I >= %L AND %I < %L RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_timestamp , v_control , v_max_partition_timestamp , v_parent_schema , v_current_partition_name); ELSE v_sql := format('WITH partition_data AS ( DELETE FROM ONLY %I.%I WHERE to_timestamp(%I) >= %L AND to_timestamp(%I) < %L RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_timestamp , v_control , v_max_partition_timestamp , v_parent_schema , v_current_partition_name); END IF; EXECUTE v_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; PERFORM @extschema@.create_function_time(p_parent_table); RETURN v_total_rows; END $$; /* * Function to undo time-based partitioning created by this extension */ CREATE OR REPLACE FUNCTION undo_partition_time(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval interval DEFAULT NULL, p_keep_table boolean DEFAULT true, p_lock_wait numeric DEFAULT 0) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_batch_loop_count int := 0; v_child_min timestamptz; v_child_loop_total bigint := 0; v_child_table text; v_control text; v_epoch boolean; v_function_name text; v_inner_loop_count int; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_move_sql text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_row record; v_rowcount bigint; v_step_id bigint; v_sub_count int; v_total bigint := 0; v_trig_name text; v_type text; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition_time')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition_time already running.'; RETURN 0; END IF; SELECT partition_type , partition_interval::interval , control , jobmon , epoch INTO v_type , v_partition_interval , v_control , v_jobmon , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; -- Check if any child tables are themselves partitioned or part of an inheritance tree. Prevent undo at this level if so. -- Need to either lock child tables at all levels or handle the proper removal of triggers on all child tables first -- before multi-level undo can be performed safely. FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table) LOOP SELECT count(*) INTO v_sub_count FROM pg_catalog.pg_inherits i JOIN pg_catalog.pg_class c ON i.inhparent = c.oid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_row.partition_tablename AND n.nspname = v_row.partition_schemaname; IF v_sub_count > 0 THEN RAISE EXCEPTION 'Child table for this parent has child table(s) itself (%). Run undo partitioning on this table or remove inheritance first to ensure all data is properly moved to parent', v_row.partition_schemaname||'.'||v_row.partition_tablename; END IF; END LOOP; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN UNDO PARTITIONING: %s', p_parent_table)); v_step_id := add_step(v_job_id, format('Undoing partitioning for table %s', p_parent_table)); END IF; IF p_batch_interval IS NULL THEN p_batch_interval := v_partition_interval; END IF; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables. SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t WHERE tgname = v_trig_name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_parent_tablename); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; RETURN -1; END IF; END IF; -- END p_lock_wait IF EXECUTE format('DROP TRIGGER IF EXISTS %I ON %I.%I', v_trig_name, v_parent_schema, v_parent_tablename); END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE format('DROP FUNCTION IF EXISTS %I.%I()', v_parent_schema, v_function_name); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); END IF; <> WHILE v_batch_loop_count < p_batch_count LOOP -- Get ordered list of child table in set. Store in variable one at a time per loop until none are left. SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC'); EXIT WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Removing child partition: %s.%s', v_parent_schema, v_child_table)); END IF; IF v_epoch = false THEN EXECUTE format('SELECT min(%I) FROM %I.%I', v_control, v_parent_schema, v_child_table) INTO v_child_min; ELSE EXECUTE format('SELECT to_timestamp(min(%I)) FROM %I.%I', v_control, v_parent_schema, v_child_table) INTO v_child_min; END IF; IF v_child_min IS NULL THEN -- No rows left in this child table. Remove from partition set. -- lockwait timeout for table drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_child_table); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; RETURN -1; END IF; END IF; -- END p_lock_wait IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_parent_schema , v_child_table , v_parent_schema , v_parent_tablename); IF p_keep_table = false THEN EXECUTE format('DROP TABLE %I.%I', v_parent_schema, v_child_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table DROPPED. Moved %s rows to parent', v_child_loop_total)); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table UNINHERITED, not DROPPED. Moved %s rows to parent', v_child_loop_total)); END IF; END IF; IF v_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_parent_schema||'.'||v_child_table; END IF; v_undo_count := v_undo_count + 1; CONTINUE outer_child_loop; END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM %I.%I WHERE %I <= %L FOR UPDATE NOWAIT' , v_parent_schema , v_child_table , v_control , v_child_min + (p_batch_interval * v_inner_loop_count)); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; RETURN -1; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval IF v_epoch = false THEN v_move_sql := format('WITH move_data AS ( DELETE FROM %I.%I WHERE %I <= %L RETURNING *) INSERT INTO %I.%I SELECT * FROM move_data' , v_parent_schema , v_child_table , v_control , v_child_min + (p_batch_interval * v_inner_loop_count) , v_parent_schema , v_parent_tablename); ELSE v_move_sql := format('WITH move_data AS ( DELETE FROM %I.%I WHERE to_timestamp(%I) <= %L RETURNING *) INSERT INTO %I.%I SELECT * FROM move_data' , v_parent_schema , v_child_table , v_control , v_child_min + (p_batch_interval * v_inner_loop_count) , v_parent_schema , v_parent_tablename); END IF; EXECUTE v_move_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Moved %s rows to parent.', v_child_loop_total)); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; IF v_batch_loop_count < p_batch_count THEN -- FOR loop never ran, so there's no child tables left. DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) to the parent. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', format('Copied %s row(s) to the parent. Removed %s partitions.', v_total, v_undo_count)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RETURN v_total; 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 UNDO PARTITIONING: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to list all child partitions in a set. */ CREATE OR REPLACE FUNCTION show_partitions (p_parent_table text, p_order text DEFAULT 'ASC') RETURNS TABLE (partition_schemaname text, partition_tablename text) LANGUAGE plpgsql STABLE SECURITY DEFINER AS $$ DECLARE v_datetime_string text; v_parent_schema text; v_parent_tablename text; v_partition_interval text; v_quarter text; v_type text; v_year text; BEGIN IF upper(p_order) NOT IN ('ASC', 'DESC') THEN RAISE EXCEPTION 'p_order paramter must be one of the following values: ASC, DESC'; END IF; SELECT partition_type , partition_interval , datetime_string INTO v_type , v_partition_interval , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname||'.'||tablename = p_parent_table; IF v_type IN ('time', 'time-custom') THEN IF v_partition_interval::interval <> '3 months' OR (v_partition_interval::interval = '3 months' AND v_type = 'time-custom') THEN RETURN QUERY EXECUTE format('SELECT n.nspname::text AS partition_schemaname, c.relname::text AS partition_name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = ''%I.%I''::regclass ORDER BY to_timestamp(substring(c.relname from ((length(c.relname) - position(''p_'' in reverse(c.relname))) + 2) ), %L) %s' , v_parent_schema , v_parent_tablename , v_datetime_string , p_order); ELSE -- For quarterly, to_timestamp() doesn't recognize "Q" in datetime string. -- First order by just the year, then order by the quarter number (should be last character in table name) RETURN QUERY EXECUTE format('SELECT n.nspname::text AS partition_schemaname, c.relname::text AS partition_name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = ''%I.%I''::regclass ORDER BY to_timestamp(substring(c.relname from ((length(c.relname) - position(''p_'' in reverse(c.relname))) + 2) for 4), ''YYYY'') %s , substring(reverse(c.relname) from 1 for 1) %s' , v_parent_schema , v_parent_tablename , p_order , p_order); END IF; ELSIF v_type = 'id' THEN RETURN QUERY EXECUTE format('SELECT n.nspname::text AS partition_schemaname, c.relname::text AS partition_name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = ''%I.%I''::regclass ORDER BY substring(c.relname from ((length(c.relname) - position(''p_'' in reverse(c.relname))) + 2) )::bigint %s' , v_parent_schema , v_parent_tablename , p_order); END IF; END $$; -- Restore dropped object privileges DO $$ DECLARE v_row record; BEGIN FOR v_row IN SELECT statement FROM partman_preserve_privs_temp LOOP IF v_row.statement IS NOT NULL THEN EXECUTE v_row.statement; END IF; END LOOP; END $$; DROP TABLE IF EXISTS partman_preserve_privs_temp; pg_partman-4.6.0/updates/pg_partman--2.2.0--2.2.1.sql000066400000000000000000000517741412761706100214370ustar00rootroot00000000000000-- Fixed search path bug with run_maintenance() introduced in v2.2.0. Was causing errors if the pg_partman schema was not in the search path of the role calling it. (Github Issue #71) /* * Function to manage pre-creation of the next partitions in a set. * Also manages dropping old partitions if the retention option is set. * If p_parent_table is passed, will only run run_maintenance() on that one table (no matter what the configuration table may have set for it) * Otherwise, will run on all tables in the config table with p_run_maintenance() set to true. * For large partition sets, running analyze can cause maintenance to take longer than expected. Can set p_analyze to false to avoid a forced analyze run. * Be aware that constraint exclusion may not work properly until an analyze on the partition set is run. */ CREATE OR REPLACE FUNCTION run_maintenance(p_parent_table text DEFAULT NULL, p_analyze boolean DEFAULT true, p_jobmon boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_check_subpart int; v_create_count int := 0; v_current_partition text; v_current_partition_id bigint; v_current_partition_timestamp timestamp; v_datetime_string text; v_drop_count int := 0; v_id_position int; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_created boolean; v_last_partition_id bigint; v_last_partition_timestamp timestamp; v_max_id_parent bigint; v_max_time_parent timestamp; v_next_partition_id bigint; v_next_partition_timestamp timestamp; v_parent_schema text; v_parent_tablename text; v_premade_count int; v_premake_id_max bigint; v_premake_id_min bigint; v_premake_timestamp_min timestamp; v_premake_timestamp_max timestamp; v_quarter text; v_row record; v_row_max_id record; v_row_max_time record; v_row_sub record; v_skip_maint boolean; v_step_id bigint; v_step_overflow_id bigint; v_step_serial_id bigint; v_sub_id_max bigint; v_sub_id_max_suffix bigint; v_sub_id_min bigint; v_sub_parent text; v_sub_timestamp_max timestamp; v_sub_timestamp_max_suffix timestamp; v_sub_timestamp_min timestamp; v_tablename text; v_tables_list_sql text; v_time_position int; v_year text; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman maintenance already running.'; RETURN; END IF; 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' AND e.extnamespace = n.oid; END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.add_job(%L)', v_jobmon_schema, 'PARTMAN RUN MAINTENANCE') INTO v_job_id; EXECUTE format('SELECT %I.add_step(%L, %L)', v_jobmon_schema, v_job_id, 'Running maintenance loop') INTO v_step_id; END IF; -- Check for consistent data in part_config_sub table. Was unable to get this working properly as either a constraint or trigger. -- Would either delay raising an error until the next write (which I cannot predict) or disallow future edits to update a sub-partition set's configuration. -- This way at least provides a consistent way to check that I know will run. If anyone can get a working constraint/trigger, please help! -- Don't have to worry about this in the serial trigger maintenance since subpartitioning requires run_maintenance(). FOR v_row IN SELECT sub_parent FROM @extschema@.part_config_sub LOOP SELECT count(*) INTO v_check_subpart FROM @extschema@.check_subpart_sameconfig(v_row.sub_parent); IF v_check_subpart > 1 THEN RAISE EXCEPTION 'Inconsistent data in part_config_sub table. Sub-partition tables that are themselves sub-partitions cannot have differing configuration values among their siblings. Run this query: "SELECT * FROM @extschema@.check_subpart_sameconfig(''%'');" This should only return a single row or nothing. If multiple rows are returned, results are all children of the given parent. Update the differing values to be consistent for your desired values.', v_row.sub_parent; END IF; END LOOP; v_row := NULL; -- Ensure it's reset v_tables_list_sql := 'SELECT parent_table , partition_type , partition_interval , control , premake , datetime_string , undo_in_progress , sub_partition_set_full , epoch FROM @extschema@.part_config WHERE sub_partition_set_full = false'; IF p_parent_table IS NULL THEN v_tables_list_sql := v_tables_list_sql || ' AND use_run_maintenance = true'; ELSE v_tables_list_sql := v_tables_list_sql || format(' AND parent_table = %L', p_parent_table); END IF; FOR v_row IN EXECUTE v_tables_list_sql LOOP CONTINUE WHEN v_row.undo_in_progress; v_skip_maint := true; -- reset every loop SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = v_row.parent_table; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LIMIT 1; IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_last_partition: %', v_row.parent_table, v_last_partition; END IF; IF v_row.partition_type = 'time' OR v_row.partition_type = 'time-custom' THEN v_time_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; IF v_row.partition_interval::interval <> '3 months' OR (v_row.partition_interval::interval = '3 months' AND v_row.partition_type = 'time-custom') THEN v_last_partition_timestamp := to_timestamp(substring(v_last_partition from v_time_position), v_row.datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(v_last_partition FROM v_time_position), 'q', 1); v_quarter := split_part(substring(v_last_partition FROM v_time_position), 'q', 2); CASE WHEN v_quarter = '1' THEN v_last_partition_timestamp := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_last_partition_timestamp := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_last_partition_timestamp := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_last_partition_timestamp := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_time IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP IF v_row.epoch = false THEN EXECUTE format('SELECT max(%I)::text FROM %I.%I' , v_row.control , v_row_max_time.partition_schemaname , v_row_max_time.partition_tablename ) INTO v_current_partition_timestamp; ELSE EXECUTE format('SELECT to_timestamp(max(%I))::text FROM %I.%I' , v_row.control , v_row_max_time.partition_schemaname , v_row_max_time.partition_tablename ) INTO v_current_partition_timestamp; END IF; IF v_current_partition_timestamp IS NOT NULL THEN SELECT suffix_timestamp INTO v_current_partition_timestamp FROM @extschema@.show_partition_name(v_row.parent_table, v_current_partition_timestamp::text); EXIT; END IF; END LOOP; -- Check for values in the parent table. If they are there and greater than all child values, use that instead -- This allows maintenance to continue working properly if there is a large gap in data insertion. Data will remain in parent, but new tables will be created IF v_row.epoch = false THEN EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_row.control, v_parent_schema, v_parent_tablename) INTO v_max_time_parent; ELSE EXECUTE format('SELECT to_timestamp(max(%I)) FROM ONLY %I.%I', v_row.control, v_parent_schema, v_parent_tablename) INTO v_max_time_parent; END IF; IF p_debug THEN RAISE NOTICE 'run_maint: v_current_partition_timestamp: %, v_max_time_parent: %', v_current_partition_timestamp, v_max_time_parent; END IF; IF v_max_time_parent > v_current_partition_timestamp THEN SELECT suffix_timestamp INTO v_current_partition_timestamp FROM @extschema@.show_partition_name(v_row.parent_table, v_max_time_parent::text); END IF; IF v_current_partition_timestamp IS NULL THEN -- Partition set is completely empty. Nothing to do CONTINUE; END IF; -- If this is a subpartition, determine if the last child table has been made. If so, mark it as full so future maintenance runs can skip it SELECT sub_min::timestamp, sub_max::timestamp INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'time'); IF v_sub_timestamp_max IS NOT NULL THEN SELECT suffix_timestamp INTO v_sub_timestamp_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_timestamp_max::text); IF v_sub_timestamp_max_suffix = v_last_partition_timestamp THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; -- Check and see how many premade partitions there are. v_premade_count = round(EXTRACT('epoch' FROM age(v_last_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); v_next_partition_timestamp := v_last_partition_timestamp; IF p_debug THEN RAISE NOTICE 'run_maint before loop: current_partition_timestamp: %, v_premade_count: %, v_sub_timestamp_min: %, v_sub_timestamp_max: %' , v_current_partition_timestamp , v_premade_count , v_sub_timestamp_min , v_sub_timestamp_max; END IF; -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed WHILE (v_premade_count < v_row.premake) LOOP IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_premade_count: %, v_next_partition_timestamp: %', v_row.parent_table, v_premade_count, v_next_partition_timestamp; END IF; IF v_next_partition_timestamp < v_sub_timestamp_min OR v_next_partition_timestamp > v_sub_timestamp_max THEN -- With subpartitioning, no need to run if the timestamp is not in the parent table's range EXIT; END IF; BEGIN v_next_partition_timestamp := v_next_partition_timestamp + v_row.partition_interval::interval; EXCEPTION WHEN datetime_field_overflow THEN v_premade_count := v_row.premake; -- do this so it can exit the premake check loop and continue in the outer for loop IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.add_step(%L, %L)', v_jobmon_schema, v_job_id, 'Attempted partition time interval is outside PostgreSQL''s supported time range.') INTO v_step_overflow_id; EXECUTE format('SELECT %I.update_step(%L, %L, %L)', v_jobmon_schema, v_step_overflow_id, 'CRITICAL', 'Child partition creation skippd for parent table '||v_partition_time); END IF; RAISE WARNING 'Attempted partition time interval is outside PostgreSQL''s supported time range. Child partition creation skipped for parent table %', v_row.parent_table; CONTINUE; END; v_last_partition_created := @extschema@.create_partition_time(v_row.parent_table, ARRAY[v_next_partition_timestamp], p_analyze); IF v_last_partition_created THEN v_create_count := v_create_count + 1; PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); END IF; v_premade_count = round(EXTRACT('epoch' FROM age(v_next_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); END LOOP; ELSIF v_row.partition_type = 'id' THEN -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%I)::text FROM %I.%I' , v_row.control , v_row_max_id.partition_schemaname , v_row_max_id.partition_tablename) INTO v_current_partition_id; IF v_current_partition_id IS NOT NULL THEN SELECT suffix_id INTO v_current_partition_id FROM @extschema@.show_partition_name(v_row.parent_table, v_current_partition_id::text); EXIT; END IF; END LOOP; -- Check for values in the parent table. If they are there and greater than all child values, use that instead -- This allows maintenance to continue working properly if there is a large gap in data insertion. Data will remain in parent, but new tables will be created EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_row.control, v_parent_schema, v_parent_tablename) INTO v_max_id_parent; IF v_max_id_parent > v_current_partition_id THEN SELECT suffix_id INTO v_current_partition_id FROM @extschema@.show_partition_name(v_row.parent_table, v_max_id_parent::text); END IF; IF v_current_partition_id IS NULL THEN -- Partition set is completely empty. Nothing to do CONTINUE; END IF; v_id_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; v_last_partition_id = substring(v_last_partition from v_id_position)::bigint; -- Determine if this table is a child of a subpartition parent. If so, get limits to see if run_maintenance even needs to run for it. SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'id'); IF v_sub_id_max IS NOT NULL THEN SELECT suffix_id INTO v_sub_id_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_id_max::text); IF v_sub_id_max_suffix = v_last_partition_id THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; v_next_partition_id := v_last_partition_id; v_premade_count := ((v_last_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed. WHILE (v_premade_count < v_row.premake) LOOP IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_premade_count: %, v_next_partition_id: %', v_row.parent_table, v_premade_count, v_next_partition_id; END IF; IF v_next_partition_id < v_sub_id_min OR v_next_partition_id > v_sub_id_max THEN -- With subpartitioning, no need to run if the id is not in the parent table's range EXIT; END IF; v_next_partition_id := v_next_partition_id + v_row.partition_interval::bigint; v_last_partition_created := @extschema@.create_partition_id(v_row.parent_table, ARRAY[v_next_partition_id], p_analyze); IF v_last_partition_created THEN v_create_count := v_create_count + 1; PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); END IF; v_premade_count := ((v_next_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); END LOOP; END IF; -- end main IF check for time or id -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table := v_row.parent_table, p_job_id := v_job_id, p_debug := p_debug); END LOOP; -- end of creation loop -- Manage dropping old partitions if retention option is set FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND (partition_type = 'time' OR partition_type = 'time-custom') LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); END IF; END IF; IF v_drop_count > 0 THEN PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); END IF; END LOOP; FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND partition_type = 'id' LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); END IF; END IF; IF v_drop_count > 0 THEN PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.update_step(%L, %L, ''Partition maintenance finished. %s partitions made. %s partitions dropped.'')' , v_jobmon_schema , v_step_id , 'OK' , v_create_count , v_drop_count); IF v_step_overflow_id IS NOT NULL OR v_step_serial_id IS NOT NULL THEN EXECUTE format('SELECT %I.fail_job(%L)', v_jobmon_schema, v_job_id); ELSE EXECUTE format('SELECT %I.close_job(%L)', v_jobmon_schema, v_job_id); END IF; END IF; 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 RUN MAINTENANCE'')', v_jobmon_schema) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; pg_partman-4.6.0/updates/pg_partman--2.2.1--2.2.2.sql000066400000000000000000000006161412761706100214260ustar00rootroot00000000000000-- NOTE: No changes to the core extension code contained in this update. This file is only here for version upgrade continuity. -- Fixed infinite loop in reapply_indexes.py if the same index name would have been used more than once. Thanks to bougyman for tha bug report. -- Fixed indexes being applied to the parent table instead of the children in reapply_indexes.py (Github Push Request #73). pg_partman-4.6.0/updates/pg_partman--2.2.2--2.2.3.sql000066400000000000000000002444121412761706100214340ustar00rootroot00000000000000-- Fixed bug in partition_data_time() that would cause an infinite loop when moving data would require creating a new partition newer than the newest one. This would only occur when using a custom time interval. Infinite loop would also occur when using the partition_data.py python script (Github Issue #83). -- Properly handle the special PUBLIC role when granting/revoking privileges on child tables (Github Issue #66). -- Fixed python exception in reapply_indexes.py if the parent table has no indexes (Github Issue #86). -- Fixed all undo_partition functions to remove the child table in the same batch session when its rowcount reaches zero. Previously an extra batch may have been required to remove all child tables and remove config information after the last row was moved to the parent. -- Fixed undo_partition functions to be more strict on the triggers they drop and ensure they only drop the trigger on the target parent table. -- Fixed undo_partition() to properly remove entries from the custom_time_partitions config table if necessary. Also greatly simplified code in this function. -- Consolidated privilege management into new apply_privileges() internal function. -- Improved performance when applying parent privileges to child tables. Most noticable on larger partition sets with many grants. (Github Issue #78) -- Added a sort to the Makefile when creating the sql extension file. Allows more predictable output between builds (Github Push Request #16 from Mimeo extension). /* * Apply privileges that exist on a given parent to the given child table */ CREATE FUNCTION apply_privileges(p_parent_schema text, p_parent_tablename text, p_child_schema text, p_child_tablename text, p_job_id bigint DEFAULT NULL) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_child_grant record; v_child_owner text; v_grantees text[]; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_match boolean; v_parent_grant record; v_parent_owner text; v_revoke text; v_row_revoke record; v_sql text; v_step_id bigint; BEGIN SELECT jobmon INTO v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_schema ||'.'|| p_parent_tablename; IF v_jobmon IS NULL THEN RAISE EXCEPTION 'Given table is not managed by this extention: %.%', p_parent_schema, p_parent_tablename; END IF; SELECT tableowner INTO v_parent_owner FROM pg_catalog.pg_tables WHERE schemaname = p_parent_schema AND tablename = p_parent_tablename; SELECT tableowner INTO v_child_owner FROM pg_tables WHERE schemaname = p_child_schema AND tablename = p_child_tablename; IF v_parent_owner IS NULL THEN RAISE EXCEPTION 'Given parent table does not exist: %.%', v_parent_schema, v_parent_tablename; END IF; IF v_child_owner IS NULL THEN RAISE EXCEPTION 'Given child table does not exist: %.%', v_child_schema, v_child_tablename; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; END IF; IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN EXECUTE format('SELECT %I.add_job(%L)', v_jobmon_schema, format('PARTMAN APPLYING PRIVILEGES TO CHILD TABLE: %s.%s', p_child_schema, p_child_tablename)) INTO v_job_id; ELSE v_job_id := p_job_id; END IF; EXECUTE format('SELECT %I.add_step(%L, %L)', v_jobmon_schema, v_job_id, format('Setting new child table privileges for %s.%s', p_child_schema, p_child_tablename)) INTO v_step_id; END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.update_step(%L, %L, %L)' , v_jobmon_schema , v_step_id , 'PENDING' , format('Applying privileges on child partition: %s.%s' , p_child_schema , p_child_tablename) ); END IF; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types , grantee FROM information_schema.table_privileges WHERE table_schema = p_parent_schema AND table_name = p_parent_tablename GROUP BY grantee LOOP -- Compare parent & child grants. Don't re-apply if it already exists v_match := false; v_sql := NULL; FOR v_child_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types , grantee FROM information_schema.table_privileges WHERE table_schema = p_child_schema AND table_name = p_child_tablename GROUP BY grantee LOOP IF v_parent_grant.types = v_child_grant.types AND v_parent_grant.grantee = v_child_grant.grantee THEN v_match := true; END IF; END LOOP; IF v_match = false THEN IF v_parent_grant.grantee = 'PUBLIC' THEN v_sql := 'GRANT %s ON %I.%I TO %s'; ELSE v_sql := 'GRANT %s ON %I.%I TO %I'; END IF; EXECUTE format(v_sql , array_to_string(v_parent_grant.types, ',') , p_child_schema , p_child_tablename , v_parent_grant.grantee); v_sql := NULL; SELECT string_agg(r, ',') INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN IF v_parent_grant.grantee = 'PUBLIC' THEN v_sql := 'REVOKE %s ON %I.%I FROM %s CASCADE'; ELSE v_sql := 'REVOKE %s ON %I.%I FROM %I CASCADE'; END IF; EXECUTE format(v_sql , v_revoke , p_child_schema , p_child_tablename , v_parent_grant.grantee); v_sql := NULL; END IF; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN FOR v_row_revoke IN SELECT role FROM ( SELECT DISTINCT grantee::text AS role FROM information_schema.table_privileges WHERE table_schema = p_child_schema AND table_name = p_child_tablename EXCEPT SELECT unnest(v_grantees)) x LOOP IF v_row_revoke.role IS NOT NULL THEN IF v_row_revoke.role = 'PUBLIC' THEN v_sql := 'REVOKE ALL ON %I.%I FROM %s'; ELSE v_sql := 'REVOKE ALL ON %I.%I FROM %I'; END IF; EXECUTE format(v_sql , p_child_schema , p_child_tablename , v_row_revoke.role); END IF; END LOOP; END IF; IF v_parent_owner <> v_child_owner THEN EXECUTE format('ALTER TABLE %I.%I OWNER TO %I' , p_child_schema , p_child_tablename , v_parent_owner); END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.update_step(%L, %L, %L)', v_jobmon_schema, v_step_id, 'OK', 'Done'); IF p_job_id IS NULL THEN EXECUTE format('SELECT %I.close_job(%L)', v_jobmon_schema, v_job_id); END IF; END IF; 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 RE-APPLYING PRIVILEGES TO ALL CHILD TABLES OF: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to re-apply ownership & privileges on all child tables in a partition set using parent table as reference */ CREATE OR REPLACE FUNCTION reapply_privileges(p_parent_table text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_parent_schema text; v_parent_tablename text; v_row record; v_step_id bigint; BEGIN SELECT jobmon INTO v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon IS NULL THEN RAISE EXCEPTION 'Given table is not managed by this extention: %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; IF v_parent_tablename IS NULL THEN RAISE EXCEPTION 'Given parent table does not exist: %', p_parent_table; END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.add_job(%L)', v_jobmon_schema, format('PARTMAN RE-APPLYING PRIVILEGES TO ALL CHILD TABLES OF: %s', p_parent_table)) INTO v_job_id; END IF; FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'ASC') LOOP PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_row.partition_schemaname, v_row.partition_tablename, v_job_id); END LOOP; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.close_job(%L)', v_jobmon_schema, v_job_id); END IF; 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 RE-APPLYING PRIVILEGES TO ALL CHILD TABLES OF: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Populate the child table(s) of a time-based partition set with old data from the original parent */ CREATE OR REPLACE FUNCTION partition_data_time(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval interval DEFAULT NULL, p_lock_wait numeric DEFAULT 0, p_order text DEFAULT 'ASC') RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_datetime_string text; v_current_partition_name text; v_epoch boolean; v_last_partition text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_timestamp timestamp; v_min_partition_timestamp timestamp; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_partition_suffix text; v_partition_timestamp timestamp[]; v_quarter text; v_rowcount bigint; v_sql text; v_start_control timestamp; v_time_position int; v_total_rows bigint := 0; v_type text; v_year text; BEGIN SELECT partition_type , partition_interval::interval , control , datetime_string , epoch INTO v_type , v_partition_interval , v_control , v_datetime_string , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; FOR i IN 1..p_batch_count LOOP IF v_epoch = false THEN IF p_order = 'ASC' THEN EXECUTE format('SELECT min(%I) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSIF p_order = 'DESC' THEN EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; ELSE IF p_order = 'ASC' THEN EXECUTE format('SELECT to_timestamp(min(%I)) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSIF p_order = 'DESC' THEN EXECUTE format('SELECT to_timestamp(max(%I)) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; END IF; IF v_start_control IS NULL THEN EXIT; END IF; IF v_type = 'time' THEN CASE WHEN v_partition_interval = '15 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '15min'::interval * floor(date_part('minute', v_start_control) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '30min'::interval * floor(date_part('minute', v_start_control) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control); WHEN v_partition_interval = '1 day' THEN v_min_partition_timestamp := date_trunc('day', v_start_control); WHEN v_partition_interval = '1 week' THEN v_min_partition_timestamp := date_trunc('week', v_start_control); WHEN v_partition_interval = '1 month' THEN v_min_partition_timestamp := date_trunc('month', v_start_control); WHEN v_partition_interval = '3 months' THEN v_min_partition_timestamp := date_trunc('quarter', v_start_control); WHEN v_partition_interval = '1 year' THEN v_min_partition_timestamp := date_trunc('year', v_start_control); END CASE; ELSIF v_type = 'time-custom' THEN v_time_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; v_min_partition_timestamp := to_timestamp(substring(v_last_partition from v_time_position), v_datetime_string); v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; LOOP IF v_start_control >= v_min_partition_timestamp AND v_start_control < v_max_partition_timestamp THEN EXIT; ELSE BEGIN IF v_start_control > v_max_partition_timestamp THEN -- Keep going forward in time, checking if child partition time interval encompasses the current v_start_control value v_min_partition_timestamp := v_max_partition_timestamp; v_max_partition_timestamp := v_max_partition_timestamp + v_partition_interval; ELSE -- Keep going backwards in time, checking if child partition time interval encompasses the current v_start_control value v_max_partition_timestamp := v_min_partition_timestamp; v_min_partition_timestamp := v_min_partition_timestamp - v_partition_interval; END IF; EXCEPTION WHEN datetime_field_overflow THEN RAISE EXCEPTION 'Attempted partition time interval is outside PostgreSQL''s supported time range. Unable to create partition with interval before timestamp % ', v_min_partition_interval; END; END IF; END LOOP; END IF; v_partition_timestamp := ARRAY[v_min_partition_timestamp]; IF p_order = 'ASC' THEN -- Ensure batch interval given as parameter doesn't cause maximum to overflow the current partition maximum IF (v_start_control + p_batch_interval) >= (v_min_partition_timestamp + v_partition_interval) THEN v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; ELSE v_max_partition_timestamp := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; -- Ensure batch interval given as parameter doesn't cause minimum to underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_timestamp THEN v_min_partition_timestamp = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN IF v_epoch = false THEN v_sql := format('SELECT * FROM ONLY %I.%I WHERE %I >= %L AND %I < %L FOR UPDATE NOWAIT' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_timestamp , v_control , v_max_partition_timestamp); ELSE v_sql := format('SELECT * FROM ONLY %I.%I WHERE to_timestamp(%I) >= %L AND to_timestamp(%I) < %L FOR UPDATE NOWAIT' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_timestamp , v_control , v_max_partition_timestamp); END IF; EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; PERFORM @extschema@.create_partition_time(p_parent_table, v_partition_timestamp); -- This suffix generation code is in create_partition_time() as well v_partition_suffix := to_char(v_min_partition_timestamp, v_datetime_string); v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); IF v_epoch = false THEN v_sql := format('WITH partition_data AS ( DELETE FROM ONLY %I.%I WHERE %I >= %L AND %I < %L RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_timestamp , v_control , v_max_partition_timestamp , v_parent_schema , v_current_partition_name); ELSE v_sql := format('WITH partition_data AS ( DELETE FROM ONLY %I.%I WHERE to_timestamp(%I) >= %L AND to_timestamp(%I) < %L RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_timestamp , v_control , v_max_partition_timestamp , v_parent_schema , v_current_partition_name); END IF; EXECUTE v_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; PERFORM @extschema@.create_function_time(p_parent_table); RETURN v_total_rows; END $$; /* * Function to create id partitions */ CREATE OR REPLACE FUNCTION create_partition_id(p_parent_table text, p_partition_ids bigint[], p_analyze boolean DEFAULT true) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_exists text; v_grantees text[]; v_hasoids boolean; v_id bigint; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_parent_tablespace text; v_partition_interval bigint; v_partition_created boolean := false; v_partition_name text; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_sub_id_max bigint; v_sub_id_min bigint; v_unlogged char; BEGIN SELECT control , partition_interval , inherit_fk , jobmon INTO v_control , v_partition_interval , v_inherit_fk , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'id'); SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; FOREACH v_id IN ARRAY p_partition_ids LOOP -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_id_min IS NOT NULL THEN IF v_id < v_sub_id_min OR v_id > v_sub_id_max THEN CONTINUE; END IF; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_id::text, TRUE); -- If child table already exists, skip creation SELECT tablename INTO v_exists FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema AND tablename = v_partition_name; IF v_exists IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_id||' to '||(v_id + v_partition_interval)-1); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_parent_tablename AND n.nspname = v_parent_schema; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_parent_tablename AND n.nspname = v_parent_schema; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %s AND %I < %s )' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_control , v_id , v_control , v_id + v_partition_interval); EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I', v_parent_schema, v_partition_name, v_parent_schema, v_parent_tablename); PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_control , sub_partition_type , 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_use_run_maintenance , sub_epoch , sub_optimize_trigger , sub_optimize_constraint , sub_jobmon FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Subpartitioning '||v_partition_name); END IF; 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_use_run_maintenance := %L , p_inherit_fk := %L , p_epoch := %L , p_jobmon := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_use_run_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint WHERE parent_table = v_parent_schema||'.'||v_partition_name; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; -- end sub partitioning LOOP v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s', p_parent_table)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; PERFORM close_job(v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RETURN v_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to create a child table in a time-based partition set */ CREATE OR REPLACE FUNCTION create_partition_time(p_parent_table text, p_partition_times timestamp[], p_analyze boolean DEFAULT true) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_datetime_string text; v_exists text; v_epoch boolean; v_grantees text[]; v_hasoids boolean; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_partition_created boolean := false; v_partition_name text; v_partition_suffix text; v_parent_tablespace text; v_partition_interval interval; v_partition_timestamp_end timestamp; v_partition_timestamp_start timestamp; v_quarter text; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_sub_timestamp_max timestamp; v_sub_timestamp_min timestamp; v_trunc_value text; v_time timestamp; v_type text; v_unlogged char; v_year text; BEGIN SELECT partition_type , control , partition_interval , epoch , inherit_fk , jobmon , datetime_string INTO v_type , v_control , v_partition_interval , v_epoch , v_inherit_fk , v_jobmon , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'time' OR partition_type = 'time-custom'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::timestamp, sub_max::timestamp INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'time'); SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_timestamp_start := v_time; BEGIN v_partition_timestamp_end := v_time + v_partition_interval; 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_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_time||' skipped'); CONTINUE; END; -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_timestamp_min IS NOT NULL THEN IF v_time < v_sub_timestamp_min OR v_time > v_sub_timestamp_max THEN CONTINUE; END IF; END IF; -- This suffix generation code is in partition_data_time() as well v_partition_suffix := to_char(v_time, v_datetime_string); v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); SELECT tablename INTO v_exists FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema AND tablename = v_partition_name; IF v_exists IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Creating new partition %s.%s with interval from %s to %s' , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end-'1sec'::interval)); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_parent_tablename AND n.nspname = v_parent_schema; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_parent_tablename AND n.nspname = v_parent_schema; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; IF v_epoch = false THEN EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_control , v_partition_timestamp_start , v_control , v_partition_timestamp_end); ELSE EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (to_timestamp(%I) >= %L AND to_timestamp(%I) < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_control , v_partition_timestamp_start , v_control , v_partition_timestamp_end); END IF; EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); -- If custom time, set extra config options. IF v_type = 'time-custom' THEN INSERT INTO @extschema@.custom_time_partitions (parent_table, child_table, partition_range) VALUES ( p_parent_table, v_parent_schema||'.'||v_partition_name, tstzrange(v_partition_timestamp_start, v_partition_timestamp_end, '[)') ); END IF; PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_control , sub_partition_type , 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_use_run_maintenance , sub_epoch , sub_optimize_trigger , sub_optimize_constraint , sub_jobmon FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Subpartitioning %s.%s', v_parent_schema, v_partition_name)); END IF; 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_use_run_maintenance := %L , p_inherit_fk := %L , p_epoch := %L , p_jobmon := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_use_run_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint WHERE parent_table = v_parent_schema||'.'||v_partition_name; END LOOP; -- end sub partitioning LOOP v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s. Attempted intervals: %s', p_parent_table, p_partition_times)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; 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; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RETURN v_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to undo partitioning. * Will actually work on any parent/child table set, not just ones created by pg_partman. */ CREATE OR REPLACE FUNCTION undo_partition(p_parent_table text, p_batch_count int DEFAULT 1, p_keep_table boolean DEFAULT true, p_jobmon boolean DEFAULT true, p_lock_wait numeric DEFAULT 0) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_batch_loop_count bigint := 0; v_child_count bigint; v_child_table text; v_copy_sql text; v_function_name text; v_job_id bigint; v_jobmon_schema text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_rowcount bigint; v_step_id bigint; v_total bigint := 0; v_trig_name text; v_type text; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition already running.'; RETURN 0; END IF; IF p_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN UNDO PARTITIONING: %s', p_parent_table)); v_step_id := add_step(v_job_id, format('Undoing partitioning for table %s', p_parent_table)); END IF; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables and stop new id partitions from being made. SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t JOIN pg_catalog.pg_class c ON t.tgrelid = c.oid WHERE tgname = v_trig_name AND c.relname = v_parent_tablename; SELECT proname INTO v_function_name FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid WHERE n.nspname = v_parent_schema AND proname = v_function_name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_parent_tablename); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; RETURN -1; END IF; END IF; -- END p_lock_wait IF EXECUTE format('DROP TRIGGER IF EXISTS %I ON %I.%I', v_trig_name, v_parent_schema, v_parent_tablename); END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later IF v_function_name IS NOT NULL THEN EXECUTE format('DROP FUNCTION IF EXISTS %I.%I()', v_parent_schema, v_function_name); END IF; IF v_jobmon_schema IS NOT NULL THEN IF (v_trig_name IS NOT NULL OR v_function_name IS NOT NULL) THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); ELSE PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process.'); END IF; END IF; WHILE v_batch_loop_count < p_batch_count LOOP -- Get ordered list of child table in set. Store in variable one at a time per loop until none are left. -- Not using show_partitions() so it can work on non-pg_partman partition sets WITH parent_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE c1.relname = v_parent_tablename AND n1.nspname = v_parent_schema ) SELECT c.relname INTO v_child_table FROM pg_catalog.pg_inherits i JOIN pg_catalog.pg_class c ON i.inhrelid = c.oid JOIN parent_info p ON i.inhparent = p.oid ORDER BY i.inhrelid ASC; EXIT WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Removing child partition: %s.%s', v_parent_schema, v_child_table)); END IF; -- lockwait timeout for table drop v_lock_obtained := FALSE; -- reset for reuse in loop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_child_table); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; RETURN -1; END IF; END IF; -- END p_lock_wait IF v_copy_sql := format('INSERT INTO %I.%I SELECT * FROM %I.%I' , v_parent_schema , v_parent_tablename , v_parent_schema , v_child_table); EXECUTE v_copy_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_parent_schema , v_child_table , v_parent_schema , v_parent_tablename); IF p_keep_table = false THEN EXECUTE format('DROP TABLE %I.%I', v_parent_schema, v_child_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table DROPPED. Moved %s rows to parent', v_rowcount)); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table UNINHERITED, not DROPPED. Copied %s rows to parent', v_rowcount)); END IF; END IF; SELECT partition_type INTO v_type FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_parent_schema||'.'||v_child_table; END IF; v_batch_loop_count := v_batch_loop_count + 1; v_undo_count := v_undo_count + 1; END LOOP; -- v_batch_loop_count IF v_undo_count = 0 THEN -- FOR loop never ran, so there's no child tables left. DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman (if it existed)'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) from % child table(s) to the parent: %', v_total, v_undo_count, p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', format('Copied %s row(s) from %s child table(s) to the parent', v_total, v_undo_count)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RETURN v_total; 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 UNDO PARTITIONING: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to undo time-based partitioning created by this extension */ CREATE OR REPLACE FUNCTION undo_partition_time(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval interval DEFAULT NULL, p_keep_table boolean DEFAULT true, p_lock_wait numeric DEFAULT 0) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_batch_loop_count int := 0; v_child_min timestamptz; v_child_loop_total bigint := 0; v_child_table text; v_control text; v_epoch boolean; v_function_name text; v_inner_loop_count int; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_move_sql text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_row record; v_rowcount bigint; v_step_id bigint; v_sub_count int; v_total bigint := 0; v_trig_name text; v_type text; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition_time')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition_time already running.'; RETURN 0; END IF; SELECT partition_type , partition_interval::interval , control , jobmon , epoch INTO v_type , v_partition_interval , v_control , v_jobmon , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; -- Check if any child tables are themselves partitioned or part of an inheritance tree. Prevent undo at this level if so. -- Need to either lock child tables at all levels or handle the proper removal of triggers on all child tables first -- before multi-level undo can be performed safely. FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table) LOOP SELECT count(*) INTO v_sub_count FROM pg_catalog.pg_inherits i JOIN pg_catalog.pg_class c ON i.inhparent = c.oid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_row.partition_tablename AND n.nspname = v_row.partition_schemaname; IF v_sub_count > 0 THEN RAISE EXCEPTION 'Child table for this parent has child table(s) itself (%). Run undo partitioning on this table or remove inheritance first to ensure all data is properly moved to parent', v_row.partition_schemaname||'.'||v_row.partition_tablename; END IF; END LOOP; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN UNDO PARTITIONING: %s', p_parent_table)); v_step_id := add_step(v_job_id, format('Undoing partitioning for table %s', p_parent_table)); END IF; IF p_batch_interval IS NULL THEN p_batch_interval := v_partition_interval; END IF; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables. SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t JOIN pg_catalog.pg_class c ON t.tgrelid = c.oid WHERE tgname = v_trig_name AND c.relname = v_parent_tablename; SELECT proname INTO v_function_name FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid WHERE n.nspname = v_parent_schema AND proname = v_function_name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_parent_tablename); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; RETURN -1; END IF; END IF; -- END p_lock_wait IF EXECUTE format('DROP TRIGGER IF EXISTS %I ON %I.%I', v_trig_name, v_parent_schema, v_parent_tablename); END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later IF v_function_name IS NOT NULL THEN EXECUTE format('DROP FUNCTION IF EXISTS %I.%I()', v_parent_schema, v_function_name); END IF; IF v_jobmon_schema IS NOT NULL THEN IF (v_trig_name IS NOT NULL OR v_function_name IS NOT NULL) THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); ELSE PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process.'); END IF; END IF; <> LOOP SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC'); EXIT outer_child_loop WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Removing child partition: %s.%s', v_parent_schema, v_child_table)); END IF; IF v_epoch = false THEN EXECUTE format('SELECT min(%I) FROM %I.%I', v_control, v_parent_schema, v_child_table) INTO v_child_min; ELSE EXECUTE format('SELECT to_timestamp(min(%I)) FROM %I.%I', v_control, v_parent_schema, v_child_table) INTO v_child_min; END IF; IF v_child_min IS NULL THEN -- No rows left in this child table. Remove from partition set. -- lockwait timeout for table drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_child_table); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; RETURN -1; END IF; END IF; -- END p_lock_wait IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_parent_schema , v_child_table , v_parent_schema , v_parent_tablename); IF p_keep_table = false THEN EXECUTE format('DROP TABLE %I.%I', v_parent_schema, v_child_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table DROPPED. Moved %s rows to parent', v_child_loop_total)); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table UNINHERITED, not DROPPED. Moved %s rows to parent', v_child_loop_total)); END IF; END IF; IF v_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_parent_schema||'.'||v_child_table; END IF; v_undo_count := v_undo_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached CONTINUE outer_child_loop; -- skip data moving steps below END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM %I.%I WHERE %I <= %L FOR UPDATE NOWAIT' , v_parent_schema , v_child_table , v_control , v_child_min + (p_batch_interval * v_inner_loop_count)); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; RETURN -1; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval IF v_epoch = false THEN v_move_sql := format('WITH move_data AS ( DELETE FROM %I.%I WHERE %I <= %L RETURNING *) INSERT INTO %I.%I SELECT * FROM move_data' , v_parent_schema , v_child_table , v_control , v_child_min + (p_batch_interval * v_inner_loop_count) , v_parent_schema , v_parent_tablename); ELSE v_move_sql := format('WITH move_data AS ( DELETE FROM %I.%I WHERE to_timestamp(%I) <= %L RETURNING *) INSERT INTO %I.%I SELECT * FROM move_data' , v_parent_schema , v_child_table , v_control , v_child_min + (p_batch_interval * v_inner_loop_count) , v_parent_schema , v_parent_tablename); END IF; EXECUTE v_move_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Moved %s rows to parent.', v_child_loop_total)); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; -- Check again if table is empty and go to outer loop again to drop it if so IF v_epoch = false THEN EXECUTE format('SELECT min(%I) FROM %I.%I', v_control, v_parent_schema, v_child_table) INTO v_child_min; ELSE EXECUTE format('SELECT to_timestamp(min(%I)) FROM %I.%I', v_control, v_parent_schema, v_child_table) INTO v_child_min; END IF; CONTINUE outer_child_loop WHEN v_child_min IS NULL; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC') LIMIT 1; IF v_child_table IS NULL THEN DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) to the parent. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', format('Copied %s row(s) to the parent. Removed %s partitions.', v_total, v_undo_count)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RETURN v_total; 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 UNDO PARTITIONING: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to undo id-based partitioning created by this extension */ CREATE OR REPLACE FUNCTION undo_partition_id(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval bigint DEFAULT NULL, p_keep_table boolean DEFAULT true, p_lock_wait numeric DEFAULT 0) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_batch_loop_count int := 0; v_child_loop_total bigint := 0; v_child_min bigint; v_child_table text; v_control text; v_exists int; v_function_name text; v_inner_loop_count int; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_move_sql text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_row record; v_rowcount bigint; v_step_id bigint; v_sub_count int; v_trig_name text; v_total bigint := 0; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition_id')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition_id already running.'; RETURN 0; END IF; SELECT partition_interval::bigint , control , jobmon INTO v_partition_interval , v_control , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; -- Check if any child tables are themselves partitioned or part of an inheritance tree. Prevent undo at this level if so. -- Need to either lock child tables at all levels or handle the proper removal of triggers on all child tables first -- before multi-level undo can be performed safely. FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table) LOOP SELECT count(*) INTO v_sub_count FROM pg_catalog.pg_inherits i JOIN pg_catalog.pg_class c ON i.inhparent = c.oid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_row.partition_tablename AND n.nspname = v_row.partition_schemaname; IF v_sub_count > 0 THEN RAISE EXCEPTION 'Child table for this parent has child table(s) itself (%). Run undo partitioning on this table or remove inheritance first to ensure all data is properly moved to parent', v_row.partition_schemaname||'.'||v_row.partition_tablename; END IF; END LOOP; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN UNDO PARTITIONING: %s', p_parent_table)); v_step_id := add_step(v_job_id, format('Undoing partitioning for table %s', p_parent_table)); END IF; IF p_batch_interval IS NULL THEN p_batch_interval := v_partition_interval; END IF; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables and stop new id partitions from being made. SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname ||'.'|| tablename = p_parent_table; v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t JOIN pg_catalog.pg_class c ON t.tgrelid = c.oid WHERE tgname = v_trig_name AND c.relname = v_parent_tablename; SELECT proname INTO v_function_name FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid WHERE n.nspname = v_parent_schema AND proname = v_function_name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_parent_tablename); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; RETURN -1; END IF; END IF; -- END p_lock_wait IF EXECUTE format('DROP TRIGGER IF EXISTS %I ON %I.%I', v_trig_name, v_parent_schema, v_parent_tablename); END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later IF v_function_name IS NOT NULL THEN EXECUTE format('DROP FUNCTION IF EXISTS %I.%I()', v_parent_schema, v_function_name); END IF; IF v_jobmon_schema IS NOT NULL THEN IF (v_trig_name IS NOT NULL OR v_function_name IS NOT NULL) THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); ELSE PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process.'); END IF; END IF; <> LOOP -- Get ordered list of child table in set. Store in variable one at a time per loop until none are left or batch count is reached. -- This easily allows it to loop over same child table until empty or move onto next child table after it's dropped SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC'); EXIT WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Removing child partition: %s.%s', v_parent_schema, v_child_table)); END IF; EXECUTE format('SELECT min(%I) FROM %I.%I', v_control, v_parent_schema, v_child_table) INTO v_child_min; IF v_child_min IS NULL THEN -- No rows left in this child table. Remove from partition set. -- lockwait timeout for table drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_child_table); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; RETURN -1; END IF; END IF; -- END p_lock_wait IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_parent_schema , v_child_table , v_parent_schema , v_parent_tablename); IF p_keep_table = false THEN EXECUTE format('DROP TABLE %I.%I', v_parent_schema, v_child_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table DROPPED. Moved %s rows to parent', v_child_loop_total)); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table UNINHERITED, not DROPPED. Moved %s rows to parent', v_child_loop_total)); END IF; END IF; v_undo_count := v_undo_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached CONTINUE outer_child_loop; -- skip data moving steps below END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP -- lockwait timeout for row batches IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM %I.%I WHERE %I <= %s FOR UPDATE NOWAIT' , v_parent_schema , v_child_table , v_control , v_child_min + (p_batch_interval * v_inner_loop_count)); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; RETURN -1; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval v_move_sql := format('WITH move_data AS ( DELETE FROM %I.%I WHERE %I <= %s RETURNING *) INSERT INTO %I.%I SELECT * FROM move_data' , v_parent_schema , v_child_table , v_control , v_child_min + (p_batch_interval * v_inner_loop_count) , v_parent_schema , v_parent_tablename); EXECUTE v_move_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Moved %s rows to parent.', v_child_loop_total)); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; -- Check again if table is empty and go to outer loop again to drop it if so EXECUTE format('SELECT min(%I) FROM %I.%I', v_control, v_parent_schema, v_child_table) INTO v_child_min; CONTINUE outer_child_loop WHEN v_child_min IS NULL; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC') LIMIT 1; IF v_child_table IS NULL THEN DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) to the parent. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', format('Copied %s row(s) to the parent. Removed %s partitions.', v_total, v_undo_count)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RETURN v_total; 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 UNDO PARTITIONING: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; pg_partman-4.6.0/updates/pg_partman--2.2.3--2.3.0.sql000066400000000000000000006650211412761706100214360ustar00rootroot00000000000000-- New migration documentation (doc/migration.md) to provide assistance changing an existing partition set to be used by pg_partman. Note that pg_partman does not support having child table names that do not comply with its naming convention. -- Added new configuration option to configuration table for time-based partitioning: infinite_time_partitions (Github Pull Request 84) -- By default, if no new data is being inserted, no new partitions are created. This keeps pg_partman from creating new, empty tables infinitely when data insertion has stopped. -- Setting this value to true in part_config or part_config_sub will tell pg_partman to continue making partitions forever with time-based partitioning, even if there's no new data -- Note that serial/id partition is not affected by this and will only make new partitions if new data is being inserted. -- Implemented the clustering work done in fork by https://github.com/dturon/pg_partman -- If a parent table is set to cluster on a specific index, that clustering property will be inherited to all children. -- This does NOT actually cluster the table data, it only inherits the cluster property so that if/when CLUSTER is run on the partition set, it will use a default. -- Note that FILLFACTOR is not inherited at this time, so adjust your re-cluster schedule accordingly. -- Optimized catalog lookups throughout many functions to improve performance on databases with many tables (Github Issue 87). -- For custom time partition sets, their trigger function has had this optimization applied (normal time & id did not have this issue). The next time a child partition is created, the new function will be put in place. If you need it immediately, run: create_function_time('parent_table'). -- Changed the "optimize_constraints" config option to calculate the "old" partition based on what the current partition is instead of the data contained in the partition set. For time series with the new infinite option above set, this means the "current" partition is always moving forward despite no new data. This returns it to the behaviour it had before v2.2.0. The reapply_constraints.py script had not been updated properly to account for the new config option and was not calculating the "old" tables properly at all (it was looking for tables much further in the past than intended) (Github Issue #89). -- New python script to provide additional VACUUM maintenance to help avoid needless vacuums and transaction id wraparound issues: vacuum_maintenance.py -- The script checks the age(relfrozenid) value of all tables in a partition set (parent & children) and runs a VACUUM on them if they are higher than vacuum_freeze_min_age. -- Passing the --freeze option will run a VACUUM FREEZE on all tables in a set that match the above criteria. -- Passing the --full option does the same as above, but runs VACUUM FULL instead. Note this will lock all tables it is vacuuming, possibly even the parent, so review the --dryrun list before running it. -- An explanation for the necessity of using this script is far beyond what can be written in a CHANGELOG or the script help itself. Google is your friend. Basically if you have old, static child tables that will never be dropped, it is highly recommend you run this occasionally with the --freeze option to avoid future database maintenance issues. -- Pulled in bugfix for using the intarray contrib extension in conjunction with constraint management (Github Pull Request #88). -- Pulled in bugfix for using bigint instead of int type for variable in apply_constraints() (Github Pull Request #90). -- Fixed bug that may have caused serial trigger function to not contain all optimized child table conditions. No write failures would have occurred, just slower than expected performance. -- Fixed bug in partition_data.py & undo_partition.py that would cause either to throw an error and then not reset autovacuum if script was interrupted (Ctrl+C) (Github Issue #92). -- They also now properly handle SIGTERM to reset autovac as well. -- Note that disabling/enabling autovac now requires an extra available connection to the database. ALTER TABLE @extschema@.part_config ADD COLUMN infinite_time_partitions boolean NOT NULL DEFAULT false; ALTER TABLE @extschema@.part_config_sub ADD COLUMN sub_infinite_time_partitions boolean NOT NULL DEFAULT false; CREATE TEMP TABLE partman_preserve_privs_temp (statement text); INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.check_subpart_sameconfig(text) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'check_subpart_sameconfig'; DROP FUNCTION @extschema@.check_subpart_sameconfig(text); /* * Function to manage pre-creation of the next partitions in a set. * Also manages dropping old partitions if the retention option is set. * If p_parent_table is passed, will only run run_maintenance() on that one table (no matter what the configuration table may have set for it) * Otherwise, will run on all tables in the config table with p_run_maintenance() set to true. * For large partition sets, running analyze can cause maintenance to take longer than expected. Can set p_analyze to false to avoid a forced analyze run. * Be aware that constraint exclusion may not work properly until an analyze on the partition set is run. */ CREATE OR REPLACE FUNCTION run_maintenance(p_parent_table text DEFAULT NULL, p_analyze boolean DEFAULT true, p_jobmon boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_check_subpart int; v_create_count int := 0; v_current_partition text; v_current_partition_id bigint; v_current_partition_timestamp timestamp; v_datetime_string text; v_drop_count int := 0; v_id_position int; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_created boolean; v_last_partition_id bigint; v_last_partition_timestamp timestamp; v_max_id_parent bigint; v_max_time_parent timestamp; v_next_partition_id bigint; v_next_partition_timestamp timestamp; v_parent_schema text; v_parent_tablename text; v_premade_count int; v_premake_id_max bigint; v_premake_id_min bigint; v_premake_timestamp_min timestamp; v_premake_timestamp_max timestamp; v_quarter text; v_row record; v_row_max_id record; v_row_max_time record; v_row_sub record; v_skip_maint boolean; v_step_id bigint; v_step_overflow_id bigint; v_step_serial_id bigint; v_sub_id_max bigint; v_sub_id_max_suffix bigint; v_sub_id_min bigint; v_sub_parent text; v_sub_timestamp_max timestamp; v_sub_timestamp_max_suffix timestamp; v_sub_timestamp_min timestamp; v_tablename text; v_tables_list_sql text; v_time_position int; v_year text; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman maintenance already running.'; RETURN; END IF; 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' AND e.extnamespace = n.oid; END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.add_job(%L)', v_jobmon_schema, 'PARTMAN RUN MAINTENANCE') INTO v_job_id; EXECUTE format('SELECT %I.add_step(%L, %L)', v_jobmon_schema, v_job_id, 'Running maintenance loop') INTO v_step_id; END IF; -- Check for consistent data in part_config_sub table. Was unable to get this working properly as either a constraint or trigger. -- Would either delay raising an error until the next write (which I cannot predict) or disallow future edits to update a sub-partition set's configuration. -- This way at least provides a consistent way to check that I know will run. If anyone can get a working constraint/trigger, please help! -- Don't have to worry about this in the serial trigger maintenance since subpartitioning requires run_maintenance(). FOR v_row IN SELECT sub_parent FROM @extschema@.part_config_sub LOOP SELECT count(*) INTO v_check_subpart FROM @extschema@.check_subpart_sameconfig(v_row.sub_parent); IF v_check_subpart > 1 THEN RAISE EXCEPTION 'Inconsistent data in part_config_sub table. Sub-partition tables that are themselves sub-partitions cannot have differing configuration values among their siblings. Run this query: "SELECT * FROM @extschema@.check_subpart_sameconfig(''%'');" This should only return a single row or nothing. If multiple rows are returned, results are all children of the given parent. Update the differing values to be consistent for your desired values.', v_row.sub_parent; END IF; END LOOP; v_row := NULL; -- Ensure it's reset v_tables_list_sql := 'SELECT parent_table , partition_type , partition_interval , control , premake , datetime_string , undo_in_progress , sub_partition_set_full , epoch , infinite_time_partitions FROM @extschema@.part_config WHERE sub_partition_set_full = false'; IF p_parent_table IS NULL THEN v_tables_list_sql := v_tables_list_sql || ' AND use_run_maintenance = true'; ELSE v_tables_list_sql := v_tables_list_sql || format(' AND parent_table = %L', p_parent_table); END IF; FOR v_row IN EXECUTE v_tables_list_sql LOOP CONTINUE WHEN v_row.undo_in_progress; v_skip_maint := true; -- reset every loop SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(v_row.parent_table, '.', 1) AND tablename = split_part(v_row.parent_table, '.', 2); SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LIMIT 1; IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_last_partition: %', v_row.parent_table, v_last_partition; END IF; IF v_row.partition_type = 'time' OR v_row.partition_type = 'time-custom' THEN v_time_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; IF v_row.partition_interval::interval <> '3 months' OR (v_row.partition_interval::interval = '3 months' AND v_row.partition_type = 'time-custom') THEN v_last_partition_timestamp := to_timestamp(substring(v_last_partition from v_time_position), v_row.datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(v_last_partition FROM v_time_position), 'q', 1); v_quarter := split_part(substring(v_last_partition FROM v_time_position), 'q', 2); CASE WHEN v_quarter = '1' THEN v_last_partition_timestamp := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_last_partition_timestamp := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_last_partition_timestamp := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_last_partition_timestamp := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_time IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP IF v_row.epoch = false THEN EXECUTE format('SELECT max(%I)::text FROM %I.%I' , v_row.control , v_row_max_time.partition_schemaname , v_row_max_time.partition_tablename ) INTO v_current_partition_timestamp; ELSE EXECUTE format('SELECT to_timestamp(max(%I))::text FROM %I.%I' , v_row.control , v_row_max_time.partition_schemaname , v_row_max_time.partition_tablename ) INTO v_current_partition_timestamp; END IF; IF v_current_partition_timestamp IS NOT NULL THEN SELECT suffix_timestamp INTO v_current_partition_timestamp FROM @extschema@.show_partition_name(v_row.parent_table, v_current_partition_timestamp::text); EXIT; END IF; END LOOP; -- Check for values in the parent table. If they are there and greater than all child values, use that instead -- This allows maintenance to continue working properly if there is a large gap in data insertion. Data will remain in parent, but new tables will be created IF v_row.epoch = false THEN EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_row.control, v_parent_schema, v_parent_tablename) INTO v_max_time_parent; ELSE EXECUTE format('SELECT to_timestamp(max(%I)) FROM ONLY %I.%I', v_row.control, v_parent_schema, v_parent_tablename) INTO v_max_time_parent; END IF; IF p_debug THEN RAISE NOTICE 'run_maint: v_current_partition_timestamp: %, v_max_time_parent: %', v_current_partition_timestamp, v_max_time_parent; END IF; IF v_max_time_parent > v_current_partition_timestamp THEN SELECT suffix_timestamp INTO v_current_partition_timestamp FROM @extschema@.show_partition_name(v_row.parent_table, v_max_time_parent::text); END IF; IF v_current_partition_timestamp IS NULL THEN -- Partition set is completely empty IF v_row.infinite_time_partitions IS TRUE THEN -- Set it to now so new partitions continue to be created v_current_partition_timestamp = CURRENT_TIMESTAMP; ELSE -- Nothing to do CONTINUE; END IF; END IF; -- If this is a subpartition, determine if the last child table has been made. If so, mark it as full so future maintenance runs can skip it SELECT sub_min::timestamp, sub_max::timestamp INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'time'); IF v_sub_timestamp_max IS NOT NULL THEN SELECT suffix_timestamp INTO v_sub_timestamp_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_timestamp_max::text); IF v_sub_timestamp_max_suffix = v_last_partition_timestamp THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; -- Check and see how many premade partitions there are. v_premade_count = round(EXTRACT('epoch' FROM age(v_last_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); v_next_partition_timestamp := v_last_partition_timestamp; IF p_debug THEN RAISE NOTICE 'run_maint before loop: current_partition_timestamp: %, v_premade_count: %, v_sub_timestamp_min: %, v_sub_timestamp_max: %' , v_current_partition_timestamp , v_premade_count , v_sub_timestamp_min , v_sub_timestamp_max; END IF; -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed WHILE (v_premade_count < v_row.premake) LOOP IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_premade_count: %, v_next_partition_timestamp: %', v_row.parent_table, v_premade_count, v_next_partition_timestamp; END IF; IF v_next_partition_timestamp < v_sub_timestamp_min OR v_next_partition_timestamp > v_sub_timestamp_max THEN -- With subpartitioning, no need to run if the timestamp is not in the parent table's range EXIT; END IF; BEGIN v_next_partition_timestamp := v_next_partition_timestamp + v_row.partition_interval::interval; EXCEPTION WHEN datetime_field_overflow THEN v_premade_count := v_row.premake; -- do this so it can exit the premake check loop and continue in the outer for loop IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.add_step(%L, %L)', v_jobmon_schema, v_job_id, 'Attempted partition time interval is outside PostgreSQL''s supported time range.') INTO v_step_overflow_id; EXECUTE format('SELECT %I.update_step(%L, %L, %L)', v_jobmon_schema, v_step_overflow_id, 'CRITICAL', 'Child partition creation skippd for parent table '||v_partition_time); END IF; RAISE WARNING 'Attempted partition time interval is outside PostgreSQL''s supported time range. Child partition creation skipped for parent table %', v_row.parent_table; CONTINUE; END; v_last_partition_created := @extschema@.create_partition_time(v_row.parent_table, ARRAY[v_next_partition_timestamp], p_analyze); IF v_last_partition_created THEN v_create_count := v_create_count + 1; PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); END IF; v_premade_count = round(EXTRACT('epoch' FROM age(v_next_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); END LOOP; ELSIF v_row.partition_type = 'id' THEN -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%I)::text FROM %I.%I' , v_row.control , v_row_max_id.partition_schemaname , v_row_max_id.partition_tablename) INTO v_current_partition_id; IF v_current_partition_id IS NOT NULL THEN SELECT suffix_id INTO v_current_partition_id FROM @extschema@.show_partition_name(v_row.parent_table, v_current_partition_id::text); EXIT; END IF; END LOOP; -- Check for values in the parent table. If they are there and greater than all child values, use that instead -- This allows maintenance to continue working properly if there is a large gap in data insertion. Data will remain in parent, but new tables will be created EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_row.control, v_parent_schema, v_parent_tablename) INTO v_max_id_parent; IF v_max_id_parent > v_current_partition_id THEN SELECT suffix_id INTO v_current_partition_id FROM @extschema@.show_partition_name(v_row.parent_table, v_max_id_parent::text); END IF; IF v_current_partition_id IS NULL THEN -- Partition set is completely empty. Nothing to do CONTINUE; END IF; v_id_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; v_last_partition_id = substring(v_last_partition from v_id_position)::bigint; -- Determine if this table is a child of a subpartition parent. If so, get limits to see if run_maintenance even needs to run for it. SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'id'); IF v_sub_id_max IS NOT NULL THEN SELECT suffix_id INTO v_sub_id_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_id_max::text); IF v_sub_id_max_suffix = v_last_partition_id THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; v_next_partition_id := v_last_partition_id; v_premade_count := ((v_last_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed. WHILE (v_premade_count < v_row.premake) LOOP IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_premade_count: %, v_next_partition_id: %', v_row.parent_table, v_premade_count, v_next_partition_id; END IF; IF v_next_partition_id < v_sub_id_min OR v_next_partition_id > v_sub_id_max THEN -- With subpartitioning, no need to run if the id is not in the parent table's range EXIT; END IF; v_next_partition_id := v_next_partition_id + v_row.partition_interval::bigint; v_last_partition_created := @extschema@.create_partition_id(v_row.parent_table, ARRAY[v_next_partition_id], p_analyze); IF v_last_partition_created THEN v_create_count := v_create_count + 1; PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); END IF; v_premade_count := ((v_next_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); END LOOP; END IF; -- end main IF check for time or id -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table := v_row.parent_table, p_job_id := v_job_id, p_debug := p_debug); END LOOP; -- end of creation loop -- Manage dropping old partitions if retention option is set FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND (partition_type = 'time' OR partition_type = 'time-custom') LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); END IF; END IF; IF v_drop_count > 0 THEN PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); END IF; END LOOP; FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND partition_type = 'id' LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); END IF; END IF; IF v_drop_count > 0 THEN PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.update_step(%L, %L, ''Partition maintenance finished. %s partitions made. %s partitions dropped.'')' , v_jobmon_schema , v_step_id , 'OK' , v_create_count , v_drop_count); IF v_step_overflow_id IS NOT NULL OR v_step_serial_id IS NOT NULL THEN EXECUTE format('SELECT %I.fail_job(%L)', v_jobmon_schema, v_job_id); ELSE EXECUTE format('SELECT %I.close_job(%L)', v_jobmon_schema, v_job_id); END IF; END IF; 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 RUN MAINTENANCE'')', v_jobmon_schema) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to create id partitions */ CREATE OR REPLACE FUNCTION create_partition_id(p_parent_table text, p_partition_ids bigint[], p_analyze boolean DEFAULT true) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_exists text; v_grantees text[]; v_hasoids boolean; v_id bigint; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_parent_tablespace text; v_partition_interval bigint; v_partition_created boolean := false; v_partition_name text; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_sub_id_max bigint; v_sub_id_min bigint; v_unlogged char; BEGIN SELECT control , partition_interval , inherit_fk , jobmon INTO v_control , v_partition_interval , v_inherit_fk , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'id'); SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1) AND tablename = split_part(p_parent_table, '.', 2); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; FOREACH v_id IN ARRAY p_partition_ids LOOP -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_id_min IS NOT NULL THEN IF v_id < v_sub_id_min OR v_id > v_sub_id_max THEN CONTINUE; END IF; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_id::text, TRUE); -- If child table already exists, skip creation SELECT tablename INTO v_exists FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema AND tablename = v_partition_name; IF v_exists IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_id||' to '||(v_id + v_partition_interval)-1); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_parent_tablename AND n.nspname = v_parent_schema; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_parent_tablename AND n.nspname = v_parent_schema; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %s AND %I < %s )' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_control , v_id , v_control , v_id + v_partition_interval); EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I', v_parent_schema, v_partition_name, v_parent_schema, v_parent_tablename); PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_use_run_maintenance , sub_infinite_time_partitions , sub_jobmon FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Subpartitioning '||v_partition_name); END IF; 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_use_run_maintenance := %L , p_inherit_fk := %L , p_epoch := %L , p_jobmon := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_use_run_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions WHERE parent_table = v_parent_schema||'.'||v_partition_name; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; -- end sub partitioning LOOP v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s', p_parent_table)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; PERFORM close_job(v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RETURN v_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to create a child table in a time-based partition set */ CREATE OR REPLACE FUNCTION create_partition_time(p_parent_table text, p_partition_times timestamp[], p_analyze boolean DEFAULT true) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_datetime_string text; v_exists text; v_epoch boolean; v_grantees text[]; v_hasoids boolean; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_partition_created boolean := false; v_partition_name text; v_partition_suffix text; v_parent_tablespace text; v_partition_interval interval; v_partition_timestamp_end timestamp; v_partition_timestamp_start timestamp; v_quarter text; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_sub_timestamp_max timestamp; v_sub_timestamp_min timestamp; v_trunc_value text; v_time timestamp; v_type text; v_unlogged char; v_year text; BEGIN SELECT partition_type , control , partition_interval , epoch , inherit_fk , jobmon , datetime_string INTO v_type , v_control , v_partition_interval , v_epoch , v_inherit_fk , v_jobmon , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'time' OR partition_type = 'time-custom'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::timestamp, sub_max::timestamp INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'time'); SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1) AND tablename = split_part(p_parent_table, '.', 2); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_timestamp_start := v_time; BEGIN v_partition_timestamp_end := v_time + v_partition_interval; 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_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_time||' skipped'); CONTINUE; END; -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_timestamp_min IS NOT NULL THEN IF v_time < v_sub_timestamp_min OR v_time > v_sub_timestamp_max THEN CONTINUE; END IF; END IF; -- This suffix generation code is in partition_data_time() as well v_partition_suffix := to_char(v_time, v_datetime_string); v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); SELECT tablename INTO v_exists FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema AND tablename = v_partition_name; IF v_exists IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Creating new partition %s.%s with interval from %s to %s' , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end-'1sec'::interval)); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_parent_tablename AND n.nspname = v_parent_schema; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_parent_tablename AND n.nspname = v_parent_schema; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; IF v_epoch = false THEN EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_control , v_partition_timestamp_start , v_control , v_partition_timestamp_end); ELSE EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (to_timestamp(%I) >= %L AND to_timestamp(%I) < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_control , v_partition_timestamp_start , v_control , v_partition_timestamp_end); END IF; EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); -- If custom time, set extra config options. IF v_type = 'time-custom' THEN INSERT INTO @extschema@.custom_time_partitions (parent_table, child_table, partition_range) VALUES ( p_parent_table, v_parent_schema||'.'||v_partition_name, tstzrange(v_partition_timestamp_start, v_partition_timestamp_end, '[)') ); END IF; PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_use_run_maintenance , sub_infinite_time_partitions , sub_jobmon FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Subpartitioning %s.%s', v_parent_schema, v_partition_name)); END IF; 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_use_run_maintenance := %L , p_inherit_fk := %L , p_epoch := %L , p_jobmon := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_use_run_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions WHERE parent_table = v_parent_schema||'.'||v_partition_name; END LOOP; -- end sub partitioning LOOP v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s. Attempted intervals: %s', p_parent_table, p_partition_times)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; 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; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RETURN v_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to undo id-based partitioning created by this extension */ CREATE OR REPLACE FUNCTION undo_partition_id(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval bigint DEFAULT NULL, p_keep_table boolean DEFAULT true, p_lock_wait numeric DEFAULT 0) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_batch_loop_count int := 0; v_child_loop_total bigint := 0; v_child_min bigint; v_child_table text; v_control text; v_exists int; v_function_name text; v_inner_loop_count int; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_move_sql text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_row record; v_rowcount bigint; v_step_id bigint; v_sub_count int; v_trig_name text; v_total bigint := 0; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition_id')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition_id already running.'; RETURN 0; END IF; SELECT partition_interval::bigint , control , jobmon INTO v_partition_interval , v_control , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; -- Check if any child tables are themselves partitioned or part of an inheritance tree. Prevent undo at this level if so. -- Need to either lock child tables at all levels or handle the proper removal of triggers on all child tables first -- before multi-level undo can be performed safely. FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table) LOOP SELECT count(*) INTO v_sub_count FROM pg_catalog.pg_inherits i JOIN pg_catalog.pg_class c ON i.inhparent = c.oid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_row.partition_tablename AND n.nspname = v_row.partition_schemaname; IF v_sub_count > 0 THEN RAISE EXCEPTION 'Child table for this parent has child table(s) itself (%). Run undo partitioning on this table or remove inheritance first to ensure all data is properly moved to parent', v_row.partition_schemaname||'.'||v_row.partition_tablename; END IF; END LOOP; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN UNDO PARTITIONING: %s', p_parent_table)); v_step_id := add_step(v_job_id, format('Undoing partitioning for table %s', p_parent_table)); END IF; IF p_batch_interval IS NULL THEN p_batch_interval := v_partition_interval; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1) AND tablename = split_part(p_parent_table, '.', 2); -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables and stop new id partitions from being made. v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t JOIN pg_catalog.pg_class c ON t.tgrelid = c.oid WHERE tgname = v_trig_name AND c.relname = v_parent_tablename; SELECT proname INTO v_function_name FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid WHERE n.nspname = v_parent_schema AND proname = v_function_name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_parent_tablename); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; RETURN -1; END IF; END IF; -- END p_lock_wait IF EXECUTE format('DROP TRIGGER IF EXISTS %I ON %I.%I', v_trig_name, v_parent_schema, v_parent_tablename); END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later IF v_function_name IS NOT NULL THEN EXECUTE format('DROP FUNCTION IF EXISTS %I.%I()', v_parent_schema, v_function_name); END IF; IF v_jobmon_schema IS NOT NULL THEN IF (v_trig_name IS NOT NULL OR v_function_name IS NOT NULL) THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); ELSE PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process.'); END IF; END IF; <> LOOP -- Get ordered list of child table in set. Store in variable one at a time per loop until none are left or batch count is reached. -- This easily allows it to loop over same child table until empty or move onto next child table after it's dropped SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC') LIMIT 1; EXIT WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Removing child partition: %s.%s', v_parent_schema, v_child_table)); END IF; EXECUTE format('SELECT min(%I) FROM %I.%I', v_control, v_parent_schema, v_child_table) INTO v_child_min; IF v_child_min IS NULL THEN -- No rows left in this child table. Remove from partition set. -- lockwait timeout for table drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_child_table); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; RETURN -1; END IF; END IF; -- END p_lock_wait IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_parent_schema , v_child_table , v_parent_schema , v_parent_tablename); IF p_keep_table = false THEN EXECUTE format('DROP TABLE %I.%I', v_parent_schema, v_child_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table DROPPED. Moved %s rows to parent', v_child_loop_total)); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table UNINHERITED, not DROPPED. Moved %s rows to parent', v_child_loop_total)); END IF; END IF; v_undo_count := v_undo_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached CONTINUE outer_child_loop; -- skip data moving steps below END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP -- lockwait timeout for row batches IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM %I.%I WHERE %I <= %s FOR UPDATE NOWAIT' , v_parent_schema , v_child_table , v_control , v_child_min + (p_batch_interval * v_inner_loop_count)); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; RETURN -1; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval v_move_sql := format('WITH move_data AS ( DELETE FROM %I.%I WHERE %I <= %s RETURNING *) INSERT INTO %I.%I SELECT * FROM move_data' , v_parent_schema , v_child_table , v_control , v_child_min + (p_batch_interval * v_inner_loop_count) , v_parent_schema , v_parent_tablename); EXECUTE v_move_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Moved %s rows to parent.', v_child_loop_total)); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; -- Check again if table is empty and go to outer loop again to drop it if so EXECUTE format('SELECT min(%I) FROM %I.%I', v_control, v_parent_schema, v_child_table) INTO v_child_min; CONTINUE outer_child_loop WHEN v_child_min IS NULL; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC') LIMIT 1; IF v_child_table IS NULL THEN DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) to the parent. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', format('Copied %s row(s) to the parent. Removed %s partitions.', v_total, v_undo_count)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RETURN v_total; 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 UNDO PARTITIONING: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to undo time-based partitioning created by this extension */ CREATE OR REPLACE FUNCTION undo_partition_time(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval interval DEFAULT NULL, p_keep_table boolean DEFAULT true, p_lock_wait numeric DEFAULT 0) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_batch_loop_count int := 0; v_child_min timestamptz; v_child_loop_total bigint := 0; v_child_table text; v_control text; v_epoch boolean; v_function_name text; v_inner_loop_count int; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_move_sql text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_row record; v_rowcount bigint; v_step_id bigint; v_sub_count int; v_total bigint := 0; v_trig_name text; v_type text; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition_time')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition_time already running.'; RETURN 0; END IF; SELECT partition_type , partition_interval::interval , control , jobmon , epoch INTO v_type , v_partition_interval , v_control , v_jobmon , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; -- Check if any child tables are themselves partitioned or part of an inheritance tree. Prevent undo at this level if so. -- Need to either lock child tables at all levels or handle the proper removal of triggers on all child tables first -- before multi-level undo can be performed safely. FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table) LOOP SELECT count(*) INTO v_sub_count FROM pg_catalog.pg_inherits i JOIN pg_catalog.pg_class c ON i.inhparent = c.oid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_row.partition_tablename AND n.nspname = v_row.partition_schemaname; IF v_sub_count > 0 THEN RAISE EXCEPTION 'Child table for this parent has child table(s) itself (%). Run undo partitioning on this table or remove inheritance first to ensure all data is properly moved to parent', v_row.partition_schemaname||'.'||v_row.partition_tablename; END IF; END LOOP; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN UNDO PARTITIONING: %s', p_parent_table)); v_step_id := add_step(v_job_id, format('Undoing partitioning for table %s', p_parent_table)); END IF; IF p_batch_interval IS NULL THEN p_batch_interval := v_partition_interval; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1) AND tablename = split_part(p_parent_table, '.', 2); -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables. v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t JOIN pg_catalog.pg_class c ON t.tgrelid = c.oid WHERE tgname = v_trig_name AND c.relname = v_parent_tablename; SELECT proname INTO v_function_name FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid WHERE n.nspname = v_parent_schema AND proname = v_function_name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_parent_tablename); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; RETURN -1; END IF; END IF; -- END p_lock_wait IF EXECUTE format('DROP TRIGGER IF EXISTS %I ON %I.%I', v_trig_name, v_parent_schema, v_parent_tablename); END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later IF v_function_name IS NOT NULL THEN EXECUTE format('DROP FUNCTION IF EXISTS %I.%I()', v_parent_schema, v_function_name); END IF; IF v_jobmon_schema IS NOT NULL THEN IF (v_trig_name IS NOT NULL OR v_function_name IS NOT NULL) THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); ELSE PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process.'); END IF; END IF; <> LOOP -- Get ordered list of child table in set. Store in variable one at a time per loop until none are left or batch count is reached. -- This easily allows it to loop over same child table until empty or move onto next child table after it's dropped SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC') LIMIT 1; EXIT outer_child_loop WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Removing child partition: %s.%s', v_parent_schema, v_child_table)); END IF; IF v_epoch = false THEN EXECUTE format('SELECT min(%I) FROM %I.%I', v_control, v_parent_schema, v_child_table) INTO v_child_min; ELSE EXECUTE format('SELECT to_timestamp(min(%I)) FROM %I.%I', v_control, v_parent_schema, v_child_table) INTO v_child_min; END IF; IF v_child_min IS NULL THEN -- No rows left in this child table. Remove from partition set. -- lockwait timeout for table drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_child_table); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; RETURN -1; END IF; END IF; -- END p_lock_wait IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_parent_schema , v_child_table , v_parent_schema , v_parent_tablename); IF p_keep_table = false THEN EXECUTE format('DROP TABLE %I.%I', v_parent_schema, v_child_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table DROPPED. Moved %s rows to parent', v_child_loop_total)); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table UNINHERITED, not DROPPED. Moved %s rows to parent', v_child_loop_total)); END IF; END IF; IF v_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_parent_schema||'.'||v_child_table; END IF; v_undo_count := v_undo_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached CONTINUE outer_child_loop; -- skip data moving steps below END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM %I.%I WHERE %I <= %L FOR UPDATE NOWAIT' , v_parent_schema , v_child_table , v_control , v_child_min + (p_batch_interval * v_inner_loop_count)); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; RETURN -1; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval IF v_epoch = false THEN v_move_sql := format('WITH move_data AS ( DELETE FROM %I.%I WHERE %I <= %L RETURNING *) INSERT INTO %I.%I SELECT * FROM move_data' , v_parent_schema , v_child_table , v_control , v_child_min + (p_batch_interval * v_inner_loop_count) , v_parent_schema , v_parent_tablename); ELSE v_move_sql := format('WITH move_data AS ( DELETE FROM %I.%I WHERE to_timestamp(%I) <= %L RETURNING *) INSERT INTO %I.%I SELECT * FROM move_data' , v_parent_schema , v_child_table , v_control , v_child_min + (p_batch_interval * v_inner_loop_count) , v_parent_schema , v_parent_tablename); END IF; EXECUTE v_move_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Moved %s rows to parent.', v_child_loop_total)); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; -- Check again if table is empty and go to outer loop again to drop it if so IF v_epoch = false THEN EXECUTE format('SELECT min(%I) FROM %I.%I', v_control, v_parent_schema, v_child_table) INTO v_child_min; ELSE EXECUTE format('SELECT to_timestamp(min(%I)) FROM %I.%I', v_control, v_parent_schema, v_child_table) INTO v_child_min; END IF; CONTINUE outer_child_loop WHEN v_child_min IS NULL; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC') LIMIT 1; IF v_child_table IS NULL THEN DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) to the parent. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', format('Copied %s row(s) to the parent. Removed %s partitions.', v_total, v_undo_count)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RETURN v_total; 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 UNDO PARTITIONING: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to apply cluster from parent to child table * Adapted from code fork by https://github.com/dturon/pg_partman */ CREATE FUNCTION apply_cluster(p_parent_schema text, p_parent_tablename text, p_child_schema text, p_child_tablename text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_parent_indexdef text; v_row record; v_sql text; BEGIN WITH parent_info AS ( SELECT c.oid AS parent_oid FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = p_parent_schema AND c.relname = p_parent_tablename ) SELECT substring(pg_get_indexdef(i.indexrelid) from ' USING .*$') AS index_def INTO v_parent_indexdef FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid JOIN parent_info p ON p.parent_oid = indrelid WHERE i.indisclustered = true; -- Loop over all existing indexes in child table to find one with matching definition FOR v_row IN WITH child_info AS ( SELECT c.oid AS child_oid FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = p_child_schema AND c.relname = p_child_tablename ) SELECT substring(pg_get_indexdef(i.indexrelid) from ' USING .*$') AS child_indexdef , c.relname AS child_indexname FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid JOIN child_info p ON p.child_oid = indrelid LOOP IF v_row.child_indexdef = v_parent_indexdef THEN v_sql = format('ALTER TABLE %I.%I CLUSTER ON %I', p_child_schema, p_child_tablename, v_row.child_indexname); RAISE DEBUG '%', v_sql; EXECUTE v_sql; END IF; END LOOP; END; $$; /* * Apply constraints managed by partman extension */ CREATE OR REPLACE FUNCTION apply_constraints(p_parent_table text, p_child_table text DEFAULT NULL, p_analyze boolean DEFAULT FALSE, p_job_id bigint DEFAULT NULL, p_debug boolean DEFAULT FALSE) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_child_exists text; v_child_tablename text; v_col text; v_constraint_cols text[]; v_constraint_col_type text; v_constraint_name text; v_constraint_values record; v_control text; v_datetime_string text; v_existing_constraint_name text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_id bigint; v_last_partition_timestamp timestamp; v_max_id bigint; v_max_timestamp timestamp; v_old_search_path text; v_optimize_constraint int; v_parent_schema text; v_parent_table text; v_parent_tablename text; v_partition_interval text; v_partition_suffix text; v_premake int; v_sql text; v_step_id bigint; v_suffix_position int; v_type text; BEGIN SELECT parent_table , partition_type , control , premake , partition_interval , optimize_constraint , datetime_string , constraint_cols , jobmon INTO v_parent_table , v_type , v_control , v_premake , v_partition_interval , v_optimize_constraint , v_datetime_string , v_constraint_cols , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND constraint_cols IS NOT NULL; IF v_constraint_cols IS NULL THEN IF p_debug THEN RAISE NOTICE 'Given parent table (%) not set up for constraint management (constraint_cols is NULL)', p_parent_table; END IF; -- Returns silently to allow this function to be simply called by maintenance processes without having to check if config options are set. RETURN; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(v_parent_table, '.', 1) AND tablename = split_part(v_parent_table, '.', 2); IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE CONSTRAINT: %s', v_parent_table)); ELSE v_job_id = p_job_id; END IF; END IF; -- If p_child_table is null, figure out the partition that is the one right before the optimize_constraint value backwards. IF p_child_table IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying additional constraints: Automatically determining most recent child on which to apply constraints'); END IF; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(v_parent_table, 'DESC') LIMIT 1; v_suffix_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; IF v_type IN ('time', 'time-custom') THEN v_last_partition_timestamp := to_timestamp(substring(v_last_partition from v_suffix_position), v_datetime_string); v_partition_suffix := to_char(v_last_partition_timestamp - (v_partition_interval::interval * (v_optimize_constraint + v_premake + 1) ), v_datetime_string); ELSIF v_type = 'id' THEN v_last_partition_id := substring(v_last_partition from v_suffix_position)::int; v_partition_suffix := (v_last_partition_id - (v_partition_interval::int * (v_optimize_constraint + v_premake + 1) ))::text; END IF; v_child_tablename := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); IF p_debug THEN RAISE NOTICE 'apply_constraint: v_parent_tablename: % , v_partition_suffix: %', v_parent_tablename, v_partition_suffix; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Target child table: %s.%s', v_parent_schema, v_child_tablename)); END IF; ELSE v_child_tablename = split_part(p_child_table, '.', 2); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying additional constraints: Checking if target child table exists'); END IF; SELECT tablename FROM pg_catalog.pg_tables INTO v_child_exists WHERE schemaname = v_parent_schema AND tablename = v_child_tablename; IF v_child_exists IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Target child table (%s) does not exist. Skipping constraint creation.', v_child_tablename)); IF p_job_id IS NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; IF p_debug THEN RAISE NOTICE 'Target child table (%) does not exist. Skipping constraint creation.', v_child_tablename; END IF; RETURN; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; FOREACH v_col IN ARRAY v_constraint_cols LOOP SELECT con.conname INTO v_existing_constraint_name FROM pg_catalog.pg_constraint con JOIN pg_class c ON c.oid = con.conrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN pg_catalog.pg_attribute a ON con.conrelid = a.attrelid WHERE c.relname = v_child_tablename AND n.nspname = v_parent_schema AND con.conname LIKE 'partmanconstr_%' AND con.contype = 'c' AND a.attname = v_col AND ARRAY[a.attnum] OPERATOR(pg_catalog.<@) con.conkey AND a.attisdropped = false; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Applying additional constraints: Applying new constraint on column: %s', v_col)); END IF; IF v_existing_constraint_name IS NOT NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Partman managed constraint already exists on this table (%s) and column (%s). Skipping creation.', v_child_tablename, v_col)); END IF; IF p_debug THEN RAISE NOTICE 'Partman managed constraint already exists on this table (%) and column (%). Skipping creation.', v_child_tablename, v_col ; END IF; CONTINUE; END IF; -- Ensure column name gets put on end of constraint name to help avoid naming conflicts v_constraint_name := @extschema@.check_name_length('partmanconstr_'||v_child_tablename, p_suffix := '_'||v_col); EXECUTE format('SELECT min(%I)::text AS min, max(%I)::text AS max FROM %I.%I', v_col, v_col, v_parent_schema, v_child_tablename) INTO v_constraint_values; IF v_constraint_values IS NOT NULL THEN v_sql := format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I <= %L)' , v_parent_schema , v_child_tablename , v_constraint_name , v_col , v_constraint_values.min , v_col , v_constraint_values.max); IF p_debug THEN RAISE NOTICE 'Constraint creation query: %', v_sql; END IF; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('New constraint created: %s', v_sql)); END IF; ELSE IF p_debug THEN RAISE NOTICE 'Given column (%) contains all NULLs. No constraint created', v_col; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Given column (%s) contains all NULLs. No constraint created', v_col)); END IF; END IF; END LOOP; IF p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Applying additional constraints: Running analyze on partition set: %s', v_parent_table)); END IF; IF p_debug THEN RAISE NOTICE 'Running analyze on partition set: %', v_parent_table; END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; 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 CONSTRAINT: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Apply foreign keys that exist on the given parent to the given child table */ CREATE OR REPLACE FUNCTION apply_foreign_keys(p_parent_table text, p_child_table text, p_job_id bigint DEFAULT NULL, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_count int := 0; v_job_id bigint; v_jobmon text; v_jobmon_schema text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_ref_schema text; v_ref_table text; v_row record; v_schemaname text; v_sql text; v_step_id bigint; v_tablename text; BEGIN SELECT jobmon INTO v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN APPLYING FOREIGN KEYS: %s', p_parent_table)); ELSE -- Don't create a new job, add steps into given job v_job_id := p_job_id; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Applying foreign keys to %s if they exist on parent', p_child_table)); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1) AND tablename = split_part(p_parent_table, '.', 2); SELECT schemaname, tablename INTO v_schemaname, v_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_child_table, '.', 1) AND tablename = split_part(p_child_table, '.', 2); IF v_tablename IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'CRITICAL', format('Target child table (%s) does not exist.', p_child_table)); PERFORM fail_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RAISE EXCEPTION 'Target child table (%) does not exist.', p_child_table; RETURN; END IF; FOR v_row 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 JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_parent_tablename AND n.nspname = v_parent_schema AND contype = 'f' LOOP v_sql := format('ALTER TABLE %I.%I ADD %s' , v_schemaname , v_tablename , v_row.constraint_def); IF p_debug THEN RAISE NOTICE 'Constraint creation query: %', v_sql; END IF; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'FK applied'); END IF; v_count := v_count + 1; END LOOP; IF v_count = 0 AND v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'No FKs found on parent'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; 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 APPLYING FOREIGN KEYS: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to turn a table into the parent of a partition set */ CREATE OR REPLACE FUNCTION 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_use_run_maintenance boolean DEFAULT NULL , p_start_partition text DEFAULT NULL , p_inherit_fk boolean DEFAULT true , p_epoch boolean DEFAULT false , p_jobmon boolean DEFAULT true , p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_base_timestamp timestamp; v_count int := 1; v_datetime_string 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_id_position int; v_job_id bigint; v_jobmon_schema text; v_last_partition_created boolean; v_max bigint; v_notnull boolean; v_old_search_path text; v_parent_partition_id bigint; v_parent_partition_timestamp timestamp; v_parent_schema text; v_parent_tablename text; v_partition_time timestamp; v_partition_time_array timestamp[]; v_partition_id_array bigint[]; v_row record; v_run_maint boolean; v_sql text; v_start_time timestamp; v_starting_partition_id bigint; v_step_id bigint; v_step_overflow_id bigint; v_sub_parent text; v_success boolean := false; v_time_interval interval; v_time_position int; 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); BEGIN IF position('.' in p_parent_table) = 0 THEN RAISE EXCEPTION 'Parent table must be schema qualified'; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1) AND tablename = split_part(p_parent_table, '.', 2); 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 AND n.nspname = v_parent_schema AND a.attname = p_control; IF 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; IF p_type = 'id' AND p_epoch = true THEN RAISE EXCEPTION 'p_epoch can only be used with time-based partitioning'; END IF; IF NOT @extschema@.check_partition_type(p_type) THEN RAISE EXCEPTION '% is not a valid partitioning type', p_type; END IF; EXECUTE format('LOCK TABLE %I.%I IN ACCESS EXCLUSIVE MODE', v_parent_schema, v_parent_tablename); IF p_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF p_use_run_maintenance IS NOT NULL THEN IF p_use_run_maintenance IS FALSE AND (p_type = 'time' OR p_type = 'time-custom') THEN RAISE EXCEPTION 'p_run_maintenance cannot be set to false for time based partitioning'; END IF; v_run_maint := p_use_run_maintenance; ELSIF p_type = 'time' OR p_type = 'time-custom' THEN v_run_maint := TRUE; ELSIF p_type = 'id' THEN v_run_maint := FALSE; ELSE RAISE EXCEPTION 'use_run_maintenance value cannot be set NULL'; END IF; 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 AND n.nspname = v_parent_schema ), sibling_children AS ( SELECT i.inhrelid::regclass::text AS tablename FROM pg_inherits i JOIN parent_table p ON i.inhparent = p.parent_oid ) 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_use_run_maintenance , sub_epoch , sub_optimize_trigger , sub_optimize_constraint , sub_infinite_time_partitions , sub_jobmon 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_use_run_maintenance , sub_epoch , sub_optimize_trigger , sub_optimize_constraint , sub_infinite_time_partitions , sub_jobmon) 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_use_run_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); END LOOP; IF p_type = 'time' OR p_type = 'time-custom' 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 <> 'time-custom' THEN RAISE EXCEPTION 'Must use a predefined time interval if not using type "time-custom". See documentation.'; 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::timestamp, 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))::timestamp; 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 , use_run_maintenance , inherit_fk , jobmon) VALUES ( p_parent_table , p_type , v_time_interval , p_epoch , p_control , p_premake , p_constraint_cols , v_datetime_string , v_run_maint , p_inherit_fk , p_jobmon); 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 AND n.nspname = v_parent_schema ) SELECT n.nspname, c.relname, p.datetime_string INTO v_top_parent_schema, v_top_parent_table, v_top_datetime_string 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 v_time_position := (length(p_parent_table) - position('p_' in reverse(p_parent_table))) + 2; v_parent_partition_timestamp := to_timestamp(substring(p_parent_table from v_time_position), v_top_datetime_string); 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 -- Currently unknown edge case if code gets here RAISE EXCEPTION 'No child tables created. Unexpected edge case encountered. Please report this error to author with conditions that led to it.'; END IF; END 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 p_type = 'id' THEN v_id_interval := p_interval::bigint; IF v_id_interval < 10 THEN RAISE EXCEPTION 'Interval for serial 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 AND c.relname = v_higher_parent_table ) SELECT n.nspname, c.relname INTO v_higher_parent_schema, v_higher_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 WHERE p.partition_type = 'id'; IF v_higher_parent_table IS NOT NULL THEN -- 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 v_sql := format('SELECT COALESCE(%L, max(%I)::bigint, 0) FROM %I.%I LIMIT 1' , p_start_partition::bigint , p_control , v_top_parent_schema , v_top_parent_table); EXECUTE v_sql INTO v_max; 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 , use_run_maintenance , inherit_fk , jobmon) VALUES ( p_parent_table , p_type , v_id_interval , p_control , p_premake , p_constraint_cols , v_run_maint , p_inherit_fk , p_jobmon); 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 AND n.nspname = v_parent_schema ) SELECT 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 WHERE p.partition_type = 'id'; IF v_top_parent_table IS NOT NULL THEN -- Create the lowest possible partition that is within the boundary of the parent v_id_position := (length(p_parent_table) - position('p_' in reverse(p_parent_table))) + 2; v_parent_partition_id = substring(p_parent_table from v_id_position)::bigint; 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 EXCEPTION 'No child tables created. Unexpected edge case encountered. Please report this error to author with conditions that led to it.'; END IF; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating partition function'); END IF; IF p_type = 'time' OR p_type = 'time-custom' 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 p_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); 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; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; 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 $$; /* * Create the trigger function for the parent table of an id-based partition set */ CREATE OR REPLACE FUNCTION create_function_id(p_parent_table text, p_job_id bigint DEFAULT NULL) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_control text; v_count int; v_current_partition_name text; v_current_partition_id bigint; v_datetime_string text; v_final_partition_id bigint; v_function_name 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_position int; v_job_id bigint; v_jobmon text; v_jobmon_schema text; v_last_partition text; v_max bigint; v_next_partition_id bigint; v_next_partition_name text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_premake int; v_prev_partition_id bigint; v_prev_partition_name text; v_row_max_id record; v_run_maint boolean; v_step_id bigint; v_top_parent text := p_parent_table; v_trig_func text; v_optimize_trigger int; BEGIN SELECT partition_interval::bigint , control , premake , optimize_trigger , use_run_maintenance , jobmon INTO v_partition_interval , v_control , v_premake , v_optimize_trigger , v_run_maint , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE FUNCTION: %s', p_parent_table)); ELSE v_job_id = p_job_id; END IF; v_step_id := add_step(v_job_id, format('Creating partition function for table %s', p_parent_table)); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1) AND tablename = split_part(p_parent_table, '.', 2); v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); -- Get the highest level top parent if multi-level partitioned in order to get proper max() value below 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 AND c.relname = v_higher_parent_table ) SELECT n.nspname, c.relname INTO v_higher_parent_schema, v_higher_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 WHERE p.partition_type = 'id'; IF v_higher_parent_table IS NOT NULL THEN -- initially set in DECLARE v_top_parent := v_higher_parent_schema||'.'||v_higher_parent_table; END IF; END LOOP; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_top_parent, 'DESC') LOOP EXECUTE format('SELECT max(%I) FROM %I.%I', v_control, v_row_max_id.partition_schemaname, v_row_max_id.partition_tablename) INTO v_max; IF v_max IS NOT NULL THEN EXIT; END IF; END LOOP; IF v_max IS NULL THEN v_max := 0; END IF; v_current_partition_id = v_max - (v_max % v_partition_interval); v_next_partition_id := v_current_partition_id + v_partition_interval; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_current_partition_id::text, TRUE); v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_current_partition_id bigint; v_current_partition_name text; v_id_position int; v_last_partition text := %L; v_next_partition_id bigint; v_next_partition_name text; v_partition_created boolean; BEGIN IF TG_OP = ''INSERT'' THEN IF NEW.%I >= %s AND NEW.%I < %s THEN ' , v_parent_schema , v_function_name , v_last_partition , v_control , v_current_partition_id , v_control , v_next_partition_id ); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema AND tablename = v_current_partition_name; IF v_count > 0 THEN v_trig_func := v_trig_func || format(' INSERT INTO %I.%I VALUES (NEW.*); ', v_parent_schema, v_current_partition_name); ELSE v_trig_func := v_trig_func || ' -- Child table for current values does not exist in this partition set, so write to parent RETURN NEW;'; END IF; FOR i IN 1..v_optimize_trigger LOOP v_prev_partition_id := v_current_partition_id - (v_partition_interval * i); v_next_partition_id := v_current_partition_id + (v_partition_interval * i); v_final_partition_id := v_next_partition_id + v_partition_interval; v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, v_prev_partition_id::text, TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, v_next_partition_id::text, TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles optimize_trigger being larger than premake (to go back in time further) and edge case of changing optimize_trigger immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema AND tablename = v_prev_partition_name; IF v_count > 0 THEN -- Only handle previous partitions if they're starting above zero IF v_prev_partition_id >= 0 THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %s AND NEW.%I < %s THEN INSERT INTO %I.%I VALUES (NEW.*); ' , v_control , v_prev_partition_id , v_control , v_prev_partition_id + v_partition_interval , v_parent_schema , v_prev_partition_name ); END IF; END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema AND tablename = v_next_partition_name; IF v_count > 0 THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %s AND NEW.%I < %s THEN INSERT INTO %I.%I VALUES (NEW.*);' , v_control , v_next_partition_id , v_control , v_final_partition_id , v_parent_schema , v_next_partition_name ); END IF; END LOOP; v_trig_func := v_trig_func ||format(' ELSE v_current_partition_id := NEW.%I - (NEW.%I %% %s); v_current_partition_name := @extschema@.check_name_length(%L, v_current_partition_id::text, TRUE); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = %L AND tablename = v_current_partition_name; IF v_count > 0 THEN EXECUTE format(''INSERT INTO %%I.%%I VALUES($1.*)'', %L, v_current_partition_name) USING NEW; ELSE RETURN NEW; END IF; END IF;' , v_control , v_control , v_partition_interval , v_parent_tablename , v_parent_schema , v_parent_schema ); IF v_run_maint IS FALSE THEN v_trig_func := v_trig_func ||format(' v_current_partition_id := NEW.%I - (NEW.%I %% %s); IF (NEW.%I %% %s) > (%s / 2) THEN v_id_position := (length(v_last_partition) - position(''p_'' in reverse(v_last_partition))) + 2; v_next_partition_id := (substring(v_last_partition from v_id_position)::bigint) + %s; WHILE ((v_next_partition_id - v_current_partition_id) / %s) <= %s LOOP v_partition_created := @extschema@.create_partition_id(%L, ARRAY[v_next_partition_id]); IF v_partition_created THEN PERFORM @extschema@.create_function_id(%L); PERFORM @extschema@.apply_constraints(%L); END IF; v_next_partition_id := v_next_partition_id + %s; END LOOP; END IF;' , v_control , v_control , v_partition_interval , v_control , v_partition_interval , v_partition_interval , v_partition_interval , v_partition_interval , v_premake , p_parent_table , p_parent_table , p_parent_table , v_partition_interval ); END IF; v_trig_func := v_trig_func ||' END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for current id interval: %s to %s', v_current_partition_id, v_final_partition_id-1)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; 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 FUNCTION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''Partition function maintenance for table %s 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 $$; /* * Create the trigger function for the parent table of a time-based partition set */ CREATE OR REPLACE FUNCTION create_function_time(p_parent_table text, p_job_id bigint DEFAULT NULL) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_control text; v_count int; v_current_partition_name text; v_current_partition_timestamp timestamptz; v_datetime_string text; v_epoch boolean; v_final_partition_timestamp timestamptz; v_function_name text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_new_length int; v_next_partition_name text; v_next_partition_timestamp timestamptz; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_prev_partition_name text; v_prev_partition_timestamp timestamptz; v_step_id bigint; v_trig_func text; v_optimize_trigger int; v_type text; BEGIN SELECT partition_type , partition_interval::interval , epoch , control , optimize_trigger , datetime_string , jobmon INTO v_type , v_partition_interval , v_epoch , v_control , v_optimize_trigger , v_datetime_string , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE FUNCTION: %s', p_parent_table)); ELSE v_job_id = p_job_id; END IF; v_step_id := add_step(v_job_id, format('Creating partition function for table %s', p_parent_table)); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1) AND tablename = split_part(p_parent_table, '.', 2); v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); IF v_type = 'time' THEN v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_partition_name text; v_partition_timestamp timestamptz; BEGIN IF TG_OP = ''INSERT'' THEN ' , v_parent_schema , v_function_name); IF v_epoch = false THEN CASE WHEN v_partition_interval = '15 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', NEW.%I) + ''15min''::interval * floor(date_part(''minute'', NEW.%I) / 15.0);' , v_control , v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', NEW.%I) + ''30min''::interval * floor(date_part(''minute'', NEW.%I) / 30.0);' , v_control , v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 day' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''day'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 week' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''week'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 month' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''month'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_partition_interval = '3 months' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''quarter'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 year' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''year'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; ELSE -- epoch is true CASE WHEN v_partition_interval = '15 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', to_timestamp(NEW.%I)) + ''15min''::interval * floor(date_part(''minute'', NEW.%I) / 15.0);' , v_control , v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', to_timestamp(NEW.%I)) + ''30min''::interval * floor(date_part(''minute'', NEW.%I) / 30.0);' , v_control , v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', to_timestamp(NEW.%I));', v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 day' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''day'', to_timestamp(NEW.%I));', v_control); v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 week' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''week'', to_timestamp(NEW.%I));', v_control); v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 month' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''month'', to_timestamp(NEW.%I));', v_control); v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_partition_interval = '3 months' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''quarter'', to_timestamp(NEW.%I));', v_control); v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 year' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''year'', to_timestamp(NEW.%I));', v_control); v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; END IF; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, to_char(v_current_partition_timestamp, v_datetime_string), TRUE); v_next_partition_timestamp := v_current_partition_timestamp + v_partition_interval::interval; IF v_epoch = false THEN v_trig_func := v_trig_func ||format(' IF NEW.%I >= %L AND NEW.%I < %L THEN ' , v_control , v_current_partition_timestamp , v_control , v_next_partition_timestamp); ELSE v_trig_func := v_trig_func ||format(' IF to_timestamp(NEW.%I) >= %L AND to_timestamp(NEW.%I) < %L THEN ' , v_control , v_current_partition_timestamp , v_control , v_next_partition_timestamp); END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema AND tablename = v_current_partition_name; IF v_count > 0 THEN v_trig_func := v_trig_func || format(' INSERT INTO %I.%I VALUES (NEW.*); ', v_parent_schema, v_current_partition_name); ELSE v_trig_func := v_trig_func || ' -- Child table for current values does not exist in this partition set, so write to parent RETURN NEW;'; END IF; FOR i IN 1..v_optimize_trigger LOOP v_prev_partition_timestamp := v_current_partition_timestamp - (v_partition_interval::interval * i); v_next_partition_timestamp := v_current_partition_timestamp + (v_partition_interval::interval * i); v_final_partition_timestamp := v_next_partition_timestamp + (v_partition_interval::interval); v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, to_char(v_prev_partition_timestamp, v_datetime_string), TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, to_char(v_next_partition_timestamp, v_datetime_string), TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles optimize_trigger being larger than premake (to go back in time further) and edge case of changing optimize_trigger immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema AND tablename = v_prev_partition_name; IF v_count > 0 THEN IF v_epoch = false THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %L AND NEW.%I < %L THEN INSERT INTO %I.%I VALUES (NEW.*);' , v_control , v_prev_partition_timestamp , v_control , v_prev_partition_timestamp + v_partition_interval::interval , v_parent_schema , v_prev_partition_name); ELSE v_trig_func := v_trig_func ||format(' ELSIF to_timestamp(NEW.%I) >= %L AND to_timestamp(NEW.%I) < %L THEN INSERT INTO %I.%I VALUES (NEW.*);' , v_control , v_prev_partition_timestamp , v_control , v_prev_partition_timestamp + v_partition_interval::interval , v_parent_schema , v_prev_partition_name); END IF; END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema AND tablename = v_next_partition_name; IF v_count > 0 THEN IF v_epoch = false THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %L AND NEW.%I < %L THEN INSERT INTO %I.%I VALUES (NEW.*);' , v_control , v_next_partition_timestamp , v_control , v_final_partition_timestamp , v_parent_schema , v_next_partition_name); ELSE v_trig_func := v_trig_func ||format(' ELSIF to_timestamp(NEW.%I) >= %L AND to_timestamp(NEW.%I) < %L THEN INSERT INTO %I.%I VALUES (NEW.*);' , v_control , v_next_partition_timestamp , v_control , v_final_partition_timestamp , v_parent_schema , v_next_partition_name); END IF; END IF; END LOOP; v_trig_func := v_trig_func||format(' ELSE v_partition_name := @extschema@.check_name_length(%L, to_char(v_partition_timestamp, %L), TRUE); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = %L AND tablename = v_partition_name; IF v_count > 0 THEN EXECUTE format(''INSERT INTO %%I.%%I VALUES($1.*)'', %L, v_partition_name) USING NEW; ELSE RETURN NEW; END IF; END IF;' , v_parent_tablename , v_datetime_string , v_parent_schema , v_parent_schema); v_trig_func := v_trig_func ||' END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for current time interval: %s to %s' , v_current_partition_timestamp , v_final_partition_timestamp-'1sec'::interval)); END IF; ELSIF v_type = 'time-custom' THEN v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_child_schemaname text; v_child_table text; v_child_tablename text; BEGIN ' , v_parent_schema , v_function_name); IF v_epoch = false THEN v_trig_func := v_trig_func || format(' SELECT child_table INTO v_child_table FROM @extschema@.custom_time_partitions WHERE partition_range @> NEW.%I AND parent_table = %L;' , v_control , v_parent_schema||'.'||v_parent_tablename); ELSE -- epoch true v_trig_func := v_trig_func || format(' SELECT child_table INTO v_child_table FROM @extschema@.custom_time_partitions WHERE partition_range @> to_timestamp(NEW.%I) AND parent_table = %L;' , v_control , v_parent_schema||'.'||v_parent_tablename); END IF; v_trig_func := v_trig_func || ' SELECT schemaname, tablename INTO v_child_schemaname, v_child_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(v_child_table, ''.'', 1) AND tablename = split_part(v_child_table, ''.'', 2); IF v_child_schemaname IS NOT NULL AND v_child_tablename IS NOT NULL THEN EXECUTE format(''INSERT INTO %I.%I VALUES ($1.*)'', v_child_schemaname, v_child_tablename) USING NEW; ELSE RETURN NEW; END IF; RETURN NULL; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for custom time table: %s', p_parent_table)); END IF; ELSE RAISE EXCEPTION 'ERROR: Invalid time partitioning type given: %', v_type; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; 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 FUNCTION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''Partition function maintenance for table %s 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 $$; CREATE OR REPLACE FUNCTION create_trigger(p_parent_table text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_function_name text; v_new_length int; v_parent_schema text; v_parent_tablename text; v_trig_name text; v_trig_sql text; BEGIN SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1) AND tablename = split_part(p_parent_table, '.', 2); v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); -- Ensure function name matches the naming pattern v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); v_trig_sql := format('CREATE TRIGGER %I BEFORE INSERT ON %I.%I FOR EACH ROW EXECUTE PROCEDURE %I.%I()' , v_trig_name , v_parent_schema , v_parent_tablename , v_parent_schema , v_function_name); EXECUTE v_trig_sql; END $$; /* * Drop constraints managed by pg_partman */ CREATE OR REPLACE FUNCTION drop_constraints(p_parent_table text, p_child_table text, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_child_schemaname text; v_child_tablename text; v_col text; v_constraint_cols text[]; v_existing_constraint_name text; v_exists boolean := FALSE; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_sql text; v_step_id bigint; BEGIN SELECT constraint_cols , jobmon INTO v_constraint_cols , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_constraint_cols IS NULL THEN RAISE EXCEPTION 'Given parent table (%) not set up for constraint management (constraint_cols is NULL)', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; SELECT schemaname, tablename INTO v_child_schemaname, v_child_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_child_table, '.', 1) AND tablename = split_part(p_child_table, '.', 2); IF v_child_tablename IS NULL THEN RAISE EXCEPTION 'Unable to find given child table in system catalogs: %', p_child_table; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN DROP CONSTRAINT: %s', p_parent_table)); v_step_id := add_step(v_job_id, 'Entering constraint drop loop'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; FOREACH v_col IN ARRAY v_constraint_cols LOOP SELECT con.conname INTO v_existing_constraint_name FROM pg_catalog.pg_constraint con JOIN pg_class c ON c.oid = con.conrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN pg_catalog.pg_attribute a ON con.conrelid = a.attrelid WHERE c.relname = v_child_tablename AND n.nspname = v_child_schemaname AND con.conname LIKE 'partmanconstr_%' AND con.contype = 'c' AND a.attname = v_col AND ARRAY[a.attnum] OPERATOR(pg_catalog.<@) con.conkey AND a.attisdropped = false; IF v_existing_constraint_name IS NOT NULL THEN v_exists := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Dropping constraint on column: %s', v_col)); END IF; v_sql := format('ALTER TABLE %I.%I DROP CONSTRAINT %I', v_child_schemaname, v_child_tablename, v_existing_constraint_name); IF p_debug THEN RAISE NOTICE 'Constraint drop query: %', v_sql; END IF; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Drop constraint query: %s', v_sql)); END IF; END IF; END LOOP; IF v_jobmon_schema IS NOT NULL AND v_exists IS FALSE THEN v_step_id := add_step(v_job_id, format('No constraints found to drop on child table: %s', p_child_table)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; 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 DROP CONSTRAINT: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to drop child tables from an id-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ CREATE OR REPLACE FUNCTION drop_partition_id(p_parent_table text, p_retention bigint DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_control text; v_drop_count int := 0; v_id_position int; v_index record; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_max bigint; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_partition_id bigint; v_retention bigint; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_row record; v_row_max_id record; v_step_id bigint; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman drop_partition_id')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_id already running.'; RETURN 0; END IF; -- Allow override of configuration options IF p_retention IS NULL THEN SELECT partition_interval::bigint , control , retention::bigint , retention_keep_table , retention_keep_index , retention_schema , jobmon INTO v_partition_interval , v_control , v_retention , v_retention_keep_table , v_retention_keep_index , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id' AND retention IS NOT NULL; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE SELECT partition_interval::bigint , control , retention_keep_table , retention_keep_index , retention_schema , jobmon INTO v_partition_interval , v_control , v_retention_keep_table , v_retention_keep_index , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; v_retention := p_retention; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1) AND tablename = split_part(p_parent_table, '.', 2); -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%I) FROM %I.%I', v_control, v_row_max_id.partition_schemaname, v_row_max_id.partition_tablename) INTO v_max; IF v_max IS NOT NULL THEN EXIT; END IF; END LOOP; -- Loop through child tables of the given parent FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'ASC') LOOP v_id_position := (length(v_row.partition_tablename) - position('p_' in reverse(v_row.partition_tablename))) + 2; v_partition_id := substring(v_row.partition_tablename from v_id_position)::bigint; -- Add one interval since partition names contain the start of the constraint period IF v_retention <= (v_max - (v_partition_id + v_partition_interval)) THEN -- Only create a jobmon entry if there's actual retention work done IF v_jobmon_schema IS NOT NULL AND v_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN DROP ID PARTITION: %s', p_parent_table)); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Uninherit table %s.%s from %s', v_row.partition_schemaname, v_row.partition_tablename, p_parent_table)); END IF; EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_row.partition_schemaname , v_row.partition_tablename , v_parent_schema , v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop table %s.%s', v_row.partition_schemaname, v_row.partition_tablename)); END IF; EXECUTE format('DROP TABLE %I.%I CASCADE', v_row.partition_schemaname, v_row.partition_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN FOR v_index IN WITH child_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE c1.relname = v_row.partition_tablename AND n1.nspname = v_row.partition_schema ) SELECT c.relname as name , con.conname FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid LEFT JOIN pg_catalog.pg_constraint con ON i.indexrelid = con.conindid JOIN child_info ON i.indrelid = child_info.oid LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop index %s from %s.%s' , v_index.name , v_row.partition_schemaname , v_row.partition_tablename)); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I DROP CONSTRAINT %I', v_row.partition_schemaname, v_row.partition_tablename, v_index.conname); ELSE EXECUTE format('DROP INDEX %I.%I', v_row.partition_schemaname, v_index.name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Moving table %s.%s to schema %s' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema)); END IF; EXECUTE format('ALTER TABLE %I.%I SET SCHEMA %I' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if -- If child table is a subpartition, remove it from part_config & part_config_sub (should cascade due to FK) DELETE FROM @extschema@.part_config WHERE parent_table = v_row.partition_schemaname ||'.'||v_row.partition_tablename; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', format('%s partitions dropped.', v_drop_count)); PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RETURN v_drop_count; 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 DROP ID PARTITION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to drop child tables from a time-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ CREATE OR REPLACE FUNCTION drop_partition_time(p_parent_table text, p_retention interval DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_datetime_string text; v_drop_count int := 0; v_index record; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_partition_timestamp timestamp; v_quarter text; v_retention interval; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_row record; v_step_id bigint; v_time_position int; v_type text; v_year text; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman drop_partition_time')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_time already running.'; RETURN 0; END IF; -- Allow override of configuration options IF p_retention IS NULL THEN SELECT partition_type , partition_interval::interval , retention::interval , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_type , v_partition_interval , v_retention , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom') AND retention IS NOT NULL; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE SELECT partition_type , partition_interval::interval , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_type , v_partition_interval , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); v_retention := p_retention; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1) AND tablename = split_part(p_parent_table, '.', 2); -- Loop through child tables of the given parent FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'DESC') LOOP -- pull out datetime portion of partition's tablename to make the next one v_time_position := (length(v_row.partition_tablename) - position('p_' in reverse(v_row.partition_tablename))) + 2; IF v_partition_interval <> '3 months' OR (v_partition_interval = '3 months' AND v_type = 'time-custom') THEN v_partition_timestamp := to_timestamp(substring(v_row.partition_tablename from v_time_position), v_datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(v_row.partition_tablename from v_time_position), 'q', 1); v_quarter := split_part(substring(v_row.partition_tablename from v_time_position), 'q', 2); CASE WHEN v_quarter = '1' THEN v_partition_timestamp := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_partition_timestamp := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_partition_timestamp := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_partition_timestamp := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; -- Add one interval since partition names contain the start of the constraint period IF v_retention < (CURRENT_TIMESTAMP - (v_partition_timestamp + v_partition_interval)) THEN -- Only create a jobmon entry if there's actual retention work done IF v_jobmon_schema IS NOT NULL AND v_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN DROP TIME PARTITION: %s', p_parent_table)); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Uninherit table %s.%s from %s' , v_row.partition_schemaname , v_row.partition_tablename , p_parent_table)); END IF; EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_row.partition_schemaname , v_row.partition_tablename , v_parent_schema , v_parent_tablename); IF v_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_row.partition_schemaname||'.'||v_row.partition_tablename; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop table %s.%s', v_row.partition_schemaname, v_row.partition_tablename)); END IF; EXECUTE format('DROP TABLE %I.%I CASCADE', v_row.partition_schemaname, v_row.partition_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN FOR v_index IN WITH child_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE c1.relname = v_row.partition_tablename AND n1.nspname = v_row.partition_schemaname ) SELECT c.relname as name , con.conname FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid LEFT JOIN pg_catalog.pg_constraint con ON i.indexrelid = con.conindid JOIN child_info ON i.indrelid = child_info.oid LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop index %s from %s.%s' , v_index.name , v_row.partition_schemaname , v_row.partition_tablename)); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I DROP CONSTRAINT %I' , v_row.partition_schemaname , v_row.partition_tablename , v_index.conname); ELSE EXECUTE format('DROP INDEX %I.%I', v_parent_schema, v_index.name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Moving table %s.%s to schema %s' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema)); END IF; EXECUTE format('ALTER TABLE %I.%I SET SCHEMA %I', v_row.partition_schemaname, v_row.partition_tablename, v_retention_schema); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if -- If child table is a subpartition, remove it from part_config & part_config_sub (should cascade due to FK) DELETE FROM @extschema@.part_config WHERE parent_table = v_row.partition_schemaname||'.'||v_row.partition_tablename; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', format('%s partitions dropped.', v_drop_count)); PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RETURN v_drop_count; 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 DROP TIME PARTITION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Populate the child table(s) of an id-based partition set with old data from the original parent */ CREATE OR REPLACE FUNCTION partition_data_id(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval int DEFAULT NULL, p_lock_wait numeric DEFAULT 0, p_order text DEFAULT 'ASC') RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_current_partition_name text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_id bigint; v_min_partition_id bigint; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_partition_id bigint[]; v_rowcount bigint; v_sql text; v_start_control bigint; v_total_rows bigint := 0; BEGIN SELECT partition_interval::bigint , control INTO v_partition_interval , v_control FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1) AND tablename = split_part(p_parent_table, '.', 2); IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; FOR i IN 1..p_batch_count LOOP IF p_order = 'ASC' THEN EXECUTE format('SELECT min(%I) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; IF v_start_control IS NULL THEN EXIT; END IF; v_min_partition_id = v_start_control - (v_start_control % v_partition_interval); v_partition_id := ARRAY[v_min_partition_id]; -- Check if custom batch interval overflows current partition maximum IF (v_start_control + p_batch_interval) >= (v_min_partition_id + v_partition_interval) THEN v_max_partition_id := v_min_partition_id + v_partition_interval; ELSE v_max_partition_id := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN EXECUTE 'SELECT max('||v_control||') FROM ONLY '||p_parent_table INTO v_start_control; IF v_start_control IS NULL THEN EXIT; END IF; v_min_partition_id = v_start_control - (v_start_control % v_partition_interval); -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_id := v_min_partition_id + v_partition_interval; v_partition_id := ARRAY[v_min_partition_id]; -- Make sure minimum doesn't underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_id THEN v_min_partition_id = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN v_sql := format('SELECT * FROM ONLY %I.%I WHERE %I >= %s AND %I < %s FOR UPDATE NOWAIT' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_id , v_control , v_max_partition_id); EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; PERFORM @extschema@.create_partition_id(p_parent_table, v_partition_id); v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_min_partition_id::text, TRUE); EXECUTE format('WITH partition_data AS ( DELETE FROM ONLY %I.%I WHERE %I >= %s AND %I < %s RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_id , v_control , v_max_partition_id , v_parent_schema , v_current_partition_name); GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; PERFORM @extschema@.create_function_id(p_parent_table); RETURN v_total_rows; END $$; /* * Populate the child table(s) of a time-based partition set with old data from the original parent */ CREATE OR REPLACE FUNCTION partition_data_time(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval interval DEFAULT NULL, p_lock_wait numeric DEFAULT 0, p_order text DEFAULT 'ASC') RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_datetime_string text; v_current_partition_name text; v_epoch boolean; v_last_partition text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_timestamp timestamp; v_min_partition_timestamp timestamp; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_partition_suffix text; v_partition_timestamp timestamp[]; v_quarter text; v_rowcount bigint; v_sql text; v_start_control timestamp; v_time_position int; v_total_rows bigint := 0; v_type text; v_year text; BEGIN SELECT partition_type , partition_interval::interval , control , datetime_string , epoch INTO v_type , v_partition_interval , v_control , v_datetime_string , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1) AND tablename = split_part(p_parent_table, '.', 2); SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; FOR i IN 1..p_batch_count LOOP IF v_epoch = false THEN IF p_order = 'ASC' THEN EXECUTE format('SELECT min(%I) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSIF p_order = 'DESC' THEN EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; ELSE IF p_order = 'ASC' THEN EXECUTE format('SELECT to_timestamp(min(%I)) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSIF p_order = 'DESC' THEN EXECUTE format('SELECT to_timestamp(max(%I)) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; END IF; IF v_start_control IS NULL THEN EXIT; END IF; IF v_type = 'time' THEN CASE WHEN v_partition_interval = '15 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '15min'::interval * floor(date_part('minute', v_start_control) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '30min'::interval * floor(date_part('minute', v_start_control) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control); WHEN v_partition_interval = '1 day' THEN v_min_partition_timestamp := date_trunc('day', v_start_control); WHEN v_partition_interval = '1 week' THEN v_min_partition_timestamp := date_trunc('week', v_start_control); WHEN v_partition_interval = '1 month' THEN v_min_partition_timestamp := date_trunc('month', v_start_control); WHEN v_partition_interval = '3 months' THEN v_min_partition_timestamp := date_trunc('quarter', v_start_control); WHEN v_partition_interval = '1 year' THEN v_min_partition_timestamp := date_trunc('year', v_start_control); END CASE; ELSIF v_type = 'time-custom' THEN v_time_position := (length(v_last_partition) - position('p_' in reverse(v_last_partition))) + 2; v_min_partition_timestamp := to_timestamp(substring(v_last_partition from v_time_position), v_datetime_string); v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; LOOP IF v_start_control >= v_min_partition_timestamp AND v_start_control < v_max_partition_timestamp THEN EXIT; ELSE BEGIN IF v_start_control > v_max_partition_timestamp THEN -- Keep going forward in time, checking if child partition time interval encompasses the current v_start_control value v_min_partition_timestamp := v_max_partition_timestamp; v_max_partition_timestamp := v_max_partition_timestamp + v_partition_interval; ELSE -- Keep going backwards in time, checking if child partition time interval encompasses the current v_start_control value v_max_partition_timestamp := v_min_partition_timestamp; v_min_partition_timestamp := v_min_partition_timestamp - v_partition_interval; END IF; EXCEPTION WHEN datetime_field_overflow THEN RAISE EXCEPTION 'Attempted partition time interval is outside PostgreSQL''s supported time range. Unable to create partition with interval before timestamp % ', v_min_partition_interval; END; END IF; END LOOP; END IF; v_partition_timestamp := ARRAY[v_min_partition_timestamp]; IF p_order = 'ASC' THEN -- Ensure batch interval given as parameter doesn't cause maximum to overflow the current partition maximum IF (v_start_control + p_batch_interval) >= (v_min_partition_timestamp + v_partition_interval) THEN v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; ELSE v_max_partition_timestamp := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; -- Ensure batch interval given as parameter doesn't cause minimum to underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_timestamp THEN v_min_partition_timestamp = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN IF v_epoch = false THEN v_sql := format('SELECT * FROM ONLY %I.%I WHERE %I >= %L AND %I < %L FOR UPDATE NOWAIT' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_timestamp , v_control , v_max_partition_timestamp); ELSE v_sql := format('SELECT * FROM ONLY %I.%I WHERE to_timestamp(%I) >= %L AND to_timestamp(%I) < %L FOR UPDATE NOWAIT' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_timestamp , v_control , v_max_partition_timestamp); END IF; EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; PERFORM @extschema@.create_partition_time(p_parent_table, v_partition_timestamp); -- This suffix generation code is in create_partition_time() as well v_partition_suffix := to_char(v_min_partition_timestamp, v_datetime_string); v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); IF v_epoch = false THEN v_sql := format('WITH partition_data AS ( DELETE FROM ONLY %I.%I WHERE %I >= %L AND %I < %L RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_timestamp , v_control , v_max_partition_timestamp , v_parent_schema , v_current_partition_name); ELSE v_sql := format('WITH partition_data AS ( DELETE FROM ONLY %I.%I WHERE to_timestamp(%I) >= %L AND to_timestamp(%I) < %L RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_timestamp , v_control , v_max_partition_timestamp , v_parent_schema , v_current_partition_name); END IF; EXECUTE v_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; PERFORM @extschema@.create_function_time(p_parent_table); RETURN v_total_rows; END $$; /* * Function to re-apply ownership & privileges on all child tables in a partition set using parent table as reference */ CREATE OR REPLACE FUNCTION reapply_privileges(p_parent_table text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_parent_schema text; v_parent_tablename text; v_row record; v_step_id bigint; BEGIN SELECT jobmon INTO v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon IS NULL THEN RAISE EXCEPTION 'Given table is not managed by this extention: %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1) AND tablename = split_part(p_parent_table, '.', 2); IF v_parent_tablename IS NULL THEN RAISE EXCEPTION 'Given parent table does not exist: %', p_parent_table; END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.add_job(%L)', v_jobmon_schema, format('PARTMAN RE-APPLYING PRIVILEGES TO ALL CHILD TABLES OF: %s', p_parent_table)) INTO v_job_id; END IF; FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'ASC') LOOP PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_row.partition_schemaname, v_row.partition_tablename, v_job_id); END LOOP; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.close_job(%L)', v_jobmon_schema, v_job_id); END IF; 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 RE-APPLYING PRIVILEGES TO ALL CHILD TABLES OF: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Given a parent table and partition value, return the name of the child partition it would go in. * If using epoch time partitioning, give the text representation of the timestamp NOT the epoch integer value (use to_timestamp() to convert epoch values). * Also returns just the suffix value and true if the child table exists or false if it does not */ CREATE OR REPLACE FUNCTION show_partition_name(p_parent_table text, p_value text, OUT partition_table text, OUT suffix_timestamp timestamp, OUT suffix_id bigint, OUT table_exists boolean) RETURNS record LANGUAGE plpgsql STABLE AS $$ DECLARE v_child_exists text; v_datetime_string text; v_max_range timestamptz; v_min_range timestamptz; v_parent_schema text; v_parent_tablename text; v_partition_interval text; v_time_position int; v_type text; BEGIN SELECT partition_type , partition_interval , datetime_string INTO v_type , v_partition_interval , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_type IS NULL THEN RAISE EXCEPTION 'Parent table given is not managed by pg_partman (%)', p_parent_table; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1) AND tablename = split_part(p_parent_table, '.', 2); IF v_parent_tablename IS NULL THEN RAISE EXCEPTION 'Parent table given does not exist (%)', p_parent_table; END IF; IF v_type = 'time' THEN CASE WHEN v_partition_interval::interval = '15 mins' THEN suffix_timestamp := date_trunc('hour', p_value::timestamp) + '15min'::interval * floor(date_part('minute', p_value::timestamp) / 15.0); WHEN v_partition_interval::interval = '30 mins' THEN suffix_timestamp := date_trunc('hour', p_value::timestamp) + '30min'::interval * floor(date_part('minute', p_value::timestamp) / 30.0); WHEN v_partition_interval::interval = '1 hour' THEN suffix_timestamp := date_trunc('hour', p_value::timestamp); WHEN v_partition_interval::interval = '1 day' THEN suffix_timestamp := date_trunc('day', p_value::timestamp); WHEN v_partition_interval::interval = '1 week' THEN suffix_timestamp := date_trunc('week', p_value::timestamp); WHEN v_partition_interval::interval = '1 month' THEN suffix_timestamp := date_trunc('month', p_value::timestamp); WHEN v_partition_interval::interval = '3 months' THEN suffix_timestamp := date_trunc('quarter', p_value::timestamp); WHEN v_partition_interval::interval = '1 year' THEN suffix_timestamp := date_trunc('year', p_value::timestamp); END CASE; partition_table := v_parent_schema||'.'||@extschema@.check_name_length(v_parent_tablename, to_char(suffix_timestamp, v_datetime_string), TRUE); ELSIF v_type = 'id' THEN suffix_id := (p_value::bigint - (p_value::bigint % v_partition_interval::bigint)); partition_table := v_parent_schema||'.'||@extschema@.check_name_length(v_parent_tablename, suffix_id::text, TRUE); ELSIF v_type = 'time-custom' THEN SELECT child_table, lower(partition_range) INTO partition_table, suffix_timestamp FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND partition_range @> p_value::timestamptz; IF partition_table IS NULL THEN SELECT max(upper(partition_range)) INTO v_max_range FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table; SELECT min(lower(partition_range)) INTO v_min_range FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table; IF p_value::timestamp >= v_max_range THEN suffix_timestamp := v_max_range; LOOP -- Keep incrementing higher until given value is below the upper range suffix_timestamp := suffix_timestamp + v_partition_interval::interval; IF p_value::timestamp < suffix_timestamp THEN -- Have to subtract one interval because the value would actually be in the partition previous -- to this partition timestamp since the partition names contain the lower boundary suffix_timestamp := suffix_timestamp - v_partition_interval::interval; EXIT; END IF; END LOOP; ELSIF p_value::timestamp < v_min_range THEN suffix_timestamp := v_min_range; LOOP -- Keep decrementing lower until given value is below or equal to the lower range suffix_timestamp := suffix_timestamp - v_partition_interval::interval; IF p_value::timestamp >= suffix_timestamp THEN EXIT; END IF; END LOOP; ELSE RAISE EXCEPTION 'Unable to determine a valid child table for the given parent table and value'; END IF; partition_table := v_parent_schema||'.'||@extschema@.check_name_length(v_parent_tablename, to_char(suffix_timestamp, v_datetime_string), TRUE); END IF; END IF; SELECT tablename INTO v_child_exists FROM pg_catalog.pg_tables WHERE schemaname = split_part(partition_table, '.', 1) AND tablename = split_part(partition_table, '.', 2); IF v_child_exists IS NOT NULL THEN table_exists := true; ELSE table_exists := false; END IF; RETURN; END $$; /* * Function to list all child partitions in a set. */ CREATE OR REPLACE FUNCTION show_partitions (p_parent_table text, p_order text DEFAULT 'ASC') RETURNS TABLE (partition_schemaname text, partition_tablename text) LANGUAGE plpgsql STABLE SECURITY DEFINER AS $$ DECLARE v_datetime_string text; v_parent_schema text; v_parent_tablename text; v_partition_interval text; v_quarter text; v_type text; v_year text; BEGIN IF upper(p_order) NOT IN ('ASC', 'DESC') THEN RAISE EXCEPTION 'p_order paramter must be one of the following values: ASC, DESC'; END IF; SELECT partition_type , partition_interval , datetime_string INTO v_type , v_partition_interval , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1) AND tablename = split_part(p_parent_table, '.', 2); IF v_type IN ('time', 'time-custom') THEN IF v_partition_interval::interval <> '3 months' OR (v_partition_interval::interval = '3 months' AND v_type = 'time-custom') THEN RETURN QUERY EXECUTE format('SELECT n.nspname::text AS partition_schemaname, c.relname::text AS partition_name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = ''%I.%I''::regclass ORDER BY to_timestamp(substring(c.relname from ((length(c.relname) - position(''p_'' in reverse(c.relname))) + 2) ), %L) %s' , v_parent_schema , v_parent_tablename , v_datetime_string , p_order); ELSE -- For quarterly, to_timestamp() doesn't recognize "Q" in datetime string. -- First order by just the year, then order by the quarter number (should be last character in table name) RETURN QUERY EXECUTE format('SELECT n.nspname::text AS partition_schemaname, c.relname::text AS partition_name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = ''%I.%I''::regclass ORDER BY to_timestamp(substring(c.relname from ((length(c.relname) - position(''p_'' in reverse(c.relname))) + 2) for 4), ''YYYY'') %s , substring(reverse(c.relname) from 1 for 1) %s' , v_parent_schema , v_parent_tablename , p_order , p_order); END IF; ELSIF v_type = 'id' THEN RETURN QUERY EXECUTE format('SELECT n.nspname::text AS partition_schemaname, c.relname::text AS partition_name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = ''%I.%I''::regclass ORDER BY substring(c.relname from ((length(c.relname) - position(''p_'' in reverse(c.relname))) + 2) )::bigint %s' , v_parent_schema , v_parent_tablename , p_order); END IF; END $$; /* * Function to undo partitioning. * Will actually work on any parent/child table set, not just ones created by pg_partman. */ CREATE OR REPLACE FUNCTION undo_partition(p_parent_table text, p_batch_count int DEFAULT 1, p_keep_table boolean DEFAULT true, p_jobmon boolean DEFAULT true, p_lock_wait numeric DEFAULT 0) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_batch_loop_count bigint := 0; v_child_count bigint; v_child_table text; v_copy_sql text; v_function_name text; v_job_id bigint; v_jobmon_schema text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_rowcount bigint; v_step_id bigint; v_total bigint := 0; v_trig_name text; v_type text; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition already running.'; RETURN 0; END IF; IF p_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN UNDO PARTITIONING: %s', p_parent_table)); v_step_id := add_step(v_job_id, format('Undoing partitioning for table %s', p_parent_table)); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1) AND tablename = split_part(p_parent_table, '.', 2); -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables and stop new id partitions from being made. v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t JOIN pg_catalog.pg_class c ON t.tgrelid = c.oid WHERE tgname = v_trig_name AND c.relname = v_parent_tablename; SELECT proname INTO v_function_name FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid WHERE n.nspname = v_parent_schema AND proname = v_function_name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_parent_tablename); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; RETURN -1; END IF; END IF; -- END p_lock_wait IF EXECUTE format('DROP TRIGGER IF EXISTS %I ON %I.%I', v_trig_name, v_parent_schema, v_parent_tablename); END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later IF v_function_name IS NOT NULL THEN EXECUTE format('DROP FUNCTION IF EXISTS %I.%I()', v_parent_schema, v_function_name); END IF; IF v_jobmon_schema IS NOT NULL THEN IF (v_trig_name IS NOT NULL OR v_function_name IS NOT NULL) THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); ELSE PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process.'); END IF; END IF; WHILE v_batch_loop_count < p_batch_count LOOP -- Get ordered list of child table in set. Store in variable one at a time per loop until none are left. -- Not using show_partitions() so it can work on non-pg_partman partition sets WITH parent_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE c1.relname = v_parent_tablename AND n1.nspname = v_parent_schema ) SELECT c.relname INTO v_child_table FROM pg_catalog.pg_inherits i JOIN pg_catalog.pg_class c ON i.inhrelid = c.oid JOIN parent_info p ON i.inhparent = p.oid ORDER BY i.inhrelid ASC; EXIT WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Removing child partition: %s.%s', v_parent_schema, v_child_table)); END IF; -- lockwait timeout for table drop v_lock_obtained := FALSE; -- reset for reuse in loop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_child_table); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; RETURN -1; END IF; END IF; -- END p_lock_wait IF v_copy_sql := format('INSERT INTO %I.%I SELECT * FROM %I.%I' , v_parent_schema , v_parent_tablename , v_parent_schema , v_child_table); EXECUTE v_copy_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_parent_schema , v_child_table , v_parent_schema , v_parent_tablename); IF p_keep_table = false THEN EXECUTE format('DROP TABLE %I.%I', v_parent_schema, v_child_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table DROPPED. Moved %s rows to parent', v_rowcount)); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table UNINHERITED, not DROPPED. Copied %s rows to parent', v_rowcount)); END IF; END IF; SELECT partition_type INTO v_type FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_parent_schema||'.'||v_child_table; END IF; v_batch_loop_count := v_batch_loop_count + 1; v_undo_count := v_undo_count + 1; END LOOP; -- v_batch_loop_count IF v_undo_count = 0 THEN -- FOR loop never ran, so there's no child tables left. DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman (if it existed)'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) from % child table(s) to the parent: %', v_total, v_undo_count, p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', format('Copied %s row(s) from %s child table(s) to the parent', v_total, v_undo_count)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RETURN v_total; 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 UNDO PARTITIONING: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Check if parent table is a subpartition of an already existing partition set managed by pg_partman * If so, return the limits of what child tables can be created under the given parent table based on its own suffix */ CREATE OR REPLACE FUNCTION check_subpartition_limits(p_parent_table text, p_type text, OUT sub_min text, OUT sub_max text) RETURNS record LANGUAGE plpgsql AS $$ DECLARE v_datetime_string text; v_id_position int; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_quarter text; v_sub_id_max bigint; v_sub_id_min bigint; v_sub_timestamp_max timestamp; v_sub_timestamp_min timestamp; v_time_position int; v_top_datetime_string text; v_top_interval text; v_top_parent text; v_top_type text; v_year text; BEGIN SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1) AND tablename = split_part(p_parent_table, '.', 2); -- CTE query is done individually for each type (time, id) because it should return NULL if the top parent is not the same type in a subpartition set (id->time or time->id) IF p_type = 'id' THEN WITH top_oid AS ( SELECT i.inhparent AS top_parent_oid FROM pg_catalog.pg_class c JOIN pg_catalog.pg_inherits i ON c.oid = i.inhrelid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = v_parent_schema AND c.relname = v_parent_tablename ) SELECT n.nspname||'.'||c.relname, p.datetime_string, p.partition_interval, p.partition_type INTO v_top_parent, v_top_datetime_string, v_top_interval, v_top_type FROM pg_catalog.pg_class c JOIN top_oid t ON c.oid = t.top_parent_oid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace JOIN @extschema@.part_config p ON p.parent_table = n.nspname||'.'||c.relname WHERE c.oid = t.top_parent_oid AND p.partition_type = 'id'; IF v_top_parent IS NOT NULL THEN v_id_position := (length(p_parent_table) - position('p_' in reverse(p_parent_table))) + 2; v_sub_id_min := substring(p_parent_table from v_id_position)::bigint; v_sub_id_max := (v_sub_id_min + v_top_interval::bigint) - 1; sub_min := v_sub_id_min::text; sub_max := v_sub_id_max::text; END IF; ELSIF p_type = 'time' THEN WITH top_oid AS ( SELECT i.inhparent AS top_parent_oid FROM pg_catalog.pg_class c JOIN pg_catalog.pg_inherits i ON c.oid = i.inhrelid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = v_parent_schema AND c.relname = v_parent_tablename ) SELECT n.nspname||'.'||c.relname, p.datetime_string, p.partition_interval, p.partition_type INTO v_top_parent, v_top_datetime_string, v_top_interval, v_top_type FROM pg_catalog.pg_class c JOIN top_oid t ON c.oid = t.top_parent_oid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace JOIN @extschema@.part_config p ON p.parent_table = n.nspname||'.'||c.relname WHERE c.oid = t.top_parent_oid AND p.partition_type = 'time' OR p.partition_type = 'time-custom'; IF v_top_parent IS NOT NULL THEN v_time_position := (length(p_parent_table) - position('p_' in reverse(p_parent_table))) + 2; IF v_top_interval::interval <> '3 months' OR (v_top_interval::interval = '3 months' AND v_top_type = 'time-custom') THEN v_sub_timestamp_min := to_timestamp(substring(p_parent_table from v_time_position), v_top_datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(substring(p_parent_table from v_time_position), 'q', 1); v_quarter := split_part(substring(p_parent_table from v_time_position), 'q', 2); CASE WHEN v_quarter = '1' THEN v_sub_timestamp_min := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN v_sub_timestamp_min := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN v_sub_timestamp_min := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN v_sub_timestamp_min := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; v_sub_timestamp_max = (v_sub_timestamp_min + v_top_interval::interval) - '1 sec'::interval; sub_min := v_sub_timestamp_min::text; sub_max := v_sub_timestamp_max::text; END IF; ELSE RAISE EXCEPTION 'Invalid type given as parameter to check_subpartition_limits()'; END IF; RETURN; END $$; /* * Check for consistent data in part_config_sub table. Was unable to get this working properly as either a constraint or trigger. * Would either delay raising an error until the next write (which I cannot predict) or disallow future edits to update a sub-partition set's configuration. * This is called by run_maintainance() and at least provides a consistent way to check that I know will run. * If anyone can get a working constraint/trigger, please help! */ CREATE FUNCTION @extschema@.check_subpart_sameconfig(p_parent_table text) RETURNS TABLE (sub_partition_type text , sub_control text , sub_partition_interval text , sub_constraint_cols text[] , sub_premake int , sub_inherit_fk boolean , sub_retention text , sub_retention_schema text , sub_retention_keep_table boolean , sub_retention_keep_index boolean , sub_use_run_maintenance boolean , sub_epoch boolean , sub_optimize_trigger int , sub_optimize_constraint int , sub_infinite_time_partitions boolean , sub_jobmon boolean) LANGUAGE sql STABLE SECURITY DEFINER AS $$ WITH parent_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE n1.nspname = split_part(p_parent_table, '.', 1) AND c1.relname = split_part(p_parent_table, '.', 2) ) , child_tables AS ( SELECT n.nspname||'.'||c.relname AS tablename FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN parent_info pi ON h.inhparent = pi.oid ) -- Column order here must match the RETURNS TABLE definition SELECT DISTINCT a.sub_partition_type , a.sub_control , a.sub_partition_interval , a.sub_constraint_cols , a.sub_premake , a.sub_inherit_fk , a.sub_retention , a.sub_retention_schema , a.sub_retention_keep_table , a.sub_retention_keep_index , a.sub_use_run_maintenance , a.sub_epoch , a.sub_optimize_trigger , a.sub_optimize_constraint , a.sub_infinite_time_partitions , a.sub_jobmon FROM @extschema@.part_config_sub a JOIN child_tables b on a.sub_parent = b.tablename; $$; -- Restore dropped object privileges DO $$ DECLARE v_row record; BEGIN FOR v_row IN SELECT statement FROM partman_preserve_privs_temp LOOP IF v_row.statement IS NOT NULL THEN EXECUTE v_row.statement; END IF; END LOOP; END $$; DROP TABLE IF EXISTS partman_preserve_privs_temp; pg_partman-4.6.0/updates/pg_partman--2.3.0--2.3.1.sql000066400000000000000000000006701412761706100214260ustar00rootroot00000000000000-- NOTE: This update contains no updates if pg_partman is already installed. This file is only here for version upgrade continuity. -- Fixed typo in standalone create_parent.sql file that caused a brand new installation of v2.3.0 to fail (Github Issue #94). -- Added --noparent option to new vacuum_maintenance.py script to allow parent table to be excluded from the maintenance run. -- Added vacuum_maintenance.py script to documentation. pg_partman-4.6.0/updates/pg_partman--2.3.1--2.3.2.sql000066400000000000000000004005731412761706100214360ustar00rootroot00000000000000-- Fixed issue where additional constraints would not get applied on all older partitions if more than one new partition was made for a single partition set in a single maintenance run. You can fix this by using the reapply_constraints.py script to remove then reapply all additional constraints on older tables. Or if you know the specific children that are missing a constraint, you can run apply_constraints() and pass it the child table argument. -- Improved performance of create_parent() when p_start_partition is set. Was previously doing a full seq scan of the parent table which could keep it locked longer than intended (Github Pull Reqest #99). -- New function that returns partition data about a given child name: show_partition_info(). -- Returns start/end values that are contained in a given child table. -- Can pass an optional interval value to see start/end values that are different than the currently configured interval. -- Added new options to vacuum_maintenance.py script -- New --all option tells it to run against all tables managed by pg_partman. -- New --type option sets whether to run against time or id based partition sets managed by pg_partman. -- New --interval option tells the script to only run against child tables older than the given interval. See the script's --help for more info on how this works. -- All these options only work on partition sets managed by pg_partman. If none of these options are set, script still works on non-pg_partman inheritance sets. -- Disallow weekly partitioning to be a subpartition of anything else. Alignment of ISO weeks does not always match up with larger intervals and can cause constraint conflicts. -- Also disallow subpartition interval to be equal to or greater than parent. -- Set part_config_sub & custom_time_partition config tables to dump their contents with pg_dump to ensure all config data is saved. CREATE TEMP TABLE partman_preserve_privs_temp (statement text); INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_partition_id(text, bigint[], boolean, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_partition_id'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_partition_time(text, timestamp[], boolean, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_partition_time'; DROP FUNCTION @extschema@.create_partition_id(text, bigint[], boolean); DROP FUNCTION @extschema@.create_partition_time(text, timestamp[], boolean); SELECT pg_catalog.pg_extension_config_dump('part_config_sub', ''); SELECT pg_catalog.pg_extension_config_dump('custom_time_partitions', ''); CREATE FUNCTION show_partition_info(p_child_table text , p_partition_interval text DEFAULT NULL , p_parent_table text DEFAULT NULL , OUT child_start_time timestamp , OUT child_end_time timestamp , OUT child_start_id bigint , OUT child_end_id bigint , OUT suffix text) RETURNS record LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_child_schema text; v_child_tablename text; v_datetime_string text; v_parent_table text; v_partition_interval text; v_partition_type text; v_quarter text; v_suffix text; v_suffix_position int; v_year text; BEGIN SELECT schemaname, tablename INTO v_child_schema, v_child_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_child_table, '.', 1) AND tablename = split_part(p_child_table, '.', 2); IF v_child_tablename IS NULL THEN RAISE EXCEPTION 'Child table given does not exist (%)', p_child_table; END IF; IF p_parent_table IS NULL THEN SELECT n.nspname||'.'|| c.relname INTO v_parent_table FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhparent JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhrelid::regclass = p_child_table::regclass; ELSE v_parent_table := p_parent_table; END IF; IF p_partition_interval IS NULL THEN SELECT partition_interval, partition_type, datetime_string INTO v_partition_interval, v_partition_type, v_datetime_string FROM @extschema@.part_config WHERE parent_table = v_parent_table; ELSE v_partition_interval := p_partition_interval; SELECT partition_type, datetime_string INTO v_partition_type, v_datetime_string FROM @extschema@.part_config WHERE parent_table = v_parent_table; END IF; IF v_partition_type IS NULL THEN RAISE EXCEPTION 'Parent table of given child not managed by pg_partman: %', v_parent_table; END IF; v_suffix_position := (length(v_child_tablename) - position('p_' in reverse(v_child_tablename))) + 2; v_suffix := substring(v_child_tablename from v_suffix_position); IF v_partition_type = 'time' OR v_partition_type = 'time-custom' THEN IF v_partition_interval::interval <> '3 months' OR (v_partition_interval::interval = '3 months' AND v_partition_type = 'time-custom') THEN child_start_time := to_timestamp(v_suffix, v_datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(v_suffix, 'q', 1); v_quarter := split_part(v_suffix, 'q', 2); CASE WHEN v_quarter = '1' THEN child_start_time := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN child_start_time := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN child_start_time := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN child_start_time := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; child_end_time := (child_start_time + v_partition_interval::interval) - '1 second'::interval; ELSIF v_partition_type = 'id' THEN child_start_id := v_suffix::bigint; child_end_id := (child_start_id + v_partition_interval::bigint) - 1; ELSE RAISE EXCEPTION 'Invalid partition type encountered in show_partition_info()'; END IF; suffix = v_suffix; RETURN; END $$; /* * 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. * To avoid logical complications and contention issues, ALL subpartitions must be maintained using run_maintenance(). * This means the automatic, trigger based partition creation for serial partitioning will not work if it is a subpartition. */ CREATE OR REPLACE FUNCTION create_sub_parent( p_top_parent text , p_control text , p_type text , p_interval text , 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 boolean DEFAULT false , p_jobmon boolean DEFAULT true , p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_last_partition text; v_parent_interval text; v_parent_type text; v_row record; v_row_last_part record; v_run_maint boolean; v_sql text; v_success boolean := false; v_top_type text; BEGIN SELECT use_run_maintenance INTO v_run_maint FROM @extschema@.part_config WHERE parent_table = p_top_parent; IF v_run_maint 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; ELSIF v_run_maint = false THEN RAISE EXCEPTION 'Any parent table that will be part of a sub-partitioned set (on any level) must have use_run_maintenance set to true in part_config table, even for serial partitioning. See documentation for more info.'; END IF; FOR v_row IN -- Loop through all current children to turn them into partitioned tables SELECT partition_schemaname||'.'||partition_tablename AS child_table FROM @extschema@.show_partitions(p_top_parent) LOOP SELECT partition_type, partition_interval INTO v_parent_type, v_parent_interval FROM @extschema@.part_config WHERE parent_table = v_row.child_table; IF v_parent_interval = p_interval THEN RAISE EXCEPTION 'Sub-partition interval cannot be equal to parent interval'; END IF; IF (v_parent_type = 'time' OR v_parent_type = 'time-custom') AND (p_type = 'time' OR p_type = 'time-custom') THEN IF p_interval::interval > v_parent_interval::interval THEN RAISE EXCEPTION 'Sub-partition interval cannot be greater than the given parent interval'; END IF; IF p_interval = 'weekly' AND v_parent_interval::interval > '1 week'::interval THEN 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_parent_type = 'id' THEN IF p_interval::bigint > v_parent_interval::bigint THEN RAISE EXCEPTION 'Sub-partition interval cannot be greater than the given parent interval'; END IF; END IF; -- Just call existing create_parent() function but add the given parameters to the part_config_sub table as well 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_use_run_maintenance := %L , p_start_partition := %L , p_inherit_fk := %L , p_epoch := %L , p_jobmon := %L , p_debug := %L )' , v_row.child_table , p_control , p_type , p_interval , p_constraint_cols , p_premake , true , p_start_partition , p_inherit_fk , p_epoch , p_jobmon , p_debug); EXECUTE v_sql; END LOOP; 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_use_run_maintenance , sub_epoch , sub_jobmon) VALUES ( p_top_parent , p_control , p_type , p_interval , p_constraint_cols , p_premake , p_inherit_fk , true , p_epoch , p_jobmon); v_success := true; RETURN v_success; END $$; /* * Function to manage pre-creation of the next partitions in a set. * Also manages dropping old partitions if the retention option is set. * If p_parent_table is passed, will only run run_maintenance() on that one table (no matter what the configuration table may have set for it) * Otherwise, will run on all tables in the config table with p_run_maintenance() set to true. * For large partition sets, running analyze can cause maintenance to take longer than expected. Can set p_analyze to false to avoid a forced analyze run. * Be aware that constraint exclusion may not work properly until an analyze on the partition set is run. */ CREATE OR REPLACE FUNCTION run_maintenance(p_parent_table text DEFAULT NULL, p_analyze boolean DEFAULT true, p_jobmon boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_check_subpart int; v_create_count int := 0; v_current_partition text; v_current_partition_id bigint; v_current_partition_timestamp timestamp; v_datetime_string text; v_drop_count int := 0; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_created boolean; v_last_partition_id bigint; v_last_partition_timestamp timestamp; v_max_id_parent bigint; v_max_time_parent timestamp; v_next_partition_id bigint; v_next_partition_timestamp timestamp; v_parent_schema text; v_parent_tablename text; v_premade_count int; v_premake_id_max bigint; v_premake_id_min bigint; v_premake_timestamp_min timestamp; v_premake_timestamp_max timestamp; v_row record; v_row_max_id record; v_row_max_time record; v_row_sub record; v_skip_maint boolean; v_step_id bigint; v_step_overflow_id bigint; v_step_serial_id bigint; v_sub_id_max bigint; v_sub_id_max_suffix bigint; v_sub_id_min bigint; v_sub_parent text; v_sub_timestamp_max timestamp; v_sub_timestamp_max_suffix timestamp; v_sub_timestamp_min timestamp; v_tablename text; v_tables_list_sql text; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman maintenance already running.'; RETURN; END IF; 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' AND e.extnamespace = n.oid; END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.add_job(%L)', v_jobmon_schema, 'PARTMAN RUN MAINTENANCE') INTO v_job_id; EXECUTE format('SELECT %I.add_step(%L, %L)', v_jobmon_schema, v_job_id, 'Running maintenance loop') INTO v_step_id; END IF; -- Check for consistent data in part_config_sub table. Was unable to get this working properly as either a constraint or trigger. -- Would either delay raising an error until the next write (which I cannot predict) or disallow future edits to update a sub-partition set's configuration. -- This way at least provides a consistent way to check that I know will run. If anyone can get a working constraint/trigger, please help! -- Don't have to worry about this in the serial trigger maintenance since subpartitioning requires run_maintenance(). FOR v_row IN SELECT sub_parent FROM @extschema@.part_config_sub LOOP SELECT count(*) INTO v_check_subpart FROM @extschema@.check_subpart_sameconfig(v_row.sub_parent); IF v_check_subpart > 1 THEN RAISE EXCEPTION 'Inconsistent data in part_config_sub table. Sub-partition tables that are themselves sub-partitions cannot have differing configuration values among their siblings. Run this query: "SELECT * FROM @extschema@.check_subpart_sameconfig(''%'');" This should only return a single row or nothing. If multiple rows are returned, results are all children of the given parent. Update the differing values to be consistent for your desired values.', v_row.sub_parent; END IF; END LOOP; v_row := NULL; -- Ensure it's reset v_tables_list_sql := 'SELECT parent_table , partition_type , partition_interval , control , premake , datetime_string , undo_in_progress , sub_partition_set_full , epoch , infinite_time_partitions FROM @extschema@.part_config WHERE sub_partition_set_full = false'; IF p_parent_table IS NULL THEN v_tables_list_sql := v_tables_list_sql || ' AND use_run_maintenance = true'; ELSE v_tables_list_sql := v_tables_list_sql || format(' AND parent_table = %L', p_parent_table); END IF; FOR v_row IN EXECUTE v_tables_list_sql LOOP CONTINUE WHEN v_row.undo_in_progress; v_skip_maint := true; -- reset every loop SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(v_row.parent_table, '.', 1) AND tablename = split_part(v_row.parent_table, '.', 2); SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LIMIT 1; IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_last_partition: %', v_row.parent_table, v_last_partition; END IF; IF v_row.partition_type = 'time' OR v_row.partition_type = 'time-custom' THEN SELECT child_start_time INTO v_last_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_row.partition_interval, v_row.parent_table); -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_time IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP IF v_row.epoch = false THEN EXECUTE format('SELECT max(%I)::text FROM %I.%I' , v_row.control , v_row_max_time.partition_schemaname , v_row_max_time.partition_tablename ) INTO v_current_partition_timestamp; ELSE EXECUTE format('SELECT to_timestamp(max(%I))::text FROM %I.%I' , v_row.control , v_row_max_time.partition_schemaname , v_row_max_time.partition_tablename ) INTO v_current_partition_timestamp; END IF; IF v_current_partition_timestamp IS NOT NULL THEN SELECT suffix_timestamp INTO v_current_partition_timestamp FROM @extschema@.show_partition_name(v_row.parent_table, v_current_partition_timestamp::text); EXIT; END IF; END LOOP; -- Check for values in the parent table. If they are there and greater than all child values, use that instead -- This allows maintenance to continue working properly if there is a large gap in data insertion. Data will remain in parent, but new tables will be created IF v_row.epoch = false THEN EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_row.control, v_parent_schema, v_parent_tablename) INTO v_max_time_parent; ELSE EXECUTE format('SELECT to_timestamp(max(%I)) FROM ONLY %I.%I', v_row.control, v_parent_schema, v_parent_tablename) INTO v_max_time_parent; END IF; IF p_debug THEN RAISE NOTICE 'run_maint: v_current_partition_timestamp: %, v_max_time_parent: %', v_current_partition_timestamp, v_max_time_parent; END IF; IF v_max_time_parent > v_current_partition_timestamp THEN SELECT suffix_timestamp INTO v_current_partition_timestamp FROM @extschema@.show_partition_name(v_row.parent_table, v_max_time_parent::text); END IF; IF v_current_partition_timestamp IS NULL THEN -- Partition set is completely empty IF v_row.infinite_time_partitions IS TRUE THEN -- Set it to now so new partitions continue to be created v_current_partition_timestamp = CURRENT_TIMESTAMP; ELSE -- Nothing to do CONTINUE; END IF; END IF; -- If this is a subpartition, determine if the last child table has been made. If so, mark it as full so future maintenance runs can skip it SELECT sub_min::timestamp, sub_max::timestamp INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'time'); IF v_sub_timestamp_max IS NOT NULL THEN SELECT suffix_timestamp INTO v_sub_timestamp_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_timestamp_max::text); IF v_sub_timestamp_max_suffix = v_last_partition_timestamp THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; -- Check and see how many premade partitions there are. v_premade_count = round(EXTRACT('epoch' FROM age(v_last_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); v_next_partition_timestamp := v_last_partition_timestamp; IF p_debug THEN RAISE NOTICE 'run_maint before loop: current_partition_timestamp: %, v_premade_count: %, v_sub_timestamp_min: %, v_sub_timestamp_max: %' , v_current_partition_timestamp , v_premade_count , v_sub_timestamp_min , v_sub_timestamp_max; END IF; -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed WHILE (v_premade_count < v_row.premake) LOOP IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_premade_count: %, v_next_partition_timestamp: %', v_row.parent_table, v_premade_count, v_next_partition_timestamp; END IF; IF v_next_partition_timestamp < v_sub_timestamp_min OR v_next_partition_timestamp > v_sub_timestamp_max THEN -- With subpartitioning, no need to run if the timestamp is not in the parent table's range EXIT; END IF; BEGIN v_next_partition_timestamp := v_next_partition_timestamp + v_row.partition_interval::interval; EXCEPTION WHEN datetime_field_overflow THEN v_premade_count := v_row.premake; -- do this so it can exit the premake check loop and continue in the outer for loop IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.add_step(%L, %L)', v_jobmon_schema, v_job_id, 'Attempted partition time interval is outside PostgreSQL''s supported time range.') INTO v_step_overflow_id; EXECUTE format('SELECT %I.update_step(%L, %L, %L)', v_jobmon_schema, v_step_overflow_id, 'CRITICAL', 'Child partition creation skippd for parent table '||v_partition_time); END IF; RAISE WARNING 'Attempted partition time interval is outside PostgreSQL''s supported time range. Child partition creation skipped for parent table %', v_row.parent_table; CONTINUE; END; v_last_partition_created := @extschema@.create_partition_time(v_row.parent_table, ARRAY[v_next_partition_timestamp], p_analyze); IF v_last_partition_created THEN v_create_count := v_create_count + 1; PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); END IF; v_premade_count = round(EXTRACT('epoch' FROM age(v_next_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); END LOOP; ELSIF v_row.partition_type = 'id' THEN -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%I)::text FROM %I.%I' , v_row.control , v_row_max_id.partition_schemaname , v_row_max_id.partition_tablename) INTO v_current_partition_id; IF v_current_partition_id IS NOT NULL THEN SELECT suffix_id INTO v_current_partition_id FROM @extschema@.show_partition_name(v_row.parent_table, v_current_partition_id::text); EXIT; END IF; END LOOP; -- Check for values in the parent table. If they are there and greater than all child values, use that instead -- This allows maintenance to continue working properly if there is a large gap in data insertion. Data will remain in parent, but new tables will be created EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_row.control, v_parent_schema, v_parent_tablename) INTO v_max_id_parent; IF v_max_id_parent > v_current_partition_id THEN SELECT suffix_id INTO v_current_partition_id FROM @extschema@.show_partition_name(v_row.parent_table, v_max_id_parent::text); END IF; IF v_current_partition_id IS NULL THEN -- Partition set is completely empty. Nothing to do CONTINUE; END IF; SELECT child_start_id INTO v_last_partition_id FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_row.partition_interval, v_row.parent_table); -- Determine if this table is a child of a subpartition parent. If so, get limits to see if run_maintenance even needs to run for it. SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'id'); IF v_sub_id_max IS NOT NULL THEN SELECT suffix_id INTO v_sub_id_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_id_max::text); IF v_sub_id_max_suffix = v_last_partition_id THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; v_next_partition_id := v_last_partition_id; v_premade_count := ((v_last_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed. WHILE (v_premade_count < v_row.premake) LOOP IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_premade_count: %, v_next_partition_id: %', v_row.parent_table, v_premade_count, v_next_partition_id; END IF; IF v_next_partition_id < v_sub_id_min OR v_next_partition_id > v_sub_id_max THEN -- With subpartitioning, no need to run if the id is not in the parent table's range EXIT; END IF; v_next_partition_id := v_next_partition_id + v_row.partition_interval::bigint; v_last_partition_created := @extschema@.create_partition_id(v_row.parent_table, ARRAY[v_next_partition_id], p_analyze); IF v_last_partition_created THEN v_create_count := v_create_count + 1; PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); END IF; v_premade_count := ((v_next_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); END LOOP; END IF; -- end main IF check for time or id END LOOP; -- end of creation loop -- Manage dropping old partitions if retention option is set FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND (partition_type = 'time' OR partition_type = 'time-custom') LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); END IF; END IF; IF v_drop_count > 0 THEN PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); END IF; END LOOP; FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND partition_type = 'id' LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); END IF; END IF; IF v_drop_count > 0 THEN PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.update_step(%L, %L, ''Partition maintenance finished. %s partitions made. %s partitions dropped.'')' , v_jobmon_schema , v_step_id , 'OK' , v_create_count , v_drop_count); IF v_step_overflow_id IS NOT NULL OR v_step_serial_id IS NOT NULL THEN EXECUTE format('SELECT %I.fail_job(%L)', v_jobmon_schema, v_job_id); ELSE EXECUTE format('SELECT %I.close_job(%L)', v_jobmon_schema, v_job_id); END IF; END IF; 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 RUN MAINTENANCE'')', v_jobmon_schema) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to create id partitions */ CREATE FUNCTION create_partition_id(p_parent_table text, p_partition_ids bigint[], p_analyze boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_exists text; v_grantees text[]; v_hasoids boolean; v_id bigint; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_parent_tablespace text; v_partition_interval bigint; v_partition_created boolean := false; v_partition_name text; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_sub_id_max bigint; v_sub_id_min bigint; v_unlogged char; BEGIN SELECT control , partition_interval , inherit_fk , jobmon INTO v_control , v_partition_interval , v_inherit_fk , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'id'); SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1) AND tablename = split_part(p_parent_table, '.', 2); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; FOREACH v_id IN ARRAY p_partition_ids LOOP -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_id_min IS NOT NULL THEN IF v_id < v_sub_id_min OR v_id > v_sub_id_max THEN CONTINUE; END IF; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_id::text, TRUE); -- If child table already exists, skip creation SELECT tablename INTO v_exists FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema AND tablename = v_partition_name; IF v_exists IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_id||' to '||(v_id + v_partition_interval)-1); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_parent_tablename AND n.nspname = v_parent_schema; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_parent_tablename AND n.nspname = v_parent_schema; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %s AND %I < %s )' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_control , v_id , v_control , v_id + v_partition_interval); EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I', v_parent_schema, v_partition_name, v_parent_schema, v_parent_tablename); PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_use_run_maintenance , sub_infinite_time_partitions , sub_jobmon FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Subpartitioning '||v_partition_name); END IF; 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_use_run_maintenance := %L , p_inherit_fk := %L , p_epoch := %L , p_jobmon := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_use_run_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions WHERE parent_table = v_parent_schema||'.'||v_partition_name; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; -- end sub partitioning LOOP -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table, p_job_id := v_job_id, p_debug := p_debug); v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s', p_parent_table)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; PERFORM close_job(v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RETURN v_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to create a child table in a time-based partition set */ CREATE FUNCTION create_partition_time(p_parent_table text, p_partition_times timestamp[], p_analyze boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_datetime_string text; v_exists text; v_epoch boolean; v_grantees text[]; v_hasoids boolean; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_partition_created boolean := false; v_partition_name text; v_partition_suffix text; v_parent_tablespace text; v_partition_interval interval; v_partition_timestamp_end timestamp; v_partition_timestamp_start timestamp; v_quarter text; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_sub_timestamp_max timestamp; v_sub_timestamp_min timestamp; v_trunc_value text; v_time timestamp; v_type text; v_unlogged char; v_year text; BEGIN SELECT partition_type , control , partition_interval , epoch , inherit_fk , jobmon , datetime_string INTO v_type , v_control , v_partition_interval , v_epoch , v_inherit_fk , v_jobmon , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'time' OR partition_type = 'time-custom'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::timestamp, sub_max::timestamp INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'time'); SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1) AND tablename = split_part(p_parent_table, '.', 2); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_timestamp_start := v_time; BEGIN v_partition_timestamp_end := v_time + v_partition_interval; 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_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_time||' skipped'); CONTINUE; END; -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_timestamp_min IS NOT NULL THEN IF v_time < v_sub_timestamp_min OR v_time > v_sub_timestamp_max THEN CONTINUE; END IF; END IF; -- This suffix generation code is in partition_data_time() as well v_partition_suffix := to_char(v_time, v_datetime_string); v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); SELECT tablename INTO v_exists FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema AND tablename = v_partition_name; IF v_exists IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Creating new partition %s.%s with interval from %s to %s' , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end-'1sec'::interval)); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_parent_tablename AND n.nspname = v_parent_schema; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_parent_tablename AND n.nspname = v_parent_schema; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; IF v_epoch = false THEN EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_control , v_partition_timestamp_start , v_control , v_partition_timestamp_end); ELSE EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (to_timestamp(%I) >= %L AND to_timestamp(%I) < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_control , v_partition_timestamp_start , v_control , v_partition_timestamp_end); END IF; EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); -- If custom time, set extra config options. IF v_type = 'time-custom' THEN INSERT INTO @extschema@.custom_time_partitions (parent_table, child_table, partition_range) VALUES ( p_parent_table, v_parent_schema||'.'||v_partition_name, tstzrange(v_partition_timestamp_start, v_partition_timestamp_end, '[)') ); END IF; PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_use_run_maintenance , sub_infinite_time_partitions , sub_jobmon FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Subpartitioning %s.%s', v_parent_schema, v_partition_name)); END IF; 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_use_run_maintenance := %L , p_inherit_fk := %L , p_epoch := %L , p_jobmon := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_use_run_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions WHERE parent_table = v_parent_schema||'.'||v_partition_name; END LOOP; -- end sub partitioning LOOP -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table, p_job_id := v_job_id, p_debug := p_debug); v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s. Attempted intervals: %s', p_parent_table, p_partition_times)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; 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; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RETURN v_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Apply constraints managed by partman extension */ CREATE OR REPLACE FUNCTION apply_constraints(p_parent_table text, p_child_table text DEFAULT NULL, p_analyze boolean DEFAULT FALSE, p_job_id bigint DEFAULT NULL, p_debug boolean DEFAULT FALSE) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_child_exists text; v_child_tablename text; v_col text; v_constraint_cols text[]; v_constraint_col_type text; v_constraint_name text; v_constraint_values record; v_control text; v_datetime_string text; v_existing_constraint_name text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_id bigint; v_last_partition_timestamp timestamp; v_max_id bigint; v_max_timestamp timestamp; v_old_search_path text; v_optimize_constraint int; v_parent_schema text; v_parent_table text; v_parent_tablename text; v_partition_interval text; v_partition_suffix text; v_premake int; v_sql text; v_step_id bigint; v_suffix_position int; v_type text; BEGIN SELECT parent_table , partition_type , control , premake , partition_interval , optimize_constraint , datetime_string , constraint_cols , jobmon INTO v_parent_table , v_type , v_control , v_premake , v_partition_interval , v_optimize_constraint , v_datetime_string , v_constraint_cols , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND constraint_cols IS NOT NULL; IF v_constraint_cols IS NULL THEN IF p_debug THEN RAISE NOTICE 'Given parent table (%) not set up for constraint management (constraint_cols is NULL)', p_parent_table; END IF; -- Returns silently to allow this function to be simply called by maintenance processes without having to check if config options are set. RETURN; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(v_parent_table, '.', 1) AND tablename = split_part(v_parent_table, '.', 2); IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE CONSTRAINT: %s', v_parent_table)); ELSE v_job_id = p_job_id; END IF; END IF; -- If p_child_table is null, figure out the partition that is the one right before the optimize_constraint value backwards. IF p_child_table IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying additional constraints: Automatically determining most recent child on which to apply constraints'); END IF; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(v_parent_table, 'DESC') LIMIT 1; IF v_type IN ('time', 'time-custom') THEN SELECT child_start_time INTO v_last_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_partition_interval, v_parent_table); v_partition_suffix := to_char(v_last_partition_timestamp - (v_partition_interval::interval * (v_optimize_constraint + v_premake + 1) ), v_datetime_string); ELSIF v_type = 'id' THEN SELECT child_start_id INTO v_last_partition_id FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_partition_interval, v_parent_table); v_partition_suffix := (v_last_partition_id - (v_partition_interval::int * (v_optimize_constraint + v_premake + 1) ))::text; END IF; v_child_tablename := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); IF p_debug THEN RAISE NOTICE 'apply_constraint: v_parent_tablename: % , v_partition_suffix: %', v_parent_tablename, v_partition_suffix; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Target child table: %s.%s', v_parent_schema, v_child_tablename)); END IF; ELSE v_child_tablename = split_part(p_child_table, '.', 2); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying additional constraints: Checking if target child table exists'); END IF; SELECT tablename FROM pg_catalog.pg_tables INTO v_child_exists WHERE schemaname = v_parent_schema AND tablename = v_child_tablename; IF v_child_exists IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Target child table (%s) does not exist. Skipping constraint creation.', v_child_tablename)); IF p_job_id IS NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; IF p_debug THEN RAISE NOTICE 'Target child table (%) does not exist. Skipping constraint creation.', v_child_tablename; END IF; RETURN; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; FOREACH v_col IN ARRAY v_constraint_cols LOOP SELECT con.conname INTO v_existing_constraint_name FROM pg_catalog.pg_constraint con JOIN pg_class c ON c.oid = con.conrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN pg_catalog.pg_attribute a ON con.conrelid = a.attrelid WHERE c.relname = v_child_tablename AND n.nspname = v_parent_schema AND con.conname LIKE 'partmanconstr_%' AND con.contype = 'c' AND a.attname = v_col AND ARRAY[a.attnum] OPERATOR(pg_catalog.<@) con.conkey AND a.attisdropped = false; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Applying additional constraints: Applying new constraint on column: %s', v_col)); END IF; IF v_existing_constraint_name IS NOT NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Partman managed constraint already exists on this table (%s) and column (%s). Skipping creation.', v_child_tablename, v_col)); END IF; IF p_debug THEN RAISE NOTICE 'Partman managed constraint already exists on this table (%) and column (%). Skipping creation.', v_child_tablename, v_col ; END IF; CONTINUE; END IF; -- Ensure column name gets put on end of constraint name to help avoid naming conflicts v_constraint_name := @extschema@.check_name_length('partmanconstr_'||v_child_tablename, p_suffix := '_'||v_col); EXECUTE format('SELECT min(%I)::text AS min, max(%I)::text AS max FROM %I.%I', v_col, v_col, v_parent_schema, v_child_tablename) INTO v_constraint_values; IF v_constraint_values IS NOT NULL THEN v_sql := format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I <= %L)' , v_parent_schema , v_child_tablename , v_constraint_name , v_col , v_constraint_values.min , v_col , v_constraint_values.max); IF p_debug THEN RAISE NOTICE 'Constraint creation query: %', v_sql; END IF; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('New constraint created: %s', v_sql)); END IF; ELSE IF p_debug THEN RAISE NOTICE 'Given column (%) contains all NULLs. No constraint created', v_col; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Given column (%s) contains all NULLs. No constraint created', v_col)); END IF; END IF; END LOOP; IF p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Applying additional constraints: Running analyze on partition set: %s', v_parent_table)); END IF; IF p_debug THEN RAISE NOTICE 'Running analyze on partition set: %', v_parent_table; END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; 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 CONSTRAINT: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Check if parent table is a subpartition of an already existing partition set managed by pg_partman * If so, return the limits of what child tables can be created under the given parent table based on its own suffix */ CREATE OR REPLACE FUNCTION check_subpartition_limits(p_parent_table text, p_type text, OUT sub_min text, OUT sub_max text) RETURNS record LANGUAGE plpgsql AS $$ DECLARE v_datetime_string text; v_id_position int; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_quarter text; v_sub_id_max bigint; v_sub_id_min bigint; v_sub_timestamp_max timestamp; v_sub_timestamp_min timestamp; v_time_position int; v_top_datetime_string text; v_top_interval text; v_top_parent text; v_top_type text; v_year text; BEGIN SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1) AND tablename = split_part(p_parent_table, '.', 2); -- CTE query is done individually for each type (time, id) because it should return NULL if the top parent is not the same type in a subpartition set (id->time or time->id) IF p_type = 'id' THEN WITH top_oid AS ( SELECT i.inhparent AS top_parent_oid FROM pg_catalog.pg_class c JOIN pg_catalog.pg_inherits i ON c.oid = i.inhrelid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = v_parent_schema AND c.relname = v_parent_tablename ) SELECT n.nspname||'.'||c.relname, p.datetime_string, p.partition_interval, p.partition_type INTO v_top_parent, v_top_datetime_string, v_top_interval, v_top_type FROM pg_catalog.pg_class c JOIN top_oid t ON c.oid = t.top_parent_oid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace JOIN @extschema@.part_config p ON p.parent_table = n.nspname||'.'||c.relname WHERE c.oid = t.top_parent_oid AND p.partition_type = 'id'; IF v_top_parent IS NOT NULL THEN SELECT child_start_id::text, child_end_id::text INTO sub_min, sub_max FROM @extschema@.show_partition_info(p_parent_table, v_top_interval, v_top_parent); END IF; ELSIF p_type = 'time' THEN WITH top_oid AS ( SELECT i.inhparent AS top_parent_oid FROM pg_catalog.pg_class c JOIN pg_catalog.pg_inherits i ON c.oid = i.inhrelid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = v_parent_schema AND c.relname = v_parent_tablename ) SELECT n.nspname||'.'||c.relname, p.datetime_string, p.partition_interval, p.partition_type INTO v_top_parent, v_top_datetime_string, v_top_interval, v_top_type FROM pg_catalog.pg_class c JOIN top_oid t ON c.oid = t.top_parent_oid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace JOIN @extschema@.part_config p ON p.parent_table = n.nspname||'.'||c.relname WHERE c.oid = t.top_parent_oid AND p.partition_type = 'time' OR p.partition_type = 'time-custom'; IF v_top_parent IS NOT NULL THEN SELECT child_start_time::text, child_end_time::text INTO sub_min, sub_max FROM @extschema@.show_partition_info(p_parent_table, v_top_interval, v_top_parent); END IF; ELSE RAISE EXCEPTION 'Invalid type given as parameter to check_subpartition_limits()'; END IF; RETURN; END $$; /* * Function to turn a table into the parent of a partition set */ CREATE OR REPLACE FUNCTION 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_use_run_maintenance boolean DEFAULT NULL , p_start_partition text DEFAULT NULL , p_inherit_fk boolean DEFAULT true , p_epoch boolean DEFAULT false , p_jobmon boolean DEFAULT true , p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_base_timestamp timestamp; v_count int := 1; v_datetime_string 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_job_id bigint; v_jobmon_schema text; v_last_partition_created boolean; v_max bigint; v_notnull boolean; v_old_search_path text; v_parent_partition_id bigint; v_parent_partition_timestamp timestamp; v_parent_schema text; v_parent_tablename text; v_partition_time timestamp; v_partition_time_array timestamp[]; v_partition_id_array bigint[]; v_row record; v_run_maint boolean; v_sql text; v_start_time timestamp; v_starting_partition_id bigint; v_step_id bigint; v_step_overflow_id bigint; v_sub_parent text; v_success boolean := false; 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); BEGIN IF position('.' in p_parent_table) = 0 THEN RAISE EXCEPTION 'Parent table must be schema qualified'; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1) AND tablename = split_part(p_parent_table, '.', 2); 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 AND n.nspname = v_parent_schema AND a.attname = p_control; IF 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; IF p_type = 'id' AND p_epoch = true THEN RAISE EXCEPTION 'p_epoch can only be used with time-based partitioning'; END IF; IF NOT @extschema@.check_partition_type(p_type) THEN RAISE EXCEPTION '% is not a valid partitioning type', p_type; END IF; IF p_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF p_use_run_maintenance IS NOT NULL THEN IF p_use_run_maintenance IS FALSE AND (p_type = 'time' OR p_type = 'time-custom') THEN RAISE EXCEPTION 'p_run_maintenance cannot be set to false for time based partitioning'; END IF; v_run_maint := p_use_run_maintenance; ELSIF p_type = 'time' OR p_type = 'time-custom' THEN v_run_maint := TRUE; ELSIF p_type = 'id' THEN v_run_maint := FALSE; ELSE RAISE EXCEPTION 'use_run_maintenance value cannot be set NULL'; END IF; 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 AND n.nspname = v_parent_schema ), sibling_children AS ( SELECT i.inhrelid::regclass::text AS tablename FROM pg_inherits i JOIN parent_table p ON i.inhparent = p.parent_oid ) 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_use_run_maintenance , sub_epoch , sub_optimize_trigger , sub_optimize_constraint , sub_infinite_time_partitions , sub_jobmon 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_use_run_maintenance , sub_epoch , sub_optimize_trigger , sub_optimize_constraint , sub_infinite_time_partitions , sub_jobmon) 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_use_run_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); END LOOP; IF p_type = 'time' OR p_type = 'time-custom' 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 <> 'time-custom' THEN RAISE EXCEPTION 'Must use a predefined time interval if not using type "time-custom". See documentation.'; 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::timestamp, 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))::timestamp; 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 , use_run_maintenance , inherit_fk , jobmon) VALUES ( p_parent_table , p_type , v_time_interval , p_epoch , p_control , p_premake , p_constraint_cols , v_datetime_string , v_run_maint , p_inherit_fk , p_jobmon); 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 AND n.nspname = v_parent_schema ) 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 -- Currently unknown edge case if code gets here RAISE EXCEPTION 'No child tables created. Unexpected edge case encountered. Please report this error to author with conditions that led to it.'; END IF; END 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 p_type = 'id' THEN v_id_interval := p_interval::bigint; IF v_id_interval < 10 THEN RAISE EXCEPTION 'Interval for serial 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 AND c.relname = v_higher_parent_table ) SELECT n.nspname, c.relname INTO v_higher_parent_schema, v_higher_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 WHERE p.partition_type = 'id'; IF v_higher_parent_table IS NOT NULL THEN -- 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 , use_run_maintenance , inherit_fk , jobmon) VALUES ( p_parent_table , p_type , v_id_interval , p_control , p_premake , p_constraint_cols , v_run_maint , p_inherit_fk , p_jobmon); 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 AND n.nspname = v_parent_schema ) 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 WHERE p.partition_type = 'id'; 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 EXCEPTION 'No child tables created. Unexpected edge case encountered. Please report this error to author with conditions that led to it.'; END IF; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating partition function'); END IF; IF p_type = 'time' OR p_type = 'time-custom' 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 p_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); 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; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; 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 $$; /* * Function to drop child tables from an id-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ CREATE OR REPLACE FUNCTION drop_partition_id(p_parent_table text, p_retention bigint DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_control text; v_drop_count int := 0; v_index record; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_max bigint; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_partition_id bigint; v_retention bigint; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_row record; v_row_max_id record; v_step_id bigint; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman drop_partition_id')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_id already running.'; RETURN 0; END IF; -- Allow override of configuration options IF p_retention IS NULL THEN SELECT partition_interval::bigint , control , retention::bigint , retention_keep_table , retention_keep_index , retention_schema , jobmon INTO v_partition_interval , v_control , v_retention , v_retention_keep_table , v_retention_keep_index , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id' AND retention IS NOT NULL; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE SELECT partition_interval::bigint , control , retention_keep_table , retention_keep_index , retention_schema , jobmon INTO v_partition_interval , v_control , v_retention_keep_table , v_retention_keep_index , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; v_retention := p_retention; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1) AND tablename = split_part(p_parent_table, '.', 2); -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%I) FROM %I.%I', v_control, v_row_max_id.partition_schemaname, v_row_max_id.partition_tablename) INTO v_max; IF v_max IS NOT NULL THEN EXIT; END IF; END LOOP; -- Loop through child tables of the given parent FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'ASC') LOOP SELECT child_start_id INTO v_partition_id FROM @extschema@.show_partition_info(v_row.partition_schemaname||'.'||v_row.partition_tablename , v_partition_interval::text , p_parent_table); -- Add one interval since partition names contain the start of the constraint period IF v_retention <= (v_max - (v_partition_id + v_partition_interval)) THEN -- Only create a jobmon entry if there's actual retention work done IF v_jobmon_schema IS NOT NULL AND v_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN DROP ID PARTITION: %s', p_parent_table)); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Uninherit table %s.%s from %s', v_row.partition_schemaname, v_row.partition_tablename, p_parent_table)); END IF; EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_row.partition_schemaname , v_row.partition_tablename , v_parent_schema , v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop table %s.%s', v_row.partition_schemaname, v_row.partition_tablename)); END IF; EXECUTE format('DROP TABLE %I.%I CASCADE', v_row.partition_schemaname, v_row.partition_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN FOR v_index IN WITH child_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE c1.relname = v_row.partition_tablename AND n1.nspname = v_row.partition_schema ) SELECT c.relname as name , con.conname FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid LEFT JOIN pg_catalog.pg_constraint con ON i.indexrelid = con.conindid JOIN child_info ON i.indrelid = child_info.oid LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop index %s from %s.%s' , v_index.name , v_row.partition_schemaname , v_row.partition_tablename)); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I DROP CONSTRAINT %I', v_row.partition_schemaname, v_row.partition_tablename, v_index.conname); ELSE EXECUTE format('DROP INDEX %I.%I', v_row.partition_schemaname, v_index.name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Moving table %s.%s to schema %s' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema)); END IF; EXECUTE format('ALTER TABLE %I.%I SET SCHEMA %I' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if -- If child table is a subpartition, remove it from part_config & part_config_sub (should cascade due to FK) DELETE FROM @extschema@.part_config WHERE parent_table = v_row.partition_schemaname ||'.'||v_row.partition_tablename; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', format('%s partitions dropped.', v_drop_count)); PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RETURN v_drop_count; 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 DROP ID PARTITION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to drop child tables from a time-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ CREATE OR REPLACE FUNCTION drop_partition_time(p_parent_table text, p_retention interval DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_datetime_string text; v_drop_count int := 0; v_index record; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_partition_timestamp timestamp; v_retention interval; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_row record; v_step_id bigint; v_type text; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman drop_partition_time')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_time already running.'; RETURN 0; END IF; -- Allow override of configuration options IF p_retention IS NULL THEN SELECT partition_type , partition_interval::interval , retention::interval , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_type , v_partition_interval , v_retention , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom') AND retention IS NOT NULL; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE SELECT partition_type , partition_interval::interval , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_type , v_partition_interval , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); v_retention := p_retention; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1) AND tablename = split_part(p_parent_table, '.', 2); -- Loop through child tables of the given parent FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'DESC') LOOP -- pull out datetime portion of partition's tablename to make the next one SELECT child_start_time INTO v_partition_timestamp FROM @extschema@.show_partition_info(v_row.partition_schemaname||'.'||v_row.partition_tablename , v_partition_interval::text , p_parent_table); -- Add one interval since partition names contain the start of the constraint period IF v_retention < (CURRENT_TIMESTAMP - (v_partition_timestamp + v_partition_interval)) THEN -- Only create a jobmon entry if there's actual retention work done IF v_jobmon_schema IS NOT NULL AND v_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN DROP TIME PARTITION: %s', p_parent_table)); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Uninherit table %s.%s from %s' , v_row.partition_schemaname , v_row.partition_tablename , p_parent_table)); END IF; EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_row.partition_schemaname , v_row.partition_tablename , v_parent_schema , v_parent_tablename); IF v_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_row.partition_schemaname||'.'||v_row.partition_tablename; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop table %s.%s', v_row.partition_schemaname, v_row.partition_tablename)); END IF; EXECUTE format('DROP TABLE %I.%I CASCADE', v_row.partition_schemaname, v_row.partition_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN FOR v_index IN WITH child_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE c1.relname = v_row.partition_tablename AND n1.nspname = v_row.partition_schemaname ) SELECT c.relname as name , con.conname FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid LEFT JOIN pg_catalog.pg_constraint con ON i.indexrelid = con.conindid JOIN child_info ON i.indrelid = child_info.oid LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop index %s from %s.%s' , v_index.name , v_row.partition_schemaname , v_row.partition_tablename)); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I DROP CONSTRAINT %I' , v_row.partition_schemaname , v_row.partition_tablename , v_index.conname); ELSE EXECUTE format('DROP INDEX %I.%I', v_parent_schema, v_index.name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Moving table %s.%s to schema %s' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema)); END IF; EXECUTE format('ALTER TABLE %I.%I SET SCHEMA %I', v_row.partition_schemaname, v_row.partition_tablename, v_retention_schema); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if -- If child table is a subpartition, remove it from part_config & part_config_sub (should cascade due to FK) DELETE FROM @extschema@.part_config WHERE parent_table = v_row.partition_schemaname||'.'||v_row.partition_tablename; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', format('%s partitions dropped.', v_drop_count)); PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RETURN v_drop_count; 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 DROP TIME PARTITION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Populate the child table(s) of a time-based partition set with old data from the original parent */ CREATE OR REPLACE FUNCTION partition_data_time(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval interval DEFAULT NULL, p_lock_wait numeric DEFAULT 0, p_order text DEFAULT 'ASC') RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_datetime_string text; v_current_partition_name text; v_epoch boolean; v_last_partition text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_timestamp timestamp; v_min_partition_timestamp timestamp; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_partition_suffix text; v_partition_timestamp timestamp[]; v_quarter text; v_rowcount bigint; v_sql text; v_start_control timestamp; v_time_position int; v_total_rows bigint := 0; v_type text; v_year text; BEGIN SELECT partition_type , partition_interval::interval , control , datetime_string , epoch INTO v_type , v_partition_interval , v_control , v_datetime_string , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1) AND tablename = split_part(p_parent_table, '.', 2); SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; FOR i IN 1..p_batch_count LOOP IF v_epoch = false THEN IF p_order = 'ASC' THEN EXECUTE format('SELECT min(%I) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSIF p_order = 'DESC' THEN EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; ELSE IF p_order = 'ASC' THEN EXECUTE format('SELECT to_timestamp(min(%I)) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSIF p_order = 'DESC' THEN EXECUTE format('SELECT to_timestamp(max(%I)) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; END IF; IF v_start_control IS NULL THEN EXIT; END IF; IF v_type = 'time' THEN CASE WHEN v_partition_interval = '15 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '15min'::interval * floor(date_part('minute', v_start_control) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '30min'::interval * floor(date_part('minute', v_start_control) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control); WHEN v_partition_interval = '1 day' THEN v_min_partition_timestamp := date_trunc('day', v_start_control); WHEN v_partition_interval = '1 week' THEN v_min_partition_timestamp := date_trunc('week', v_start_control); WHEN v_partition_interval = '1 month' THEN v_min_partition_timestamp := date_trunc('month', v_start_control); WHEN v_partition_interval = '3 months' THEN v_min_partition_timestamp := date_trunc('quarter', v_start_control); WHEN v_partition_interval = '1 year' THEN v_min_partition_timestamp := date_trunc('year', v_start_control); END CASE; ELSIF v_type = 'time-custom' THEN SELECT child_start_time INTO v_min_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition , v_partition_interval , p_parent_table); v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; LOOP IF v_start_control >= v_min_partition_timestamp AND v_start_control < v_max_partition_timestamp THEN EXIT; ELSE BEGIN IF v_start_control > v_max_partition_timestamp THEN -- Keep going forward in time, checking if child partition time interval encompasses the current v_start_control value v_min_partition_timestamp := v_max_partition_timestamp; v_max_partition_timestamp := v_max_partition_timestamp + v_partition_interval; ELSE -- Keep going backwards in time, checking if child partition time interval encompasses the current v_start_control value v_max_partition_timestamp := v_min_partition_timestamp; v_min_partition_timestamp := v_min_partition_timestamp - v_partition_interval; END IF; EXCEPTION WHEN datetime_field_overflow THEN RAISE EXCEPTION 'Attempted partition time interval is outside PostgreSQL''s supported time range. Unable to create partition with interval before timestamp % ', v_min_partition_interval; END; END IF; END LOOP; END IF; v_partition_timestamp := ARRAY[v_min_partition_timestamp]; IF p_order = 'ASC' THEN -- Ensure batch interval given as parameter doesn't cause maximum to overflow the current partition maximum IF (v_start_control + p_batch_interval) >= (v_min_partition_timestamp + v_partition_interval) THEN v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; ELSE v_max_partition_timestamp := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; -- Ensure batch interval given as parameter doesn't cause minimum to underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_timestamp THEN v_min_partition_timestamp = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN IF v_epoch = false THEN v_sql := format('SELECT * FROM ONLY %I.%I WHERE %I >= %L AND %I < %L FOR UPDATE NOWAIT' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_timestamp , v_control , v_max_partition_timestamp); ELSE v_sql := format('SELECT * FROM ONLY %I.%I WHERE to_timestamp(%I) >= %L AND to_timestamp(%I) < %L FOR UPDATE NOWAIT' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_timestamp , v_control , v_max_partition_timestamp); END IF; EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; PERFORM @extschema@.create_partition_time(p_parent_table, v_partition_timestamp); -- This suffix generation code is in create_partition_time() as well v_partition_suffix := to_char(v_min_partition_timestamp, v_datetime_string); v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); IF v_epoch = false THEN v_sql := format('WITH partition_data AS ( DELETE FROM ONLY %I.%I WHERE %I >= %L AND %I < %L RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_timestamp , v_control , v_max_partition_timestamp , v_parent_schema , v_current_partition_name); ELSE v_sql := format('WITH partition_data AS ( DELETE FROM ONLY %I.%I WHERE to_timestamp(%I) >= %L AND to_timestamp(%I) < %L RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_timestamp , v_control , v_max_partition_timestamp , v_parent_schema , v_current_partition_name); END IF; EXECUTE v_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; PERFORM @extschema@.create_function_time(p_parent_table); RETURN v_total_rows; END $$; /* * Given a parent table and partition value, return the name of the child partition it would go in. * If using epoch time partitioning, give the text representation of the timestamp NOT the epoch integer value (use to_timestamp() to convert epoch values). * Also returns just the suffix value and true if the child table exists or false if it does not */ CREATE OR REPLACE FUNCTION show_partition_name(p_parent_table text, p_value text, OUT partition_table text, OUT suffix_timestamp timestamp, OUT suffix_id bigint, OUT table_exists boolean) RETURNS record LANGUAGE plpgsql STABLE AS $$ DECLARE v_child_exists text; v_datetime_string text; v_max_range timestamptz; v_min_range timestamptz; v_parent_schema text; v_parent_tablename text; v_partition_interval text; v_type text; BEGIN SELECT partition_type , partition_interval , datetime_string INTO v_type , v_partition_interval , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_type IS NULL THEN RAISE EXCEPTION 'Parent table given is not managed by pg_partman (%)', p_parent_table; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1) AND tablename = split_part(p_parent_table, '.', 2); IF v_parent_tablename IS NULL THEN RAISE EXCEPTION 'Parent table given does not exist (%)', p_parent_table; END IF; IF v_type = 'time' THEN CASE WHEN v_partition_interval::interval = '15 mins' THEN suffix_timestamp := date_trunc('hour', p_value::timestamp) + '15min'::interval * floor(date_part('minute', p_value::timestamp) / 15.0); WHEN v_partition_interval::interval = '30 mins' THEN suffix_timestamp := date_trunc('hour', p_value::timestamp) + '30min'::interval * floor(date_part('minute', p_value::timestamp) / 30.0); WHEN v_partition_interval::interval = '1 hour' THEN suffix_timestamp := date_trunc('hour', p_value::timestamp); WHEN v_partition_interval::interval = '1 day' THEN suffix_timestamp := date_trunc('day', p_value::timestamp); WHEN v_partition_interval::interval = '1 week' THEN suffix_timestamp := date_trunc('week', p_value::timestamp); WHEN v_partition_interval::interval = '1 month' THEN suffix_timestamp := date_trunc('month', p_value::timestamp); WHEN v_partition_interval::interval = '3 months' THEN suffix_timestamp := date_trunc('quarter', p_value::timestamp); WHEN v_partition_interval::interval = '1 year' THEN suffix_timestamp := date_trunc('year', p_value::timestamp); END CASE; partition_table := v_parent_schema||'.'||@extschema@.check_name_length(v_parent_tablename, to_char(suffix_timestamp, v_datetime_string), TRUE); ELSIF v_type = 'id' THEN suffix_id := (p_value::bigint - (p_value::bigint % v_partition_interval::bigint)); partition_table := v_parent_schema||'.'||@extschema@.check_name_length(v_parent_tablename, suffix_id::text, TRUE); ELSIF v_type = 'time-custom' THEN SELECT child_table, lower(partition_range) INTO partition_table, suffix_timestamp FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND partition_range @> p_value::timestamptz; IF partition_table IS NULL THEN SELECT max(upper(partition_range)) INTO v_max_range FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table; SELECT min(lower(partition_range)) INTO v_min_range FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table; IF p_value::timestamp >= v_max_range THEN suffix_timestamp := v_max_range; LOOP -- Keep incrementing higher until given value is below the upper range suffix_timestamp := suffix_timestamp + v_partition_interval::interval; IF p_value::timestamp < suffix_timestamp THEN -- Have to subtract one interval because the value would actually be in the partition previous -- to this partition timestamp since the partition names contain the lower boundary suffix_timestamp := suffix_timestamp - v_partition_interval::interval; EXIT; END IF; END LOOP; ELSIF p_value::timestamp < v_min_range THEN suffix_timestamp := v_min_range; LOOP -- Keep decrementing lower until given value is below or equal to the lower range suffix_timestamp := suffix_timestamp - v_partition_interval::interval; IF p_value::timestamp >= suffix_timestamp THEN EXIT; END IF; END LOOP; ELSE RAISE EXCEPTION 'Unable to determine a valid child table for the given parent table and value'; END IF; partition_table := v_parent_schema||'.'||@extschema@.check_name_length(v_parent_tablename, to_char(suffix_timestamp, v_datetime_string), TRUE); END IF; END IF; SELECT tablename INTO v_child_exists FROM pg_catalog.pg_tables WHERE schemaname = split_part(partition_table, '.', 1) AND tablename = split_part(partition_table, '.', 2); IF v_child_exists IS NOT NULL THEN table_exists := true; ELSE table_exists := false; END IF; RETURN; END $$; -- Restore dropped object privileges DO $$ DECLARE v_row record; BEGIN FOR v_row IN SELECT statement FROM partman_preserve_privs_temp LOOP IF v_row.statement IS NOT NULL THEN EXECUTE v_row.statement; END IF; END LOOP; END $$; DROP TABLE IF EXISTS partman_preserve_privs_temp; pg_partman-4.6.0/updates/pg_partman--2.3.2--2.3.3.sql000066400000000000000000006715101412761706100214410ustar00rootroot00000000000000-- Fixed bug in partition_data_time() that was causing it to fail with custom time partition sets. This bug was introduced in v2.3.2. -- New constraint on partman config tables does not allow constraint_cols column to contain the control column. If you get this constraint volation during extension update, please review your part_config (and part_config_sub) table for this condition. You also likely have additional, unnecessary constraints on your child partitions that should be reviewed and removed. (Github Issue #100). -- Further optimizations for databases with many tables and large partition sets. Thanks to https://github.com/Crack for the contributions here (Github Issue #107). -- If an error is encountered when the partition trigger is run, pg_partman's trigger function now catches the exception and writes the row to the parent instead of just throwing an error. Reduces chance of losing data when encountering issues. Gives a WARNING in the log when this happens along with the original error that caused the issue. Thanks to David Turon for this suggestion (https://github.com/dturon/pg_partman/blob/lbox-trigger-support/sql/functions/create_function_id.sql#L192). -- Note this feature will not go into affect immediately upon extension update. It requires recreating the trigger function, which occurs when a new child table is created. You can force this by running create_function_id(parent_table) or create_function_time(parent_table). -- Ensure the control column cannot be one of the additional constraint columns. ALTER TABLE @extschema@.part_config ADD CONSTRAINT control_constraint_col_chk CHECK ((constraint_cols @> ARRAY[control]) <> true); ALTER TABLE @extschema@.part_config_sub ADD CONSTRAINT control_constraint_col_chk CHECK ((sub_constraint_cols @> ARRAY[sub_control]) <> true); /* * Function to manage pre-creation of the next partitions in a set. * Also manages dropping old partitions if the retention option is set. * If p_parent_table is passed, will only run run_maintenance() on that one table (no matter what the configuration table may have set for it) * Otherwise, will run on all tables in the config table with p_run_maintenance() set to true. * For large partition sets, running analyze can cause maintenance to take longer than expected. Can set p_analyze to false to avoid a forced analyze run. * Be aware that constraint exclusion may not work properly until an analyze on the partition set is run. */ CREATE OR REPLACE FUNCTION run_maintenance(p_parent_table text DEFAULT NULL, p_analyze boolean DEFAULT true, p_jobmon boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_check_subpart int; v_create_count int := 0; v_current_partition text; v_current_partition_id bigint; v_current_partition_timestamp timestamp; v_datetime_string text; v_drop_count int := 0; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_created boolean; v_last_partition_id bigint; v_last_partition_timestamp timestamp; v_max_id_parent bigint; v_max_time_parent timestamp; v_next_partition_id bigint; v_next_partition_timestamp timestamp; v_parent_schema text; v_parent_tablename text; v_premade_count int; v_premake_id_max bigint; v_premake_id_min bigint; v_premake_timestamp_min timestamp; v_premake_timestamp_max timestamp; v_row record; v_row_max_id record; v_row_max_time record; v_row_sub record; v_skip_maint boolean; v_step_id bigint; v_step_overflow_id bigint; v_step_serial_id bigint; v_sub_id_max bigint; v_sub_id_max_suffix bigint; v_sub_id_min bigint; v_sub_parent text; v_sub_timestamp_max timestamp; v_sub_timestamp_max_suffix timestamp; v_sub_timestamp_min timestamp; v_tablename text; v_tables_list_sql text; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman maintenance already running.'; RETURN; END IF; 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' AND e.extnamespace = n.oid; END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.add_job(%L)', v_jobmon_schema, 'PARTMAN RUN MAINTENANCE') INTO v_job_id; EXECUTE format('SELECT %I.add_step(%L, %L)', v_jobmon_schema, v_job_id, 'Running maintenance loop') INTO v_step_id; END IF; v_row := NULL; -- Ensure it's reset v_tables_list_sql := 'SELECT parent_table , partition_type , partition_interval , control , premake , datetime_string , undo_in_progress , sub_partition_set_full , epoch , infinite_time_partitions FROM @extschema@.part_config WHERE sub_partition_set_full = false'; IF p_parent_table IS NULL THEN v_tables_list_sql := v_tables_list_sql || ' AND use_run_maintenance = true'; ELSE v_tables_list_sql := v_tables_list_sql || format(' AND parent_table = %L', p_parent_table); END IF; FOR v_row IN EXECUTE v_tables_list_sql LOOP CONTINUE WHEN v_row.undo_in_progress; v_skip_maint := true; -- reset every loop -- Check for consistent data in part_config_sub table. Was unable to get this working properly as either a constraint or trigger. -- Would either delay raising an error until the next write (which I cannot predict) or disallow future edits to update a sub-partition set's configuration. -- This way at least provides a consistent way to check that I know will run. If anyone can get a working constraint/trigger, please help! -- Don't have to worry about this in the serial trigger maintenance since subpartitioning requires run_maintenance(). SELECT sub_parent INTO v_sub_parent FROM @extschema@.part_config_sub WHERE sub_parent = v_row.parent_table; IF v_sub_parent IS NOT NULL THEN SELECT count(*) INTO v_check_subpart FROM @extschema@.check_subpart_sameconfig(v_row.parent_table); IF v_check_subpart > 1 THEN RAISE EXCEPTION 'Inconsistent data in part_config_sub table. Sub-partition tables that are themselves sub-partitions cannot have differing configuration values among their siblings. Run this query: "SELECT * FROM @extschema@.check_subpart_sameconfig(''%'');" This should only return a single row or nothing. If multiple rows are returned, results are all children of the given parent. Update the differing values to be consistent for your desired values.', v_row.sub_parent; END IF; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(v_row.parent_table, '.', 1)::name AND tablename = split_part(v_row.parent_table, '.', 2)::name; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LIMIT 1; IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_last_partition: %', v_row.parent_table, v_last_partition; END IF; IF v_row.partition_type = 'time' OR v_row.partition_type = 'time-custom' THEN SELECT child_start_time INTO v_last_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_row.partition_interval, v_row.parent_table); -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_time IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP IF v_row.epoch = false THEN EXECUTE format('SELECT max(%I)::text FROM %I.%I' , v_row.control , v_row_max_time.partition_schemaname , v_row_max_time.partition_tablename ) INTO v_current_partition_timestamp; ELSE EXECUTE format('SELECT to_timestamp(max(%I))::text FROM %I.%I' , v_row.control , v_row_max_time.partition_schemaname , v_row_max_time.partition_tablename ) INTO v_current_partition_timestamp; END IF; IF v_current_partition_timestamp IS NOT NULL THEN SELECT suffix_timestamp INTO v_current_partition_timestamp FROM @extschema@.show_partition_name(v_row.parent_table, v_current_partition_timestamp::text); EXIT; END IF; END LOOP; -- Check for values in the parent table. If they are there and greater than all child values, use that instead -- This allows maintenance to continue working properly if there is a large gap in data insertion. Data will remain in parent, but new tables will be created IF v_row.epoch = false THEN EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_row.control, v_parent_schema, v_parent_tablename) INTO v_max_time_parent; ELSE EXECUTE format('SELECT to_timestamp(max(%I)) FROM ONLY %I.%I', v_row.control, v_parent_schema, v_parent_tablename) INTO v_max_time_parent; END IF; IF p_debug THEN RAISE NOTICE 'run_maint: v_current_partition_timestamp: %, v_max_time_parent: %', v_current_partition_timestamp, v_max_time_parent; END IF; IF v_max_time_parent > v_current_partition_timestamp THEN SELECT suffix_timestamp INTO v_current_partition_timestamp FROM @extschema@.show_partition_name(v_row.parent_table, v_max_time_parent::text); END IF; IF v_current_partition_timestamp IS NULL THEN -- Partition set is completely empty IF v_row.infinite_time_partitions IS TRUE THEN -- Set it to now so new partitions continue to be created v_current_partition_timestamp = CURRENT_TIMESTAMP; ELSE -- Nothing to do CONTINUE; END IF; END IF; -- If this is a subpartition, determine if the last child table has been made. If so, mark it as full so future maintenance runs can skip it SELECT sub_min::timestamp, sub_max::timestamp INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'time'); IF v_sub_timestamp_max IS NOT NULL THEN SELECT suffix_timestamp INTO v_sub_timestamp_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_timestamp_max::text); IF v_sub_timestamp_max_suffix = v_last_partition_timestamp THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; -- Check and see how many premade partitions there are. v_premade_count = round(EXTRACT('epoch' FROM age(v_last_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); v_next_partition_timestamp := v_last_partition_timestamp; IF p_debug THEN RAISE NOTICE 'run_maint before loop: current_partition_timestamp: %, v_premade_count: %, v_sub_timestamp_min: %, v_sub_timestamp_max: %' , v_current_partition_timestamp , v_premade_count , v_sub_timestamp_min , v_sub_timestamp_max; END IF; -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed WHILE (v_premade_count < v_row.premake) LOOP IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_premade_count: %, v_next_partition_timestamp: %', v_row.parent_table, v_premade_count, v_next_partition_timestamp; END IF; IF v_next_partition_timestamp < v_sub_timestamp_min OR v_next_partition_timestamp > v_sub_timestamp_max THEN -- With subpartitioning, no need to run if the timestamp is not in the parent table's range EXIT; END IF; BEGIN v_next_partition_timestamp := v_next_partition_timestamp + v_row.partition_interval::interval; EXCEPTION WHEN datetime_field_overflow THEN v_premade_count := v_row.premake; -- do this so it can exit the premake check loop and continue in the outer for loop IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.add_step(%L, %L)', v_jobmon_schema, v_job_id, 'Attempted partition time interval is outside PostgreSQL''s supported time range.') INTO v_step_overflow_id; EXECUTE format('SELECT %I.update_step(%L, %L, %L)', v_jobmon_schema, v_step_overflow_id, 'CRITICAL', 'Child partition creation skippd for parent table '||v_partition_time); END IF; RAISE WARNING 'Attempted partition time interval is outside PostgreSQL''s supported time range. Child partition creation skipped for parent table %', v_row.parent_table; CONTINUE; END; v_last_partition_created := @extschema@.create_partition_time(v_row.parent_table, ARRAY[v_next_partition_timestamp], p_analyze); IF v_last_partition_created THEN v_create_count := v_create_count + 1; PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); END IF; v_premade_count = round(EXTRACT('epoch' FROM age(v_next_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); END LOOP; ELSIF v_row.partition_type = 'id' THEN -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%I)::text FROM %I.%I' , v_row.control , v_row_max_id.partition_schemaname , v_row_max_id.partition_tablename) INTO v_current_partition_id; IF v_current_partition_id IS NOT NULL THEN SELECT suffix_id INTO v_current_partition_id FROM @extschema@.show_partition_name(v_row.parent_table, v_current_partition_id::text); EXIT; END IF; END LOOP; -- Check for values in the parent table. If they are there and greater than all child values, use that instead -- This allows maintenance to continue working properly if there is a large gap in data insertion. Data will remain in parent, but new tables will be created EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_row.control, v_parent_schema, v_parent_tablename) INTO v_max_id_parent; IF v_max_id_parent > v_current_partition_id THEN SELECT suffix_id INTO v_current_partition_id FROM @extschema@.show_partition_name(v_row.parent_table, v_max_id_parent::text); END IF; IF v_current_partition_id IS NULL THEN -- Partition set is completely empty. Nothing to do CONTINUE; END IF; SELECT child_start_id INTO v_last_partition_id FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_row.partition_interval, v_row.parent_table); -- Determine if this table is a child of a subpartition parent. If so, get limits to see if run_maintenance even needs to run for it. SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'id'); IF v_sub_id_max IS NOT NULL THEN SELECT suffix_id INTO v_sub_id_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_id_max::text); IF v_sub_id_max_suffix = v_last_partition_id THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; v_next_partition_id := v_last_partition_id; v_premade_count := ((v_last_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed. WHILE (v_premade_count < v_row.premake) LOOP IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_premade_count: %, v_next_partition_id: %', v_row.parent_table, v_premade_count, v_next_partition_id; END IF; IF v_next_partition_id < v_sub_id_min OR v_next_partition_id > v_sub_id_max THEN -- With subpartitioning, no need to run if the id is not in the parent table's range EXIT; END IF; v_next_partition_id := v_next_partition_id + v_row.partition_interval::bigint; v_last_partition_created := @extschema@.create_partition_id(v_row.parent_table, ARRAY[v_next_partition_id], p_analyze); IF v_last_partition_created THEN v_create_count := v_create_count + 1; PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); END IF; v_premade_count := ((v_next_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); END LOOP; END IF; -- end main IF check for time or id END LOOP; -- end of creation loop -- Manage dropping old partitions if retention option is set FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND (partition_type = 'time' OR partition_type = 'time-custom') LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); END IF; END IF; IF v_drop_count > 0 THEN PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); END IF; END LOOP; FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND partition_type = 'id' LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); END IF; END IF; IF v_drop_count > 0 THEN PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.update_step(%L, %L, ''Partition maintenance finished. %s partitions made. %s partitions dropped.'')' , v_jobmon_schema , v_step_id , 'OK' , v_create_count , v_drop_count); IF v_step_overflow_id IS NOT NULL OR v_step_serial_id IS NOT NULL THEN EXECUTE format('SELECT %I.fail_job(%L)', v_jobmon_schema, v_job_id); ELSE EXECUTE format('SELECT %I.close_job(%L)', v_jobmon_schema, v_job_id); END IF; END IF; 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 RUN MAINTENANCE'')', v_jobmon_schema) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Apply constraints managed by partman extension */ CREATE OR REPLACE FUNCTION apply_constraints(p_parent_table text, p_child_table text DEFAULT NULL, p_analyze boolean DEFAULT FALSE, p_job_id bigint DEFAULT NULL, p_debug boolean DEFAULT FALSE) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_child_exists text; v_child_tablename text; v_col text; v_constraint_cols text[]; v_constraint_col_type text; v_constraint_name text; v_constraint_values record; v_control text; v_datetime_string text; v_existing_constraint_name text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_id bigint; v_last_partition_timestamp timestamp; v_max_id bigint; v_max_timestamp timestamp; v_old_search_path text; v_optimize_constraint int; v_parent_schema text; v_parent_table text; v_parent_tablename text; v_partition_interval text; v_partition_suffix text; v_premake int; v_sql text; v_step_id bigint; v_suffix_position int; v_type text; BEGIN SELECT parent_table , partition_type , control , premake , partition_interval , optimize_constraint , datetime_string , constraint_cols , jobmon INTO v_parent_table , v_type , v_control , v_premake , v_partition_interval , v_optimize_constraint , v_datetime_string , v_constraint_cols , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND constraint_cols IS NOT NULL; IF v_constraint_cols IS NULL THEN IF p_debug THEN RAISE NOTICE 'Given parent table (%) not set up for constraint management (constraint_cols is NULL)', p_parent_table; END IF; -- Returns silently to allow this function to be simply called by maintenance processes without having to check if config options are set. RETURN; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(v_parent_table, '.', 1)::name AND tablename = split_part(v_parent_table, '.', 2)::name; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE CONSTRAINT: %s', v_parent_table)); ELSE v_job_id = p_job_id; END IF; END IF; -- If p_child_table is null, figure out the partition that is the one right before the optimize_constraint value backwards. IF p_child_table IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying additional constraints: Automatically determining most recent child on which to apply constraints'); END IF; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(v_parent_table, 'DESC') LIMIT 1; IF v_type IN ('time', 'time-custom') THEN SELECT child_start_time INTO v_last_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_partition_interval, v_parent_table); v_partition_suffix := to_char(v_last_partition_timestamp - (v_partition_interval::interval * (v_optimize_constraint + v_premake + 1) ), v_datetime_string); ELSIF v_type = 'id' THEN SELECT child_start_id INTO v_last_partition_id FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_partition_interval, v_parent_table); v_partition_suffix := (v_last_partition_id - (v_partition_interval::int * (v_optimize_constraint + v_premake + 1) ))::text; END IF; v_child_tablename := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); IF p_debug THEN RAISE NOTICE 'apply_constraint: v_parent_tablename: % , v_partition_suffix: %', v_parent_tablename, v_partition_suffix; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Target child table: %s.%s', v_parent_schema, v_child_tablename)); END IF; ELSE v_child_tablename = split_part(p_child_table, '.', 2); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying additional constraints: Checking if target child table exists'); END IF; SELECT tablename FROM pg_catalog.pg_tables INTO v_child_exists WHERE schemaname = v_parent_schema::name AND tablename = v_child_tablename::name; IF v_child_exists IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Target child table (%s) does not exist. Skipping constraint creation.', v_child_tablename)); IF p_job_id IS NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; IF p_debug THEN RAISE NOTICE 'Target child table (%) does not exist. Skipping constraint creation.', v_child_tablename; END IF; RETURN; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; FOREACH v_col IN ARRAY v_constraint_cols LOOP SELECT con.conname INTO v_existing_constraint_name FROM pg_catalog.pg_constraint con JOIN pg_class c ON c.oid = con.conrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN pg_catalog.pg_attribute a ON con.conrelid = a.attrelid WHERE c.relname = v_child_tablename AND n.nspname = v_parent_schema AND con.conname LIKE 'partmanconstr_%' AND con.contype = 'c' AND a.attname = v_col AND ARRAY[a.attnum] OPERATOR(pg_catalog.<@) con.conkey AND a.attisdropped = false; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Applying additional constraints: Applying new constraint on column: %s', v_col)); END IF; IF v_existing_constraint_name IS NOT NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Partman managed constraint already exists on this table (%s) and column (%s). Skipping creation.', v_child_tablename, v_col)); END IF; IF p_debug THEN RAISE NOTICE 'Partman managed constraint already exists on this table (%) and column (%). Skipping creation.', v_child_tablename, v_col ; END IF; CONTINUE; END IF; -- Ensure column name gets put on end of constraint name to help avoid naming conflicts v_constraint_name := @extschema@.check_name_length('partmanconstr_'||v_child_tablename, p_suffix := '_'||v_col); EXECUTE format('SELECT min(%I)::text AS min, max(%I)::text AS max FROM %I.%I', v_col, v_col, v_parent_schema, v_child_tablename) INTO v_constraint_values; IF v_constraint_values IS NOT NULL THEN v_sql := format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I <= %L)' , v_parent_schema , v_child_tablename , v_constraint_name , v_col , v_constraint_values.min , v_col , v_constraint_values.max); IF p_debug THEN RAISE NOTICE 'Constraint creation query: %', v_sql; END IF; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('New constraint created: %s', v_sql)); END IF; ELSE IF p_debug THEN RAISE NOTICE 'Given column (%) contains all NULLs. No constraint created', v_col; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Given column (%s) contains all NULLs. No constraint created', v_col)); END IF; END IF; END LOOP; IF p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Applying additional constraints: Running analyze on partition set: %s', v_parent_table)); END IF; IF p_debug THEN RAISE NOTICE 'Running analyze on partition set: %', v_parent_table; END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; 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 CONSTRAINT: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Apply foreign keys that exist on the given parent to the given child table */ CREATE OR REPLACE FUNCTION apply_foreign_keys(p_parent_table text, p_child_table text, p_job_id bigint DEFAULT NULL, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_count int := 0; v_job_id bigint; v_jobmon text; v_jobmon_schema text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_ref_schema text; v_ref_table text; v_row record; v_schemaname text; v_sql text; v_step_id bigint; v_tablename text; BEGIN SELECT jobmon INTO v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN APPLYING FOREIGN KEYS: %s', p_parent_table)); ELSE -- Don't create a new job, add steps into given job v_job_id := p_job_id; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Applying foreign keys to %s if they exist on parent', p_child_table)); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; SELECT schemaname, tablename INTO v_schemaname, v_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_child_table, '.', 1)::name AND tablename = split_part(p_child_table, '.', 2)::name; IF v_tablename IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'CRITICAL', format('Target child table (%s) does not exist.', p_child_table)); PERFORM fail_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RAISE EXCEPTION 'Target child table (%) does not exist.', p_child_table; RETURN; END IF; FOR v_row 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 JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_parent_tablename AND n.nspname = v_parent_schema AND contype = 'f' LOOP v_sql := format('ALTER TABLE %I.%I ADD %s' , v_schemaname , v_tablename , v_row.constraint_def); IF p_debug THEN RAISE NOTICE 'Constraint creation query: %', v_sql; END IF; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'FK applied'); END IF; v_count := v_count + 1; END LOOP; IF v_count = 0 AND v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'No FKs found on parent'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; 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 APPLYING FOREIGN KEYS: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Apply privileges that exist on a given parent to the given child table */ CREATE OR REPLACE FUNCTION apply_privileges(p_parent_schema text, p_parent_tablename text, p_child_schema text, p_child_tablename text, p_job_id bigint DEFAULT NULL) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_child_grant record; v_child_owner text; v_grantees text[]; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_match boolean; v_parent_grant record; v_parent_owner text; v_revoke text; v_row_revoke record; v_sql text; v_step_id bigint; BEGIN SELECT jobmon INTO v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_schema ||'.'|| p_parent_tablename; IF v_jobmon IS NULL THEN RAISE EXCEPTION 'Given table is not managed by this extention: %.%', p_parent_schema, p_parent_tablename; END IF; SELECT tableowner INTO v_parent_owner FROM pg_catalog.pg_tables WHERE schemaname = p_parent_schema::name AND tablename = p_parent_tablename::name; SELECT tableowner INTO v_child_owner FROM pg_catalog.pg_tables WHERE schemaname = p_child_schema::name AND tablename = p_child_tablename::name; IF v_parent_owner IS NULL THEN RAISE EXCEPTION 'Given parent table does not exist: %.%', v_parent_schema, v_parent_tablename; END IF; IF v_child_owner IS NULL THEN RAISE EXCEPTION 'Given child table does not exist: %.%', v_child_schema, v_child_tablename; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; END IF; IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN EXECUTE format('SELECT %I.add_job(%L)', v_jobmon_schema, format('PARTMAN APPLYING PRIVILEGES TO CHILD TABLE: %s.%s', p_child_schema, p_child_tablename)) INTO v_job_id; ELSE v_job_id := p_job_id; END IF; EXECUTE format('SELECT %I.add_step(%L, %L)', v_jobmon_schema, v_job_id, format('Setting new child table privileges for %s.%s', p_child_schema, p_child_tablename)) INTO v_step_id; END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.update_step(%L, %L, %L)' , v_jobmon_schema , v_step_id , 'PENDING' , format('Applying privileges on child partition: %s.%s' , p_child_schema , p_child_tablename) ); END IF; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types , grantee FROM information_schema.table_privileges WHERE table_schema = p_parent_schema AND table_name = p_parent_tablename GROUP BY grantee LOOP -- Compare parent & child grants. Don't re-apply if it already exists v_match := false; v_sql := NULL; FOR v_child_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types , grantee FROM information_schema.table_privileges WHERE table_schema = p_child_schema AND table_name = p_child_tablename GROUP BY grantee LOOP IF v_parent_grant.types = v_child_grant.types AND v_parent_grant.grantee = v_child_grant.grantee THEN v_match := true; END IF; END LOOP; IF v_match = false THEN IF v_parent_grant.grantee = 'PUBLIC' THEN v_sql := 'GRANT %s ON %I.%I TO %s'; ELSE v_sql := 'GRANT %s ON %I.%I TO %I'; END IF; EXECUTE format(v_sql , array_to_string(v_parent_grant.types, ',') , p_child_schema , p_child_tablename , v_parent_grant.grantee); v_sql := NULL; SELECT string_agg(r, ',') INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN IF v_parent_grant.grantee = 'PUBLIC' THEN v_sql := 'REVOKE %s ON %I.%I FROM %s CASCADE'; ELSE v_sql := 'REVOKE %s ON %I.%I FROM %I CASCADE'; END IF; EXECUTE format(v_sql , v_revoke , p_child_schema , p_child_tablename , v_parent_grant.grantee); v_sql := NULL; END IF; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN FOR v_row_revoke IN SELECT role FROM ( SELECT DISTINCT grantee::text AS role FROM information_schema.table_privileges WHERE table_schema = p_child_schema AND table_name = p_child_tablename EXCEPT SELECT unnest(v_grantees)) x LOOP IF v_row_revoke.role IS NOT NULL THEN IF v_row_revoke.role = 'PUBLIC' THEN v_sql := 'REVOKE ALL ON %I.%I FROM %s'; ELSE v_sql := 'REVOKE ALL ON %I.%I FROM %I'; END IF; EXECUTE format(v_sql , p_child_schema , p_child_tablename , v_row_revoke.role); END IF; END LOOP; END IF; IF v_parent_owner <> v_child_owner THEN EXECUTE format('ALTER TABLE %I.%I OWNER TO %I' , p_child_schema , p_child_tablename , v_parent_owner); END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.update_step(%L, %L, %L)', v_jobmon_schema, v_step_id, 'OK', 'Done'); IF p_job_id IS NULL THEN EXECUTE format('SELECT %I.close_job(%L)', v_jobmon_schema, v_job_id); END IF; END IF; 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 RE-APPLYING PRIVILEGES TO ALL CHILD TABLES OF: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to monitor for data getting inserted into parent tables managed by extension */ CREATE OR REPLACE FUNCTION check_parent() RETURNS SETOF @extschema@.check_parent_table LANGUAGE plpgsql STABLE SECURITY DEFINER AS $$ DECLARE v_count bigint = 0; v_row record; v_schemaname text; v_tablename text; v_sql text; v_trouble @extschema@.check_parent_table%rowtype; BEGIN FOR v_row IN SELECT DISTINCT parent_table FROM @extschema@.part_config LOOP SELECT schemaname, tablename INTO v_schemaname, v_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(v_row.parent_table, '.', 1)::name AND tablename = split_part(v_row.parent_table, '.', 2)::name; v_sql := format('SELECT count(1) AS n FROM ONLY %I.%I', v_schemaname, v_tablename); EXECUTE v_sql INTO v_count; IF v_count > 0 THEN v_trouble.parent_table := v_schemaname ||'.'|| v_tablename; v_trouble.count := v_count; RETURN NEXT v_trouble; END IF; v_count := 0; END LOOP; RETURN; END $$; /* * Check if parent table is a subpartition of an already existing partition set managed by pg_partman * If so, return the limits of what child tables can be created under the given parent table based on its own suffix */ CREATE OR REPLACE FUNCTION check_subpartition_limits(p_parent_table text, p_type text, OUT sub_min text, OUT sub_max text) RETURNS record LANGUAGE plpgsql AS $$ DECLARE v_datetime_string text; v_id_position int; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_quarter text; v_sub_id_max bigint; v_sub_id_min bigint; v_sub_timestamp_max timestamp; v_sub_timestamp_min timestamp; v_time_position int; v_top_datetime_string text; v_top_interval text; v_top_parent text; v_top_type text; v_year text; BEGIN SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- CTE query is done individually for each type (time, id) because it should return NULL if the top parent is not the same type in a subpartition set (id->time or time->id) IF p_type = 'id' THEN WITH top_oid AS ( SELECT i.inhparent AS top_parent_oid FROM pg_catalog.pg_class c JOIN pg_catalog.pg_inherits i ON c.oid = i.inhrelid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = v_parent_schema AND c.relname = v_parent_tablename ) SELECT n.nspname||'.'||c.relname, p.datetime_string, p.partition_interval, p.partition_type INTO v_top_parent, v_top_datetime_string, v_top_interval, v_top_type FROM pg_catalog.pg_class c JOIN top_oid t ON c.oid = t.top_parent_oid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace JOIN @extschema@.part_config p ON p.parent_table = n.nspname||'.'||c.relname WHERE c.oid = t.top_parent_oid AND p.partition_type = 'id'; IF v_top_parent IS NOT NULL THEN SELECT child_start_id::text, child_end_id::text INTO sub_min, sub_max FROM @extschema@.show_partition_info(p_parent_table, v_top_interval, v_top_parent); END IF; ELSIF p_type = 'time' THEN WITH top_oid AS ( SELECT i.inhparent AS top_parent_oid FROM pg_catalog.pg_class c JOIN pg_catalog.pg_inherits i ON c.oid = i.inhrelid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = v_parent_schema AND c.relname = v_parent_tablename ) SELECT n.nspname||'.'||c.relname, p.datetime_string, p.partition_interval, p.partition_type INTO v_top_parent, v_top_datetime_string, v_top_interval, v_top_type FROM pg_catalog.pg_class c JOIN top_oid t ON c.oid = t.top_parent_oid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace JOIN @extschema@.part_config p ON p.parent_table = n.nspname||'.'||c.relname WHERE c.oid = t.top_parent_oid AND p.partition_type = 'time' OR p.partition_type = 'time-custom'; IF v_top_parent IS NOT NULL THEN SELECT child_start_time::text, child_end_time::text INTO sub_min, sub_max FROM @extschema@.show_partition_info(p_parent_table, v_top_interval, v_top_parent); END IF; ELSE RAISE EXCEPTION 'Invalid type given as parameter to check_subpartition_limits()'; END IF; RETURN; END $$; /* * Create the trigger function for the parent table of an id-based partition set */ CREATE OR REPLACE FUNCTION create_function_id(p_parent_table text, p_job_id bigint DEFAULT NULL) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_control text; v_count int; v_current_partition_name text; v_current_partition_id bigint; v_datetime_string text; v_final_partition_id bigint; v_function_name 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_position int; v_job_id bigint; v_jobmon text; v_jobmon_schema text; v_last_partition text; v_max bigint; v_next_partition_id bigint; v_next_partition_name text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_premake int; v_prev_partition_id bigint; v_prev_partition_name text; v_row_max_id record; v_run_maint boolean; v_step_id bigint; v_top_parent text := p_parent_table; v_trig_func text; v_optimize_trigger int; BEGIN SELECT partition_interval::bigint , control , premake , optimize_trigger , use_run_maintenance , jobmon INTO v_partition_interval , v_control , v_premake , v_optimize_trigger , v_run_maint , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE FUNCTION: %s', p_parent_table)); ELSE v_job_id = p_job_id; END IF; v_step_id := add_step(v_job_id, format('Creating partition function for table %s', p_parent_table)); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); -- Get the highest level top parent if multi-level partitioned in order to get proper max() value below 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 AND c.relname = v_higher_parent_table ) SELECT n.nspname, c.relname INTO v_higher_parent_schema, v_higher_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 WHERE p.partition_type = 'id'; IF v_higher_parent_table IS NOT NULL THEN -- initially set in DECLARE v_top_parent := v_higher_parent_schema||'.'||v_higher_parent_table; END IF; END LOOP; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_top_parent, 'DESC') LOOP EXECUTE format('SELECT max(%I) FROM %I.%I', v_control, v_row_max_id.partition_schemaname, v_row_max_id.partition_tablename) INTO v_max; IF v_max IS NOT NULL THEN EXIT; END IF; END LOOP; IF v_max IS NULL THEN v_max := 0; END IF; v_current_partition_id = v_max - (v_max % v_partition_interval); v_next_partition_id := v_current_partition_id + v_partition_interval; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_current_partition_id::text, TRUE); v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_current_partition_id bigint; v_current_partition_name text; v_id_position int; v_last_partition text := %L; v_next_partition_id bigint; v_next_partition_name text; v_partition_created boolean; BEGIN IF TG_OP = ''INSERT'' THEN IF NEW.%I >= %s AND NEW.%I < %s THEN ' , v_parent_schema , v_function_name , v_last_partition , v_control , v_current_partition_id , v_control , v_next_partition_id ); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_current_partition_name::name; IF v_count > 0 THEN v_trig_func := v_trig_func || format(' INSERT INTO %I.%I VALUES (NEW.*); ', v_parent_schema, v_current_partition_name); ELSE v_trig_func := v_trig_func || ' -- Child table for current values does not exist in this partition set, so write to parent RETURN NEW;'; END IF; FOR i IN 1..v_optimize_trigger LOOP v_prev_partition_id := v_current_partition_id - (v_partition_interval * i); v_next_partition_id := v_current_partition_id + (v_partition_interval * i); v_final_partition_id := v_next_partition_id + v_partition_interval; v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, v_prev_partition_id::text, TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, v_next_partition_id::text, TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles optimize_trigger being larger than premake (to go back in time further) and edge case of changing optimize_trigger immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_prev_partition_name::name; IF v_count > 0 THEN -- Only handle previous partitions if they're starting above zero IF v_prev_partition_id >= 0 THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %s AND NEW.%I < %s THEN INSERT INTO %I.%I VALUES (NEW.*); ' , v_control , v_prev_partition_id , v_control , v_prev_partition_id + v_partition_interval , v_parent_schema , v_prev_partition_name ); END IF; END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_next_partition_name::name; IF v_count > 0 THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %s AND NEW.%I < %s THEN INSERT INTO %I.%I VALUES (NEW.*);' , v_control , v_next_partition_id , v_control , v_final_partition_id , v_parent_schema , v_next_partition_name ); END IF; END LOOP; v_trig_func := v_trig_func ||format(' ELSE v_current_partition_id := NEW.%I - (NEW.%I %% %s); v_current_partition_name := @extschema@.check_name_length(%L, v_current_partition_id::text, TRUE); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = %L::name AND tablename = v_current_partition_name::name; IF v_count > 0 THEN EXECUTE format(''INSERT INTO %%I.%%I VALUES($1.*)'', %L, v_current_partition_name) USING NEW; ELSE RETURN NEW; END IF; END IF;' , v_control , v_control , v_partition_interval , v_parent_tablename , v_parent_schema , v_parent_schema ); IF v_run_maint IS FALSE THEN v_trig_func := v_trig_func ||format(' v_current_partition_id := NEW.%I - (NEW.%I %% %s); IF (NEW.%I %% %s) > (%s / 2) THEN v_id_position := (length(v_last_partition) - position(''p_'' in reverse(v_last_partition))) + 2; v_next_partition_id := (substring(v_last_partition from v_id_position)::bigint) + %s; WHILE ((v_next_partition_id - v_current_partition_id) / %s) <= %s LOOP v_partition_created := @extschema@.create_partition_id(%L, ARRAY[v_next_partition_id]); IF v_partition_created THEN PERFORM @extschema@.create_function_id(%L); PERFORM @extschema@.apply_constraints(%L); END IF; v_next_partition_id := v_next_partition_id + %s; END LOOP; END IF;' , v_control , v_control , v_partition_interval , v_control , v_partition_interval , v_partition_interval , v_partition_interval , v_partition_interval , v_premake , p_parent_table , p_parent_table , p_parent_table , v_partition_interval ); END IF; v_trig_func := v_trig_func ||' END IF; RETURN NULL; EXCEPTION WHEN OTHERS THEN RAISE WARNING ''pg_partman insert into child table failed, row inserted into parent (%.%). ERROR: %'', TG_TABLE_SCHEMA, TG_TABLE_NAME, COALESCE(SQLERRM, ''unknown''); RETURN NEW; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for current id interval: %s to %s', v_current_partition_id, v_final_partition_id-1)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; 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 FUNCTION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''Partition function maintenance for table %s 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 $$; /* * Create the trigger function for the parent table of a time-based partition set */ CREATE OR REPLACE FUNCTION create_function_time(p_parent_table text, p_job_id bigint DEFAULT NULL) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_control text; v_count int; v_current_partition_name text; v_current_partition_timestamp timestamptz; v_datetime_string text; v_epoch boolean; v_final_partition_timestamp timestamptz; v_function_name text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_new_length int; v_next_partition_name text; v_next_partition_timestamp timestamptz; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_prev_partition_name text; v_prev_partition_timestamp timestamptz; v_step_id bigint; v_trig_func text; v_optimize_trigger int; v_type text; BEGIN SELECT partition_type , partition_interval::interval , epoch , control , optimize_trigger , datetime_string , jobmon INTO v_type , v_partition_interval , v_epoch , v_control , v_optimize_trigger , v_datetime_string , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE FUNCTION: %s', p_parent_table)); ELSE v_job_id = p_job_id; END IF; v_step_id := add_step(v_job_id, format('Creating partition function for table %s', p_parent_table)); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); IF v_type = 'time' THEN v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_partition_name text; v_partition_timestamp timestamptz; BEGIN IF TG_OP = ''INSERT'' THEN ' , v_parent_schema , v_function_name); IF v_epoch = false THEN CASE WHEN v_partition_interval = '15 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', NEW.%I) + ''15min''::interval * floor(date_part(''minute'', NEW.%I) / 15.0);' , v_control , v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', NEW.%I) + ''30min''::interval * floor(date_part(''minute'', NEW.%I) / 30.0);' , v_control , v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 day' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''day'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 week' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''week'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 month' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''month'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_partition_interval = '3 months' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''quarter'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 year' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''year'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; ELSE -- epoch is true CASE WHEN v_partition_interval = '15 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', to_timestamp(NEW.%I)) + ''15min''::interval * floor(date_part(''minute'', NEW.%I) / 15.0);' , v_control , v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', to_timestamp(NEW.%I)) + ''30min''::interval * floor(date_part(''minute'', NEW.%I) / 30.0);' , v_control , v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', to_timestamp(NEW.%I));', v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 day' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''day'', to_timestamp(NEW.%I));', v_control); v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 week' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''week'', to_timestamp(NEW.%I));', v_control); v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 month' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''month'', to_timestamp(NEW.%I));', v_control); v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_partition_interval = '3 months' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''quarter'', to_timestamp(NEW.%I));', v_control); v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 year' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''year'', to_timestamp(NEW.%I));', v_control); v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; END IF; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, to_char(v_current_partition_timestamp, v_datetime_string), TRUE); v_next_partition_timestamp := v_current_partition_timestamp + v_partition_interval::interval; IF v_epoch = false THEN v_trig_func := v_trig_func ||format(' IF NEW.%I >= %L AND NEW.%I < %L THEN ' , v_control , v_current_partition_timestamp , v_control , v_next_partition_timestamp); ELSE v_trig_func := v_trig_func ||format(' IF to_timestamp(NEW.%I) >= %L AND to_timestamp(NEW.%I) < %L THEN ' , v_control , v_current_partition_timestamp , v_control , v_next_partition_timestamp); END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_current_partition_name::name; IF v_count > 0 THEN v_trig_func := v_trig_func || format(' INSERT INTO %I.%I VALUES (NEW.*); ', v_parent_schema, v_current_partition_name); ELSE v_trig_func := v_trig_func || ' -- Child table for current values does not exist in this partition set, so write to parent RETURN NEW;'; END IF; FOR i IN 1..v_optimize_trigger LOOP v_prev_partition_timestamp := v_current_partition_timestamp - (v_partition_interval::interval * i); v_next_partition_timestamp := v_current_partition_timestamp + (v_partition_interval::interval * i); v_final_partition_timestamp := v_next_partition_timestamp + (v_partition_interval::interval); v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, to_char(v_prev_partition_timestamp, v_datetime_string), TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, to_char(v_next_partition_timestamp, v_datetime_string), TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles optimize_trigger being larger than premake (to go back in time further) and edge case of changing optimize_trigger immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_prev_partition_name::name; IF v_count > 0 THEN IF v_epoch = false THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %L AND NEW.%I < %L THEN INSERT INTO %I.%I VALUES (NEW.*);' , v_control , v_prev_partition_timestamp , v_control , v_prev_partition_timestamp + v_partition_interval::interval , v_parent_schema , v_prev_partition_name); ELSE v_trig_func := v_trig_func ||format(' ELSIF to_timestamp(NEW.%I) >= %L AND to_timestamp(NEW.%I) < %L THEN INSERT INTO %I.%I VALUES (NEW.*);' , v_control , v_prev_partition_timestamp , v_control , v_prev_partition_timestamp + v_partition_interval::interval , v_parent_schema , v_prev_partition_name); END IF; END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_next_partition_name::name; IF v_count > 0 THEN IF v_epoch = false THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %L AND NEW.%I < %L THEN INSERT INTO %I.%I VALUES (NEW.*);' , v_control , v_next_partition_timestamp , v_control , v_final_partition_timestamp , v_parent_schema , v_next_partition_name); ELSE v_trig_func := v_trig_func ||format(' ELSIF to_timestamp(NEW.%I) >= %L AND to_timestamp(NEW.%I) < %L THEN INSERT INTO %I.%I VALUES (NEW.*);' , v_control , v_next_partition_timestamp , v_control , v_final_partition_timestamp , v_parent_schema , v_next_partition_name); END IF; END IF; END LOOP; v_trig_func := v_trig_func||format(' ELSE v_partition_name := @extschema@.check_name_length(%L, to_char(v_partition_timestamp, %L), TRUE); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = %L::name AND tablename = v_partition_name::name; IF v_count > 0 THEN EXECUTE format(''INSERT INTO %%I.%%I VALUES($1.*)'', %L, v_partition_name) USING NEW; ELSE RETURN NEW; END IF; END IF;' , v_parent_tablename , v_datetime_string , v_parent_schema , v_parent_schema); v_trig_func := v_trig_func ||' END IF; RETURN NULL; EXCEPTION WHEN OTHERS THEN RAISE WARNING ''pg_partman insert into child table failed, row inserted into parent (%.%). ERROR: %'', TG_TABLE_SCHEMA, TG_TABLE_NAME, COALESCE(SQLERRM, ''unknown''); RETURN NEW; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for current time interval: %s to %s' , v_current_partition_timestamp , v_final_partition_timestamp-'1sec'::interval)); END IF; ELSIF v_type = 'time-custom' THEN v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_child_schemaname text; v_child_table text; v_child_tablename text; BEGIN ' , v_parent_schema , v_function_name); IF v_epoch = false THEN v_trig_func := v_trig_func || format(' SELECT child_table INTO v_child_table FROM @extschema@.custom_time_partitions WHERE partition_range @> NEW.%I AND parent_table = %L;' , v_control , v_parent_schema||'.'||v_parent_tablename); ELSE -- epoch true v_trig_func := v_trig_func || format(' SELECT child_table INTO v_child_table FROM @extschema@.custom_time_partitions WHERE partition_range @> to_timestamp(NEW.%I) AND parent_table = %L;' , v_control , v_parent_schema||'.'||v_parent_tablename); END IF; v_trig_func := v_trig_func || ' SELECT schemaname, tablename INTO v_child_schemaname, v_child_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(v_child_table, ''.'', 1)::name AND tablename = split_part(v_child_table, ''.'', 2)::name; IF v_child_schemaname IS NOT NULL AND v_child_tablename IS NOT NULL THEN EXECUTE format(''INSERT INTO %I.%I VALUES ($1.*)'', v_child_schemaname, v_child_tablename) USING NEW; ELSE RETURN NEW; END IF; RETURN NULL; EXCEPTION WHEN OTHERS THEN RAISE WARNING ''pg_partman insert into child table failed, row inserted into parent (%.%). ERROR: %'', TG_TABLE_SCHEMA, TG_TABLE_NAME, COALESCE(SQLERRM, ''unknown''); RETURN NEW; END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for custom time table: %s', p_parent_table)); END IF; ELSE RAISE EXCEPTION 'ERROR: Invalid time partitioning type given: %', v_type; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; 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 FUNCTION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''Partition function maintenance for table %s 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 $$; /* * Function to turn a table into the parent of a partition set */ CREATE OR REPLACE FUNCTION 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_use_run_maintenance boolean DEFAULT NULL , p_start_partition text DEFAULT NULL , p_inherit_fk boolean DEFAULT true , p_epoch boolean DEFAULT false , p_jobmon boolean DEFAULT true , p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_base_timestamp timestamp; v_count int := 1; v_datetime_string 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_job_id bigint; v_jobmon_schema text; v_last_partition_created boolean; v_max bigint; v_notnull boolean; v_old_search_path text; v_parent_partition_id bigint; v_parent_partition_timestamp timestamp; v_parent_schema text; v_parent_tablename text; v_partition_time timestamp; v_partition_time_array timestamp[]; v_partition_id_array bigint[]; v_row record; v_run_maint boolean; v_sql text; v_start_time timestamp; v_starting_partition_id bigint; v_step_id bigint; v_step_overflow_id bigint; v_sub_parent text; v_success boolean := false; 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); BEGIN IF position('.' in p_parent_table) = 0 THEN RAISE EXCEPTION 'Parent table must be schema qualified'; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = 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 AND n.nspname = v_parent_schema AND a.attname = p_control; IF 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; IF p_type = 'id' AND p_epoch = true THEN RAISE EXCEPTION 'p_epoch can only be used with time-based partitioning'; END IF; IF NOT @extschema@.check_partition_type(p_type) THEN RAISE EXCEPTION '% is not a valid partitioning type', p_type; END IF; IF p_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF p_use_run_maintenance IS NOT NULL THEN IF p_use_run_maintenance IS FALSE AND (p_type = 'time' OR p_type = 'time-custom') THEN RAISE EXCEPTION 'p_run_maintenance cannot be set to false for time based partitioning'; END IF; v_run_maint := p_use_run_maintenance; ELSIF p_type = 'time' OR p_type = 'time-custom' THEN v_run_maint := TRUE; ELSIF p_type = 'id' THEN v_run_maint := FALSE; ELSE RAISE EXCEPTION 'use_run_maintenance value cannot be set NULL'; END IF; 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 AND n.nspname = v_parent_schema ), sibling_children AS ( SELECT i.inhrelid::regclass::text AS tablename FROM pg_inherits i JOIN parent_table p ON i.inhparent = p.parent_oid ) 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_use_run_maintenance , sub_epoch , sub_optimize_trigger , sub_optimize_constraint , sub_infinite_time_partitions , sub_jobmon 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_use_run_maintenance , sub_epoch , sub_optimize_trigger , sub_optimize_constraint , sub_infinite_time_partitions , sub_jobmon) 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_use_run_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); END LOOP; IF p_type = 'time' OR p_type = 'time-custom' 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 <> 'time-custom' THEN RAISE EXCEPTION 'Must use a predefined time interval if not using type "time-custom". See documentation.'; 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::timestamp, 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))::timestamp; 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 , use_run_maintenance , inherit_fk , jobmon) VALUES ( p_parent_table , p_type , v_time_interval , p_epoch , p_control , p_premake , p_constraint_cols , v_datetime_string , v_run_maint , p_inherit_fk , p_jobmon); 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 AND n.nspname = v_parent_schema ) 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 -- Currently unknown edge case if code gets here RAISE EXCEPTION 'No child tables created. Unexpected edge case encountered. Please report this error to author with conditions that led to it.'; END IF; END 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 p_type = 'id' THEN v_id_interval := p_interval::bigint; IF v_id_interval < 10 THEN RAISE EXCEPTION 'Interval for serial 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 AND c.relname = v_higher_parent_table ) SELECT n.nspname, c.relname INTO v_higher_parent_schema, v_higher_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 WHERE p.partition_type = 'id'; IF v_higher_parent_table IS NOT NULL THEN -- 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 , use_run_maintenance , inherit_fk , jobmon) VALUES ( p_parent_table , p_type , v_id_interval , p_control , p_premake , p_constraint_cols , v_run_maint , p_inherit_fk , p_jobmon); 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 AND n.nspname = v_parent_schema ) 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 WHERE p.partition_type = 'id'; 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 EXCEPTION 'No child tables created. Unexpected edge case encountered. Please report this error to author with conditions that led to it.'; END IF; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating partition function'); END IF; IF p_type = 'time' OR p_type = 'time-custom' 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 p_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); 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; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; 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 $$; /* * Function to create id partitions */ CREATE OR REPLACE FUNCTION create_partition_id(p_parent_table text, p_partition_ids bigint[], p_analyze boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_exists text; v_grantees text[]; v_hasoids boolean; v_id bigint; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_parent_tablespace text; v_partition_interval bigint; v_partition_created boolean := false; v_partition_name text; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_sub_id_max bigint; v_sub_id_min bigint; v_unlogged char; BEGIN SELECT control , partition_interval , inherit_fk , jobmon INTO v_control , v_partition_interval , v_inherit_fk , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'id'); SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; FOREACH v_id IN ARRAY p_partition_ids LOOP -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_id_min IS NOT NULL THEN IF v_id < v_sub_id_min OR v_id > v_sub_id_max THEN CONTINUE; END IF; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_id::text, TRUE); -- If child table already exists, skip creation SELECT tablename INTO v_exists FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_partition_name::name; IF v_exists IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_id||' to '||(v_id + v_partition_interval)-1); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_parent_tablename AND n.nspname = v_parent_schema; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_parent_tablename AND n.nspname = v_parent_schema; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %s AND %I < %s )' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_control , v_id , v_control , v_id + v_partition_interval); EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I', v_parent_schema, v_partition_name, v_parent_schema, v_parent_tablename); PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_use_run_maintenance , sub_infinite_time_partitions , sub_jobmon FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Subpartitioning '||v_partition_name); END IF; 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_use_run_maintenance := %L , p_inherit_fk := %L , p_epoch := %L , p_jobmon := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_use_run_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions WHERE parent_table = v_parent_schema||'.'||v_partition_name; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; -- end sub partitioning LOOP -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table, p_job_id := v_job_id, p_debug := p_debug); v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s', p_parent_table)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; PERFORM close_job(v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RETURN v_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to create a child table in a time-based partition set */ CREATE OR REPLACE FUNCTION create_partition_time(p_parent_table text, p_partition_times timestamp[], p_analyze boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_datetime_string text; v_exists text; v_epoch boolean; v_grantees text[]; v_hasoids boolean; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_partition_created boolean := false; v_partition_name text; v_partition_suffix text; v_parent_tablespace text; v_partition_interval interval; v_partition_timestamp_end timestamp; v_partition_timestamp_start timestamp; v_quarter text; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_sub_timestamp_max timestamp; v_sub_timestamp_min timestamp; v_trunc_value text; v_time timestamp; v_type text; v_unlogged char; v_year text; BEGIN SELECT partition_type , control , partition_interval , epoch , inherit_fk , jobmon , datetime_string INTO v_type , v_control , v_partition_interval , v_epoch , v_inherit_fk , v_jobmon , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'time' OR partition_type = 'time-custom'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::timestamp, sub_max::timestamp INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'time'); SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_timestamp_start := v_time; BEGIN v_partition_timestamp_end := v_time + v_partition_interval; 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_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_time||' skipped'); CONTINUE; END; -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_timestamp_min IS NOT NULL THEN IF v_time < v_sub_timestamp_min OR v_time > v_sub_timestamp_max THEN CONTINUE; END IF; END IF; -- This suffix generation code is in partition_data_time() as well v_partition_suffix := to_char(v_time, v_datetime_string); v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); SELECT tablename INTO v_exists FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_partition_name::name; IF v_exists IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Creating new partition %s.%s with interval from %s to %s' , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end-'1sec'::interval)); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_parent_tablename AND n.nspname = v_parent_schema; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_parent_tablename AND n.nspname = v_parent_schema; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; IF v_epoch = false THEN EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_control , v_partition_timestamp_start , v_control , v_partition_timestamp_end); ELSE EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (to_timestamp(%I) >= %L AND to_timestamp(%I) < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_control , v_partition_timestamp_start , v_control , v_partition_timestamp_end); END IF; EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); -- If custom time, set extra config options. IF v_type = 'time-custom' THEN INSERT INTO @extschema@.custom_time_partitions (parent_table, child_table, partition_range) VALUES ( p_parent_table, v_parent_schema||'.'||v_partition_name, tstzrange(v_partition_timestamp_start, v_partition_timestamp_end, '[)') ); END IF; PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_use_run_maintenance , sub_infinite_time_partitions , sub_jobmon FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Subpartitioning %s.%s', v_parent_schema, v_partition_name)); END IF; 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_use_run_maintenance := %L , p_inherit_fk := %L , p_epoch := %L , p_jobmon := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_use_run_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions WHERE parent_table = v_parent_schema||'.'||v_partition_name; END LOOP; -- end sub partitioning LOOP -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table, p_job_id := v_job_id, p_debug := p_debug); v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s. Attempted intervals: %s', p_parent_table, p_partition_times)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; 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; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RETURN v_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to create partitioning trigger on parent table */ CREATE OR REPLACE FUNCTION create_trigger(p_parent_table text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_function_name text; v_new_length int; v_parent_schema text; v_parent_tablename text; v_trig_name text; v_trig_sql text; BEGIN SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); -- Ensure function name matches the naming pattern v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); v_trig_sql := format('CREATE TRIGGER %I BEFORE INSERT ON %I.%I FOR EACH ROW EXECUTE PROCEDURE %I.%I()' , v_trig_name , v_parent_schema , v_parent_tablename , v_parent_schema , v_function_name); EXECUTE v_trig_sql; END $$; /* * Drop constraints managed by pg_partman */ CREATE OR REPLACE FUNCTION drop_constraints(p_parent_table text, p_child_table text, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_child_schemaname text; v_child_tablename text; v_col text; v_constraint_cols text[]; v_existing_constraint_name text; v_exists boolean := FALSE; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_sql text; v_step_id bigint; BEGIN SELECT constraint_cols , jobmon INTO v_constraint_cols , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_constraint_cols IS NULL THEN RAISE EXCEPTION 'Given parent table (%) not set up for constraint management (constraint_cols is NULL)', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; SELECT schemaname, tablename INTO v_child_schemaname, v_child_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_child_table, '.', 1)::name AND tablename = split_part(p_child_table, '.', 2)::name; IF v_child_tablename IS NULL THEN RAISE EXCEPTION 'Unable to find given child table in system catalogs: %', p_child_table; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN DROP CONSTRAINT: %s', p_parent_table)); v_step_id := add_step(v_job_id, 'Entering constraint drop loop'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; FOREACH v_col IN ARRAY v_constraint_cols LOOP SELECT con.conname INTO v_existing_constraint_name FROM pg_catalog.pg_constraint con JOIN pg_class c ON c.oid = con.conrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN pg_catalog.pg_attribute a ON con.conrelid = a.attrelid WHERE c.relname = v_child_tablename AND n.nspname = v_child_schemaname AND con.conname LIKE 'partmanconstr_%' AND con.contype = 'c' AND a.attname = v_col AND ARRAY[a.attnum] OPERATOR(pg_catalog.<@) con.conkey AND a.attisdropped = false; IF v_existing_constraint_name IS NOT NULL THEN v_exists := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Dropping constraint on column: %s', v_col)); END IF; v_sql := format('ALTER TABLE %I.%I DROP CONSTRAINT %I', v_child_schemaname, v_child_tablename, v_existing_constraint_name); IF p_debug THEN RAISE NOTICE 'Constraint drop query: %', v_sql; END IF; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Drop constraint query: %s', v_sql)); END IF; END IF; END LOOP; IF v_jobmon_schema IS NOT NULL AND v_exists IS FALSE THEN v_step_id := add_step(v_job_id, format('No constraints found to drop on child table: %s', p_child_table)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; 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 DROP CONSTRAINT: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to drop child tables from an id-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ CREATE OR REPLACE FUNCTION drop_partition_id(p_parent_table text, p_retention bigint DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_control text; v_drop_count int := 0; v_index record; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_max bigint; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_partition_id bigint; v_retention bigint; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_row record; v_row_max_id record; v_step_id bigint; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman drop_partition_id')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_id already running.'; RETURN 0; END IF; -- Allow override of configuration options IF p_retention IS NULL THEN SELECT partition_interval::bigint , control , retention::bigint , retention_keep_table , retention_keep_index , retention_schema , jobmon INTO v_partition_interval , v_control , v_retention , v_retention_keep_table , v_retention_keep_index , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id' AND retention IS NOT NULL; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE SELECT partition_interval::bigint , control , retention_keep_table , retention_keep_index , retention_schema , jobmon INTO v_partition_interval , v_control , v_retention_keep_table , v_retention_keep_index , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; v_retention := p_retention; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%I) FROM %I.%I', v_control, v_row_max_id.partition_schemaname, v_row_max_id.partition_tablename) INTO v_max; IF v_max IS NOT NULL THEN EXIT; END IF; END LOOP; -- Loop through child tables of the given parent FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'ASC') LOOP SELECT child_start_id INTO v_partition_id FROM @extschema@.show_partition_info(v_row.partition_schemaname||'.'||v_row.partition_tablename , v_partition_interval::text , p_parent_table); -- Add one interval since partition names contain the start of the constraint period IF v_retention <= (v_max - (v_partition_id + v_partition_interval)) THEN -- Only create a jobmon entry if there's actual retention work done IF v_jobmon_schema IS NOT NULL AND v_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN DROP ID PARTITION: %s', p_parent_table)); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Uninherit table %s.%s from %s', v_row.partition_schemaname, v_row.partition_tablename, p_parent_table)); END IF; EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_row.partition_schemaname , v_row.partition_tablename , v_parent_schema , v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop table %s.%s', v_row.partition_schemaname, v_row.partition_tablename)); END IF; EXECUTE format('DROP TABLE %I.%I CASCADE', v_row.partition_schemaname, v_row.partition_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN FOR v_index IN WITH child_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE c1.relname = v_row.partition_tablename AND n1.nspname = v_row.partition_schema ) SELECT c.relname as name , con.conname FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid LEFT JOIN pg_catalog.pg_constraint con ON i.indexrelid = con.conindid JOIN child_info ON i.indrelid = child_info.oid LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop index %s from %s.%s' , v_index.name , v_row.partition_schemaname , v_row.partition_tablename)); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I DROP CONSTRAINT %I', v_row.partition_schemaname, v_row.partition_tablename, v_index.conname); ELSE EXECUTE format('DROP INDEX %I.%I', v_row.partition_schemaname, v_index.name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Moving table %s.%s to schema %s' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema)); END IF; EXECUTE format('ALTER TABLE %I.%I SET SCHEMA %I' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if -- If child table is a subpartition, remove it from part_config & part_config_sub (should cascade due to FK) DELETE FROM @extschema@.part_config WHERE parent_table = v_row.partition_schemaname ||'.'||v_row.partition_tablename; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', format('%s partitions dropped.', v_drop_count)); PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RETURN v_drop_count; 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 DROP ID PARTITION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to drop child tables from a time-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ CREATE OR REPLACE FUNCTION drop_partition_time(p_parent_table text, p_retention interval DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_datetime_string text; v_drop_count int := 0; v_index record; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_partition_timestamp timestamp; v_retention interval; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_row record; v_step_id bigint; v_type text; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman drop_partition_time')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_time already running.'; RETURN 0; END IF; -- Allow override of configuration options IF p_retention IS NULL THEN SELECT partition_type , partition_interval::interval , retention::interval , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_type , v_partition_interval , v_retention , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom') AND retention IS NOT NULL; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE SELECT partition_type , partition_interval::interval , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_type , v_partition_interval , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); v_retention := p_retention; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- Loop through child tables of the given parent FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'DESC') LOOP -- pull out datetime portion of partition's tablename to make the next one SELECT child_start_time INTO v_partition_timestamp FROM @extschema@.show_partition_info(v_row.partition_schemaname||'.'||v_row.partition_tablename , v_partition_interval::text , p_parent_table); -- Add one interval since partition names contain the start of the constraint period IF v_retention < (CURRENT_TIMESTAMP - (v_partition_timestamp + v_partition_interval)) THEN -- Only create a jobmon entry if there's actual retention work done IF v_jobmon_schema IS NOT NULL AND v_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN DROP TIME PARTITION: %s', p_parent_table)); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Uninherit table %s.%s from %s' , v_row.partition_schemaname , v_row.partition_tablename , p_parent_table)); END IF; EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_row.partition_schemaname , v_row.partition_tablename , v_parent_schema , v_parent_tablename); IF v_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_row.partition_schemaname||'.'||v_row.partition_tablename; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop table %s.%s', v_row.partition_schemaname, v_row.partition_tablename)); END IF; EXECUTE format('DROP TABLE %I.%I CASCADE', v_row.partition_schemaname, v_row.partition_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN FOR v_index IN WITH child_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE c1.relname = v_row.partition_tablename AND n1.nspname = v_row.partition_schemaname ) SELECT c.relname as name , con.conname FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid LEFT JOIN pg_catalog.pg_constraint con ON i.indexrelid = con.conindid JOIN child_info ON i.indrelid = child_info.oid LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop index %s from %s.%s' , v_index.name , v_row.partition_schemaname , v_row.partition_tablename)); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I DROP CONSTRAINT %I' , v_row.partition_schemaname , v_row.partition_tablename , v_index.conname); ELSE EXECUTE format('DROP INDEX %I.%I', v_parent_schema, v_index.name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Moving table %s.%s to schema %s' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema)); END IF; EXECUTE format('ALTER TABLE %I.%I SET SCHEMA %I', v_row.partition_schemaname, v_row.partition_tablename, v_retention_schema); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if -- If child table is a subpartition, remove it from part_config & part_config_sub (should cascade due to FK) DELETE FROM @extschema@.part_config WHERE parent_table = v_row.partition_schemaname||'.'||v_row.partition_tablename; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', format('%s partitions dropped.', v_drop_count)); PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RETURN v_drop_count; 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 DROP TIME PARTITION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Populate the child table(s) of an id-based partition set with old data from the original parent */ CREATE OR REPLACE FUNCTION partition_data_id(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval int DEFAULT NULL, p_lock_wait numeric DEFAULT 0, p_order text DEFAULT 'ASC') RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_current_partition_name text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_id bigint; v_min_partition_id bigint; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_partition_id bigint[]; v_rowcount bigint; v_sql text; v_start_control bigint; v_total_rows bigint := 0; BEGIN SELECT partition_interval::bigint , control INTO v_partition_interval , v_control FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; FOR i IN 1..p_batch_count LOOP IF p_order = 'ASC' THEN EXECUTE format('SELECT min(%I) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; IF v_start_control IS NULL THEN EXIT; END IF; v_min_partition_id = v_start_control - (v_start_control % v_partition_interval); v_partition_id := ARRAY[v_min_partition_id]; -- Check if custom batch interval overflows current partition maximum IF (v_start_control + p_batch_interval) >= (v_min_partition_id + v_partition_interval) THEN v_max_partition_id := v_min_partition_id + v_partition_interval; ELSE v_max_partition_id := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN EXECUTE 'SELECT max('||v_control||') FROM ONLY '||p_parent_table INTO v_start_control; IF v_start_control IS NULL THEN EXIT; END IF; v_min_partition_id = v_start_control - (v_start_control % v_partition_interval); -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_id := v_min_partition_id + v_partition_interval; v_partition_id := ARRAY[v_min_partition_id]; -- Make sure minimum doesn't underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_id THEN v_min_partition_id = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN v_sql := format('SELECT * FROM ONLY %I.%I WHERE %I >= %s AND %I < %s FOR UPDATE NOWAIT' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_id , v_control , v_max_partition_id); EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; PERFORM @extschema@.create_partition_id(p_parent_table, v_partition_id); v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_min_partition_id::text, TRUE); EXECUTE format('WITH partition_data AS ( DELETE FROM ONLY %I.%I WHERE %I >= %s AND %I < %s RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_id , v_control , v_max_partition_id , v_parent_schema , v_current_partition_name); GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; PERFORM @extschema@.create_function_id(p_parent_table); RETURN v_total_rows; END $$; /* * Populate the child table(s) of a time-based partition set with old data from the original parent */ CREATE OR REPLACE FUNCTION partition_data_time(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval interval DEFAULT NULL, p_lock_wait numeric DEFAULT 0, p_order text DEFAULT 'ASC') RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_datetime_string text; v_current_partition_name text; v_epoch boolean; v_last_partition text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_timestamp timestamp; v_min_partition_timestamp timestamp; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_partition_suffix text; v_partition_timestamp timestamp[]; v_quarter text; v_rowcount bigint; v_sql text; v_start_control timestamp; v_time_position int; v_total_rows bigint := 0; v_type text; v_year text; BEGIN SELECT partition_type , partition_interval::interval , control , datetime_string , epoch INTO v_type , v_partition_interval , v_control , v_datetime_string , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; FOR i IN 1..p_batch_count LOOP IF v_epoch = false THEN IF p_order = 'ASC' THEN EXECUTE format('SELECT min(%I) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSIF p_order = 'DESC' THEN EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; ELSE IF p_order = 'ASC' THEN EXECUTE format('SELECT to_timestamp(min(%I)) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSIF p_order = 'DESC' THEN EXECUTE format('SELECT to_timestamp(max(%I)) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; END IF; IF v_start_control IS NULL THEN EXIT; END IF; IF v_type = 'time' THEN CASE WHEN v_partition_interval = '15 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '15min'::interval * floor(date_part('minute', v_start_control) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '30min'::interval * floor(date_part('minute', v_start_control) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control); WHEN v_partition_interval = '1 day' THEN v_min_partition_timestamp := date_trunc('day', v_start_control); WHEN v_partition_interval = '1 week' THEN v_min_partition_timestamp := date_trunc('week', v_start_control); WHEN v_partition_interval = '1 month' THEN v_min_partition_timestamp := date_trunc('month', v_start_control); WHEN v_partition_interval = '3 months' THEN v_min_partition_timestamp := date_trunc('quarter', v_start_control); WHEN v_partition_interval = '1 year' THEN v_min_partition_timestamp := date_trunc('year', v_start_control); END CASE; ELSIF v_type = 'time-custom' THEN SELECT child_start_time INTO v_min_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition , v_partition_interval::text , p_parent_table); v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; LOOP IF v_start_control >= v_min_partition_timestamp AND v_start_control < v_max_partition_timestamp THEN EXIT; ELSE BEGIN IF v_start_control > v_max_partition_timestamp THEN -- Keep going forward in time, checking if child partition time interval encompasses the current v_start_control value v_min_partition_timestamp := v_max_partition_timestamp; v_max_partition_timestamp := v_max_partition_timestamp + v_partition_interval; ELSE -- Keep going backwards in time, checking if child partition time interval encompasses the current v_start_control value v_max_partition_timestamp := v_min_partition_timestamp; v_min_partition_timestamp := v_min_partition_timestamp - v_partition_interval; END IF; EXCEPTION WHEN datetime_field_overflow THEN RAISE EXCEPTION 'Attempted partition time interval is outside PostgreSQL''s supported time range. Unable to create partition with interval before timestamp % ', v_min_partition_interval; END; END IF; END LOOP; END IF; v_partition_timestamp := ARRAY[v_min_partition_timestamp]; IF p_order = 'ASC' THEN -- Ensure batch interval given as parameter doesn't cause maximum to overflow the current partition maximum IF (v_start_control + p_batch_interval) >= (v_min_partition_timestamp + v_partition_interval) THEN v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; ELSE v_max_partition_timestamp := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; -- Ensure batch interval given as parameter doesn't cause minimum to underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_timestamp THEN v_min_partition_timestamp = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN IF v_epoch = false THEN v_sql := format('SELECT * FROM ONLY %I.%I WHERE %I >= %L AND %I < %L FOR UPDATE NOWAIT' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_timestamp , v_control , v_max_partition_timestamp); ELSE v_sql := format('SELECT * FROM ONLY %I.%I WHERE to_timestamp(%I) >= %L AND to_timestamp(%I) < %L FOR UPDATE NOWAIT' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_timestamp , v_control , v_max_partition_timestamp); END IF; EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; PERFORM @extschema@.create_partition_time(p_parent_table, v_partition_timestamp); -- This suffix generation code is in create_partition_time() as well v_partition_suffix := to_char(v_min_partition_timestamp, v_datetime_string); v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); IF v_epoch = false THEN v_sql := format('WITH partition_data AS ( DELETE FROM ONLY %I.%I WHERE %I >= %L AND %I < %L RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_timestamp , v_control , v_max_partition_timestamp , v_parent_schema , v_current_partition_name); ELSE v_sql := format('WITH partition_data AS ( DELETE FROM ONLY %I.%I WHERE to_timestamp(%I) >= %L AND to_timestamp(%I) < %L RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_timestamp , v_control , v_max_partition_timestamp , v_parent_schema , v_current_partition_name); END IF; EXECUTE v_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; PERFORM @extschema@.create_function_time(p_parent_table); RETURN v_total_rows; END $$; /* * Function to re-apply ownership & privileges on all child tables in a partition set using parent table as reference */ CREATE OR REPLACE FUNCTION reapply_privileges(p_parent_table text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_parent_schema text; v_parent_tablename text; v_row record; v_step_id bigint; BEGIN SELECT jobmon INTO v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon IS NULL THEN RAISE EXCEPTION 'Given table is not managed by this extention: %', p_parent_table; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; IF v_parent_tablename IS NULL THEN RAISE EXCEPTION 'Given parent table does not exist: %', p_parent_table; END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.add_job(%L)', v_jobmon_schema, format('PARTMAN RE-APPLYING PRIVILEGES TO ALL CHILD TABLES OF: %s', p_parent_table)) INTO v_job_id; END IF; FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'ASC') LOOP PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_row.partition_schemaname, v_row.partition_tablename, v_job_id); END LOOP; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.close_job(%L)', v_jobmon_schema, v_job_id); END IF; 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 RE-APPLYING PRIVILEGES TO ALL CHILD TABLES OF: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Show the data boundries for a given child table as well as the suffix that will be used. * Passing the parent table argument improves performance by avoiding a catalog lookup. * Passing an interval lets you set one different than the default configured one if desired. */ CREATE OR REPLACE FUNCTION show_partition_info(p_child_table text , p_partition_interval text DEFAULT NULL , p_parent_table text DEFAULT NULL , OUT child_start_time timestamp , OUT child_end_time timestamp , OUT child_start_id bigint , OUT child_end_id bigint , OUT suffix text) RETURNS record LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_child_schema text; v_child_tablename text; v_datetime_string text; v_parent_table text; v_partition_interval text; v_partition_type text; v_quarter text; v_suffix text; v_suffix_position int; v_year text; BEGIN SELECT schemaname, tablename INTO v_child_schema, v_child_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_child_table, '.', 1)::name AND tablename = split_part(p_child_table, '.', 2)::name; IF v_child_tablename IS NULL THEN RAISE EXCEPTION 'Child table given does not exist (%)', p_child_table; END IF; IF p_parent_table IS NULL THEN SELECT n.nspname||'.'|| c.relname INTO v_parent_table FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhparent JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhrelid::regclass = p_child_table::regclass; ELSE v_parent_table := p_parent_table; END IF; IF p_partition_interval IS NULL THEN SELECT partition_interval, partition_type, datetime_string INTO v_partition_interval, v_partition_type, v_datetime_string FROM @extschema@.part_config WHERE parent_table = v_parent_table; ELSE v_partition_interval := p_partition_interval; SELECT partition_type, datetime_string INTO v_partition_type, v_datetime_string FROM @extschema@.part_config WHERE parent_table = v_parent_table; END IF; IF v_partition_type IS NULL THEN RAISE EXCEPTION 'Parent table of given child not managed by pg_partman: %', v_parent_table; END IF; v_suffix_position := (length(v_child_tablename) - position('p_' in reverse(v_child_tablename))) + 2; v_suffix := substring(v_child_tablename from v_suffix_position); IF v_partition_type = 'time' OR v_partition_type = 'time-custom' THEN IF v_partition_interval::interval <> '3 months' OR (v_partition_interval::interval = '3 months' AND v_partition_type = 'time-custom') THEN child_start_time := to_timestamp(v_suffix, v_datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(v_suffix, 'q', 1); v_quarter := split_part(v_suffix, 'q', 2); CASE WHEN v_quarter = '1' THEN child_start_time := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN child_start_time := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN child_start_time := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN child_start_time := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; child_end_time := (child_start_time + v_partition_interval::interval) - '1 second'::interval; ELSIF v_partition_type = 'id' THEN child_start_id := v_suffix::bigint; child_end_id := (child_start_id + v_partition_interval::bigint) - 1; ELSE RAISE EXCEPTION 'Invalid partition type encountered in show_partition_info()'; END IF; suffix = v_suffix; RETURN; END $$; /* * Given a parent table and partition value, return the name of the child partition it would go in. * If using epoch time partitioning, give the text representation of the timestamp NOT the epoch integer value (use to_timestamp() to convert epoch values). * Also returns just the suffix value and true if the child table exists or false if it does not */ CREATE OR REPLACE FUNCTION show_partition_name(p_parent_table text, p_value text, OUT partition_table text, OUT suffix_timestamp timestamp, OUT suffix_id bigint, OUT table_exists boolean) RETURNS record LANGUAGE plpgsql STABLE AS $$ DECLARE v_child_exists text; v_datetime_string text; v_max_range timestamptz; v_min_range timestamptz; v_parent_schema text; v_parent_tablename text; v_partition_interval text; v_type text; BEGIN SELECT partition_type , partition_interval , datetime_string INTO v_type , v_partition_interval , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_type IS NULL THEN RAISE EXCEPTION 'Parent table given is not managed by pg_partman (%)', p_parent_table; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; IF v_parent_tablename IS NULL THEN RAISE EXCEPTION 'Parent table given does not exist (%)', p_parent_table; END IF; IF v_type = 'time' THEN CASE WHEN v_partition_interval::interval = '15 mins' THEN suffix_timestamp := date_trunc('hour', p_value::timestamp) + '15min'::interval * floor(date_part('minute', p_value::timestamp) / 15.0); WHEN v_partition_interval::interval = '30 mins' THEN suffix_timestamp := date_trunc('hour', p_value::timestamp) + '30min'::interval * floor(date_part('minute', p_value::timestamp) / 30.0); WHEN v_partition_interval::interval = '1 hour' THEN suffix_timestamp := date_trunc('hour', p_value::timestamp); WHEN v_partition_interval::interval = '1 day' THEN suffix_timestamp := date_trunc('day', p_value::timestamp); WHEN v_partition_interval::interval = '1 week' THEN suffix_timestamp := date_trunc('week', p_value::timestamp); WHEN v_partition_interval::interval = '1 month' THEN suffix_timestamp := date_trunc('month', p_value::timestamp); WHEN v_partition_interval::interval = '3 months' THEN suffix_timestamp := date_trunc('quarter', p_value::timestamp); WHEN v_partition_interval::interval = '1 year' THEN suffix_timestamp := date_trunc('year', p_value::timestamp); END CASE; partition_table := v_parent_schema||'.'||@extschema@.check_name_length(v_parent_tablename, to_char(suffix_timestamp, v_datetime_string), TRUE); ELSIF v_type = 'id' THEN suffix_id := (p_value::bigint - (p_value::bigint % v_partition_interval::bigint)); partition_table := v_parent_schema||'.'||@extschema@.check_name_length(v_parent_tablename, suffix_id::text, TRUE); ELSIF v_type = 'time-custom' THEN SELECT child_table, lower(partition_range) INTO partition_table, suffix_timestamp FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND partition_range @> p_value::timestamptz; IF partition_table IS NULL THEN SELECT max(upper(partition_range)) INTO v_max_range FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table; SELECT min(lower(partition_range)) INTO v_min_range FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table; IF p_value::timestamp >= v_max_range THEN suffix_timestamp := v_max_range; LOOP -- Keep incrementing higher until given value is below the upper range suffix_timestamp := suffix_timestamp + v_partition_interval::interval; IF p_value::timestamp < suffix_timestamp THEN -- Have to subtract one interval because the value would actually be in the partition previous -- to this partition timestamp since the partition names contain the lower boundary suffix_timestamp := suffix_timestamp - v_partition_interval::interval; EXIT; END IF; END LOOP; ELSIF p_value::timestamp < v_min_range THEN suffix_timestamp := v_min_range; LOOP -- Keep decrementing lower until given value is below or equal to the lower range suffix_timestamp := suffix_timestamp - v_partition_interval::interval; IF p_value::timestamp >= suffix_timestamp THEN EXIT; END IF; END LOOP; ELSE RAISE EXCEPTION 'Unable to determine a valid child table for the given parent table and value'; END IF; partition_table := v_parent_schema||'.'||@extschema@.check_name_length(v_parent_tablename, to_char(suffix_timestamp, v_datetime_string), TRUE); END IF; END IF; SELECT tablename INTO v_child_exists FROM pg_catalog.pg_tables WHERE schemaname = split_part(partition_table, '.', 1)::name AND tablename = split_part(partition_table, '.', 2)::name; IF v_child_exists IS NOT NULL THEN table_exists := true; ELSE table_exists := false; END IF; RETURN; END $$; /* * Function to list all child partitions in a set in logical order. */ CREATE OR REPLACE FUNCTION show_partitions (p_parent_table text, p_order text DEFAULT 'ASC') RETURNS TABLE (partition_schemaname text, partition_tablename text) LANGUAGE plpgsql STABLE SECURITY DEFINER AS $$ DECLARE v_datetime_string text; v_parent_schema text; v_parent_tablename text; v_partition_interval text; v_quarter text; v_type text; v_year text; BEGIN IF upper(p_order) NOT IN ('ASC', 'DESC') THEN RAISE EXCEPTION 'p_order paramter must be one of the following values: ASC, DESC'; END IF; SELECT partition_type , partition_interval , datetime_string INTO v_type , v_partition_interval , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; IF v_type IN ('time', 'time-custom') THEN IF v_partition_interval::interval <> '3 months' OR (v_partition_interval::interval = '3 months' AND v_type = 'time-custom') THEN RETURN QUERY EXECUTE format('SELECT n.nspname::text AS partition_schemaname, c.relname::text AS partition_name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = ''%I.%I''::regclass ORDER BY to_timestamp(substring(c.relname from ((length(c.relname) - position(''p_'' in reverse(c.relname))) + 2) ), %L) %s' , v_parent_schema , v_parent_tablename , v_datetime_string , p_order); ELSE -- For quarterly, to_timestamp() doesn't recognize "Q" in datetime string. -- First order by just the year, then order by the quarter number (should be last character in table name) RETURN QUERY EXECUTE format('SELECT n.nspname::text AS partition_schemaname, c.relname::text AS partition_name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = ''%I.%I''::regclass ORDER BY to_timestamp(substring(c.relname from ((length(c.relname) - position(''p_'' in reverse(c.relname))) + 2) for 4), ''YYYY'') %s , substring(reverse(c.relname) from 1 for 1) %s' , v_parent_schema , v_parent_tablename , p_order , p_order); END IF; ELSIF v_type = 'id' THEN RETURN QUERY EXECUTE format('SELECT n.nspname::text AS partition_schemaname, c.relname::text AS partition_name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = ''%I.%I''::regclass ORDER BY substring(c.relname from ((length(c.relname) - position(''p_'' in reverse(c.relname))) + 2) )::bigint %s' , v_parent_schema , v_parent_tablename , p_order); END IF; END $$; /* * Function to undo partitioning. Copies data to parent without removing any data from children. * Will actually work on any parent/child table set, not just ones created by pg_partman. */ CREATE OR REPLACE FUNCTION undo_partition(p_parent_table text, p_batch_count int DEFAULT 1, p_keep_table boolean DEFAULT true, p_jobmon boolean DEFAULT true, p_lock_wait numeric DEFAULT 0) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_batch_loop_count bigint := 0; v_child_count bigint; v_child_table text; v_copy_sql text; v_function_name text; v_job_id bigint; v_jobmon_schema text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_rowcount bigint; v_step_id bigint; v_total bigint := 0; v_trig_name text; v_type text; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition already running.'; RETURN 0; END IF; IF p_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN UNDO PARTITIONING: %s', p_parent_table)); v_step_id := add_step(v_job_id, format('Undoing partitioning for table %s', p_parent_table)); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables and stop new id partitions from being made. v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t JOIN pg_catalog.pg_class c ON t.tgrelid = c.oid WHERE tgname = v_trig_name AND c.relname = v_parent_tablename; SELECT proname INTO v_function_name FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid WHERE n.nspname = v_parent_schema::name AND proname = v_function_name::name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_parent_tablename); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; RETURN -1; END IF; END IF; -- END p_lock_wait IF EXECUTE format('DROP TRIGGER IF EXISTS %I ON %I.%I', v_trig_name, v_parent_schema, v_parent_tablename); END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later IF v_function_name IS NOT NULL THEN EXECUTE format('DROP FUNCTION IF EXISTS %I.%I()', v_parent_schema, v_function_name); END IF; IF v_jobmon_schema IS NOT NULL THEN IF (v_trig_name IS NOT NULL OR v_function_name IS NOT NULL) THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); ELSE PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process.'); END IF; END IF; WHILE v_batch_loop_count < p_batch_count LOOP -- Get ordered list of child table in set. Store in variable one at a time per loop until none are left. -- Not using show_partitions() so it can work on non-pg_partman partition sets WITH parent_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE c1.relname = v_parent_tablename AND n1.nspname = v_parent_schema ) SELECT c.relname INTO v_child_table FROM pg_catalog.pg_inherits i JOIN pg_catalog.pg_class c ON i.inhrelid = c.oid JOIN parent_info p ON i.inhparent = p.oid ORDER BY i.inhrelid ASC; EXIT WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Removing child partition: %s.%s', v_parent_schema, v_child_table)); END IF; -- lockwait timeout for table drop v_lock_obtained := FALSE; -- reset for reuse in loop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_child_table); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; RETURN -1; END IF; END IF; -- END p_lock_wait IF v_copy_sql := format('INSERT INTO %I.%I SELECT * FROM %I.%I' , v_parent_schema , v_parent_tablename , v_parent_schema , v_child_table); EXECUTE v_copy_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_parent_schema , v_child_table , v_parent_schema , v_parent_tablename); IF p_keep_table = false THEN EXECUTE format('DROP TABLE %I.%I', v_parent_schema, v_child_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table DROPPED. Moved %s rows to parent', v_rowcount)); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table UNINHERITED, not DROPPED. Copied %s rows to parent', v_rowcount)); END IF; END IF; SELECT partition_type INTO v_type FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_parent_schema||'.'||v_child_table; END IF; v_batch_loop_count := v_batch_loop_count + 1; v_undo_count := v_undo_count + 1; END LOOP; -- v_batch_loop_count IF v_undo_count = 0 THEN -- FOR loop never ran, so there's no child tables left. DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman (if it existed)'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) from % child table(s) to the parent: %', v_total, v_undo_count, p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', format('Copied %s row(s) from %s child table(s) to the parent', v_total, v_undo_count)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RETURN v_total; 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 UNDO PARTITIONING: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to undo id-based partitioning created by this extension */ CREATE OR REPLACE FUNCTION undo_partition_id(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval bigint DEFAULT NULL, p_keep_table boolean DEFAULT true, p_lock_wait numeric DEFAULT 0) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_batch_loop_count int := 0; v_child_loop_total bigint := 0; v_child_min bigint; v_child_table text; v_control text; v_exists int; v_function_name text; v_inner_loop_count int; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_move_sql text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_row record; v_rowcount bigint; v_step_id bigint; v_sub_count int; v_trig_name text; v_total bigint := 0; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition_id')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition_id already running.'; RETURN 0; END IF; SELECT partition_interval::bigint , control , jobmon INTO v_partition_interval , v_control , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; -- Check if any child tables are themselves partitioned or part of an inheritance tree. Prevent undo at this level if so. -- Need to either lock child tables at all levels or handle the proper removal of triggers on all child tables first -- before multi-level undo can be performed safely. FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table) LOOP SELECT count(*) INTO v_sub_count FROM pg_catalog.pg_inherits i JOIN pg_catalog.pg_class c ON i.inhparent = c.oid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_row.partition_tablename AND n.nspname = v_row.partition_schemaname; IF v_sub_count > 0 THEN RAISE EXCEPTION 'Child table for this parent has child table(s) itself (%). Run undo partitioning on this table or remove inheritance first to ensure all data is properly moved to parent', v_row.partition_schemaname||'.'||v_row.partition_tablename; END IF; END LOOP; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN UNDO PARTITIONING: %s', p_parent_table)); v_step_id := add_step(v_job_id, format('Undoing partitioning for table %s', p_parent_table)); END IF; IF p_batch_interval IS NULL THEN p_batch_interval := v_partition_interval; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables and stop new id partitions from being made. v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t JOIN pg_catalog.pg_class c ON t.tgrelid = c.oid WHERE tgname = v_trig_name AND c.relname = v_parent_tablename; SELECT proname INTO v_function_name FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid WHERE n.nspname = v_parent_schema AND proname = v_function_name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_parent_tablename); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; RETURN -1; END IF; END IF; -- END p_lock_wait IF EXECUTE format('DROP TRIGGER IF EXISTS %I ON %I.%I', v_trig_name, v_parent_schema, v_parent_tablename); END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later IF v_function_name IS NOT NULL THEN EXECUTE format('DROP FUNCTION IF EXISTS %I.%I()', v_parent_schema, v_function_name); END IF; IF v_jobmon_schema IS NOT NULL THEN IF (v_trig_name IS NOT NULL OR v_function_name IS NOT NULL) THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); ELSE PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process.'); END IF; END IF; <> LOOP -- Get ordered list of child table in set. Store in variable one at a time per loop until none are left or batch count is reached. -- This easily allows it to loop over same child table until empty or move onto next child table after it's dropped SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC') LIMIT 1; EXIT WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Removing child partition: %s.%s', v_parent_schema, v_child_table)); END IF; EXECUTE format('SELECT min(%I) FROM %I.%I', v_control, v_parent_schema, v_child_table) INTO v_child_min; IF v_child_min IS NULL THEN -- No rows left in this child table. Remove from partition set. -- lockwait timeout for table drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_child_table); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; RETURN -1; END IF; END IF; -- END p_lock_wait IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_parent_schema , v_child_table , v_parent_schema , v_parent_tablename); IF p_keep_table = false THEN EXECUTE format('DROP TABLE %I.%I', v_parent_schema, v_child_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table DROPPED. Moved %s rows to parent', v_child_loop_total)); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table UNINHERITED, not DROPPED. Moved %s rows to parent', v_child_loop_total)); END IF; END IF; v_undo_count := v_undo_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached CONTINUE outer_child_loop; -- skip data moving steps below END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP -- lockwait timeout for row batches IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM %I.%I WHERE %I <= %s FOR UPDATE NOWAIT' , v_parent_schema , v_child_table , v_control , v_child_min + (p_batch_interval * v_inner_loop_count)); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; RETURN -1; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval v_move_sql := format('WITH move_data AS ( DELETE FROM %I.%I WHERE %I <= %s RETURNING *) INSERT INTO %I.%I SELECT * FROM move_data' , v_parent_schema , v_child_table , v_control , v_child_min + (p_batch_interval * v_inner_loop_count) , v_parent_schema , v_parent_tablename); EXECUTE v_move_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Moved %s rows to parent.', v_child_loop_total)); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; -- Check again if table is empty and go to outer loop again to drop it if so EXECUTE format('SELECT min(%I) FROM %I.%I', v_control, v_parent_schema, v_child_table) INTO v_child_min; CONTINUE outer_child_loop WHEN v_child_min IS NULL; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC') LIMIT 1; IF v_child_table IS NULL THEN DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) to the parent. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', format('Copied %s row(s) to the parent. Removed %s partitions.', v_total, v_undo_count)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RETURN v_total; 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 UNDO PARTITIONING: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to undo time-based partitioning created by this extension */ CREATE OR REPLACE FUNCTION undo_partition_time(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval interval DEFAULT NULL, p_keep_table boolean DEFAULT true, p_lock_wait numeric DEFAULT 0) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_batch_loop_count int := 0; v_child_loop_total bigint := 0; v_child_min timestamptz; v_child_table text; v_control text; v_epoch boolean; v_function_name text; v_inner_loop_count int; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_move_sql text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_row record; v_rowcount bigint; v_step_id bigint; v_sub_count int; v_total bigint := 0; v_trig_name text; v_type text; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition_time')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition_time already running.'; RETURN 0; END IF; SELECT partition_type , partition_interval::interval , control , jobmon , epoch INTO v_type , v_partition_interval , v_control , v_jobmon , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; -- Check if any child tables are themselves partitioned or part of an inheritance tree. Prevent undo at this level if so. -- Need to either lock child tables at all levels or handle the proper removal of triggers on all child tables first -- before multi-level undo can be performed safely. FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table) LOOP SELECT count(*) INTO v_sub_count FROM pg_catalog.pg_inherits i JOIN pg_catalog.pg_class c ON i.inhparent = c.oid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_row.partition_tablename AND n.nspname = v_row.partition_schemaname; IF v_sub_count > 0 THEN RAISE EXCEPTION 'Child table for this parent has child table(s) itself (%). Run undo partitioning on this table or remove inheritance first to ensure all data is properly moved to parent', v_row.partition_schemaname||'.'||v_row.partition_tablename; END IF; END LOOP; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN UNDO PARTITIONING: %s', p_parent_table)); v_step_id := add_step(v_job_id, format('Undoing partitioning for table %s', p_parent_table)); END IF; IF p_batch_interval IS NULL THEN p_batch_interval := v_partition_interval; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables. v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t JOIN pg_catalog.pg_class c ON t.tgrelid = c.oid WHERE tgname = v_trig_name AND c.relname = v_parent_tablename; SELECT proname INTO v_function_name FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid WHERE n.nspname = v_parent_schema AND proname = v_function_name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_parent_tablename); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; RETURN -1; END IF; END IF; -- END p_lock_wait IF EXECUTE format('DROP TRIGGER IF EXISTS %I ON %I.%I', v_trig_name, v_parent_schema, v_parent_tablename); END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later IF v_function_name IS NOT NULL THEN EXECUTE format('DROP FUNCTION IF EXISTS %I.%I()', v_parent_schema, v_function_name); END IF; IF v_jobmon_schema IS NOT NULL THEN IF (v_trig_name IS NOT NULL OR v_function_name IS NOT NULL) THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); ELSE PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process.'); END IF; END IF; <> LOOP -- Get ordered list of child table in set. Store in variable one at a time per loop until none are left or batch count is reached. -- This easily allows it to loop over same child table until empty or move onto next child table after it's dropped SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC') LIMIT 1; EXIT outer_child_loop WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Removing child partition: %s.%s', v_parent_schema, v_child_table)); END IF; IF v_epoch = false THEN EXECUTE format('SELECT min(%I) FROM %I.%I', v_control, v_parent_schema, v_child_table) INTO v_child_min; ELSE EXECUTE format('SELECT to_timestamp(min(%I)) FROM %I.%I', v_control, v_parent_schema, v_child_table) INTO v_child_min; END IF; IF v_child_min IS NULL THEN -- No rows left in this child table. Remove from partition set. -- lockwait timeout for table drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_child_table); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; RETURN -1; END IF; END IF; -- END p_lock_wait IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_parent_schema , v_child_table , v_parent_schema , v_parent_tablename); IF p_keep_table = false THEN EXECUTE format('DROP TABLE %I.%I', v_parent_schema, v_child_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table DROPPED. Moved %s rows to parent', v_child_loop_total)); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table UNINHERITED, not DROPPED. Moved %s rows to parent', v_child_loop_total)); END IF; END IF; IF v_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_parent_schema||'.'||v_child_table; END IF; v_undo_count := v_undo_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached CONTINUE outer_child_loop; -- skip data moving steps below END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM %I.%I WHERE %I <= %L FOR UPDATE NOWAIT' , v_parent_schema , v_child_table , v_control , v_child_min + (p_batch_interval * v_inner_loop_count)); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; RETURN -1; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval IF v_epoch = false THEN v_move_sql := format('WITH move_data AS ( DELETE FROM %I.%I WHERE %I <= %L RETURNING *) INSERT INTO %I.%I SELECT * FROM move_data' , v_parent_schema , v_child_table , v_control , v_child_min + (p_batch_interval * v_inner_loop_count) , v_parent_schema , v_parent_tablename); ELSE v_move_sql := format('WITH move_data AS ( DELETE FROM %I.%I WHERE to_timestamp(%I) <= %L RETURNING *) INSERT INTO %I.%I SELECT * FROM move_data' , v_parent_schema , v_child_table , v_control , v_child_min + (p_batch_interval * v_inner_loop_count) , v_parent_schema , v_parent_tablename); END IF; EXECUTE v_move_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Moved %s rows to parent.', v_child_loop_total)); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; -- Check again if table is empty and go to outer loop again to drop it if so IF v_epoch = false THEN EXECUTE format('SELECT min(%I) FROM %I.%I', v_control, v_parent_schema, v_child_table) INTO v_child_min; ELSE EXECUTE format('SELECT to_timestamp(min(%I)) FROM %I.%I', v_control, v_parent_schema, v_child_table) INTO v_child_min; END IF; CONTINUE outer_child_loop WHEN v_child_min IS NULL; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC') LIMIT 1; IF v_child_table IS NULL THEN DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) to the parent. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', format('Copied %s row(s) to the parent. Removed %s partitions.', v_total, v_undo_count)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RETURN v_total; 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 UNDO PARTITIONING: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; pg_partman-4.6.0/updates/pg_partman--2.3.3--2.3.4.sql000066400000000000000000006511551412761706100214460ustar00rootroot00000000000000-- IMPORTANT NOTICE: The next minor version (2.4.0) will officially drop support for the 1.x series. No further fixes (bugs, features, etc) will be backported and 1.8.8 will likely be the last version in the 1.x series. If you encounter a bug, the only supported method of fixing it will be to upgrade to the 2.x series where it may already have been fixed or you can submit an Issue on Github. -- Properly handle SECURITY DEFINER functions in a safe manner by resetting search_path as recommended in the PG docs (http://www.postgresql.org/docs/current/static/sql-createfunction.html#SQL-CREATEFUNCTION-SECURITY) (Github Pull Request #108). -- Made the exception handling in the partitioning trigger introduced in v2.3.3 optional. This was causing unexpected behavior whenever an exception was being handled by some other external process when it used a partitioned table. The trigger exception handling would take over and not allow the external process to handle it. New configuration option "trigger_exception_handling" has been added to the part_config (and part_config_sub) table to enable/disable this feature. It has been set to false by default to avoid unexpected default behavior which means any partition sets that had a trigger created by v2.3.3 will have the exception handling removed the next time it is updated. If you'd like to keep this feature, set the config table column to true. -- Added parameter p_exact_count to check_parent() to allow faster checks for data in parent table. If set to false, will only look for a single row existing in the parent instead of trying to get an exact count which could take a long time if a lot of data is in the parent (Github Pull Request #114). -- Further optimization to catalog lookups to improve performance on larger databases (Github Issue #107). ALTER TABLE @extschema@.part_config ADD COLUMN trigger_exception_handling BOOLEAN DEFAULT false; ALTER TABLE @extschema@.part_config_sub ADD COLUMN sub_trigger_exception_handling BOOLEAN DEFAULT false; CREATE TEMP TABLE partman_preserve_privs_temp (statement text); INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.check_parent(p_exact_count boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'check_parent'; DROP FUNCTION check_parent(); /* * Function to apply cluster from parent to child table * Adapted from code fork by https://github.com/dturon/pg_partman */ CREATE OR REPLACE FUNCTION apply_cluster(p_parent_schema text, p_parent_tablename text, p_child_schema text, p_child_tablename text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_indexdef text; v_row record; v_sql text; BEGIN 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'); WITH parent_info AS ( SELECT c.oid AS parent_oid FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = p_parent_schema::name AND c.relname = p_parent_tablename::name ) SELECT substring(pg_get_indexdef(i.indexrelid) from ' USING .*$') AS index_def INTO v_parent_indexdef FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid JOIN parent_info p ON p.parent_oid = indrelid WHERE i.indisclustered = true; -- Loop over all existing indexes in child table to find one with matching definition FOR v_row IN WITH child_info AS ( SELECT c.oid AS child_oid 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 ) SELECT substring(pg_get_indexdef(i.indexrelid) from ' USING .*$') AS child_indexdef , c.relname AS child_indexname FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid JOIN child_info p ON p.child_oid = indrelid LOOP IF v_row.child_indexdef = v_parent_indexdef THEN v_sql = format('ALTER TABLE %I.%I CLUSTER ON %I', p_child_schema, p_child_tablename, v_row.child_indexname); RAISE DEBUG '%', v_sql; EXECUTE v_sql; END IF; END LOOP; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END; $$; /* * Apply constraints managed by partman extension */ CREATE OR REPLACE FUNCTION apply_constraints(p_parent_table text, p_child_table text DEFAULT NULL, p_analyze boolean DEFAULT FALSE, p_job_id bigint DEFAULT NULL, p_debug boolean DEFAULT FALSE) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_child_exists text; v_child_tablename text; v_col text; v_constraint_cols text[]; v_constraint_col_type text; v_constraint_name text; v_constraint_values record; v_control text; v_datetime_string text; v_existing_constraint_name text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_id bigint; v_last_partition_timestamp timestamp; v_max_id bigint; v_max_timestamp timestamp; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_optimize_constraint int; v_parent_schema text; v_parent_table text; v_parent_tablename text; v_partition_interval text; v_partition_suffix text; v_premake int; v_sql text; v_step_id bigint; v_suffix_position int; v_type text; BEGIN SELECT parent_table , partition_type , control , premake , partition_interval , optimize_constraint , datetime_string , constraint_cols , jobmon INTO v_parent_table , v_type , v_control , v_premake , v_partition_interval , v_optimize_constraint , v_datetime_string , v_constraint_cols , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND constraint_cols IS NOT NULL; IF v_constraint_cols IS NULL THEN IF p_debug THEN RAISE NOTICE 'Given parent table (%) not set up for constraint management (constraint_cols is NULL)', p_parent_table; END IF; -- Returns silently to allow this function to be simply called by maintenance processes without having to check if config options are set. RETURN; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(v_parent_table, '.', 1)::name AND tablename = split_part(v_parent_table, '.', 2)::name; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE CONSTRAINT: %s', v_parent_table)); ELSE v_job_id = p_job_id; END IF; END IF; -- If p_child_table is null, figure out the partition that is the one right before the optimize_constraint value backwards. IF p_child_table IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying additional constraints: Automatically determining most recent child on which to apply constraints'); END IF; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(v_parent_table, 'DESC') LIMIT 1; IF v_type IN ('time', 'time-custom') THEN SELECT child_start_time INTO v_last_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_partition_interval, v_parent_table); v_partition_suffix := to_char(v_last_partition_timestamp - (v_partition_interval::interval * (v_optimize_constraint + v_premake + 1) ), v_datetime_string); ELSIF v_type = 'id' THEN SELECT child_start_id INTO v_last_partition_id FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_partition_interval, v_parent_table); v_partition_suffix := (v_last_partition_id - (v_partition_interval::int * (v_optimize_constraint + v_premake + 1) ))::text; END IF; v_child_tablename := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); IF p_debug THEN RAISE NOTICE 'apply_constraint: v_parent_tablename: % , v_partition_suffix: %', v_parent_tablename, v_partition_suffix; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Target child table: %s.%s', v_parent_schema, v_child_tablename)); END IF; ELSE v_child_tablename = split_part(p_child_table, '.', 2); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying additional constraints: Checking if target child table exists'); END IF; SELECT tablename FROM pg_catalog.pg_tables INTO v_child_exists WHERE schemaname = v_parent_schema::name AND tablename = v_child_tablename::name; IF v_child_exists IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Target child table (%s) does not exist. Skipping constraint creation.', v_child_tablename)); IF p_job_id IS NULL THEN PERFORM close_job(v_job_id); END IF; END IF; IF p_debug THEN RAISE NOTICE 'Target child table (%) does not exist. Skipping constraint creation.', v_child_tablename; END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; FOREACH v_col IN ARRAY v_constraint_cols LOOP SELECT con.conname INTO v_existing_constraint_name FROM pg_catalog.pg_constraint con JOIN pg_class c ON c.oid = con.conrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN pg_catalog.pg_attribute a ON con.conrelid = a.attrelid WHERE c.relname = v_child_tablename::name AND n.nspname = v_parent_schema::name AND con.conname LIKE 'partmanconstr_%' AND con.contype = 'c' AND a.attname = v_col::name AND ARRAY[a.attnum] OPERATOR(pg_catalog.<@) con.conkey AND a.attisdropped = false; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Applying additional constraints: Applying new constraint on column: %s', v_col)); END IF; IF v_existing_constraint_name IS NOT NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Partman managed constraint already exists on this table (%s) and column (%s). Skipping creation.', v_child_tablename, v_col)); END IF; IF p_debug THEN RAISE NOTICE 'Partman managed constraint already exists on this table (%) and column (%). Skipping creation.', v_child_tablename, v_col ; END IF; CONTINUE; END IF; -- Ensure column name gets put on end of constraint name to help avoid naming conflicts v_constraint_name := @extschema@.check_name_length('partmanconstr_'||v_child_tablename, p_suffix := '_'||v_col); EXECUTE format('SELECT min(%I)::text AS min, max(%I)::text AS max FROM %I.%I', v_col, v_col, v_parent_schema, v_child_tablename) INTO v_constraint_values; IF v_constraint_values IS NOT NULL THEN v_sql := format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I <= %L)' , v_parent_schema , v_child_tablename , v_constraint_name , v_col , v_constraint_values.min , v_col , v_constraint_values.max); IF p_debug THEN RAISE NOTICE 'Constraint creation query: %', v_sql; END IF; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('New constraint created: %s', v_sql)); END IF; ELSE IF p_debug THEN RAISE NOTICE 'Given column (%) contains all NULLs. No constraint created', v_col; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Given column (%s) contains all NULLs. No constraint created', v_col)); END IF; END IF; END LOOP; IF p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Applying additional constraints: Running analyze on partition set: %s', v_parent_table)); END IF; IF p_debug THEN RAISE NOTICE 'Running analyze on partition set: %', v_parent_table; END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); 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 CONSTRAINT: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * 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 schema given if it doesn't require truncation */ CREATE OR REPLACE FUNCTION check_name_length (p_object_name text, p_suffix text DEFAULT NULL, p_table_partition boolean DEFAULT FALSE) RETURNS text LANGUAGE plpgsql IMMUTABLE AS $$ DECLARE v_new_length int; v_new_name text; BEGIN 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 $$; /* * Function to monitor for data getting inserted into parent tables managed by extension */ CREATE OR REPLACE FUNCTION check_parent(p_exact_count boolean DEFAULT true) RETURNS SETOF @extschema@.check_parent_table LANGUAGE plpgsql STABLE SECURITY DEFINER AS $$ DECLARE v_count bigint = 0; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_row record; v_schemaname text; v_tablename text; v_sql text; v_trouble @extschema@.check_parent_table%rowtype; BEGIN 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'); FOR v_row IN SELECT parent_table FROM @extschema@.part_config LOOP SELECT schemaname, tablename INTO v_schemaname, v_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(v_row.parent_table, '.', 1)::name AND tablename = split_part(v_row.parent_table, '.', 2)::name; IF p_exact_count THEN v_sql := format('SELECT count(1) AS n FROM ONLY %I.%I', v_schemaname, v_tablename); ELSE v_sql := format('SELECT count(1) AS n FROM (SELECT 1 FROM ONLY %I.%I LIMIT 1) x', v_schemaname, v_tablename); END IF; EXECUTE v_sql INTO v_count; IF v_count > 0 THEN v_trouble.parent_table := v_schemaname ||'.'|| v_tablename; v_trouble.count := v_count; RETURN NEXT v_trouble; END IF; v_count := 0; END LOOP; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN; END $$; /* * Check for consistent data in part_config_sub table. Was unable to get this working properly as either a constraint or trigger. * Would either delay raising an error until the next write (which I cannot predict) or disallow future edits to update a sub-partition set's configuration. * This is called by run_maintainance() and at least provides a consistent way to check that I know will run. * If anyone can get a working constraint/trigger, please help! */ CREATE OR REPLACE FUNCTION @extschema@.check_subpart_sameconfig(p_parent_table text) RETURNS TABLE (sub_partition_type text , sub_control text , sub_partition_interval text , sub_constraint_cols text[] , sub_premake int , sub_inherit_fk boolean , sub_retention text , sub_retention_schema text , sub_retention_keep_table boolean , sub_retention_keep_index boolean , sub_use_run_maintenance boolean , sub_epoch boolean , sub_optimize_trigger int , sub_optimize_constraint int , sub_infinite_time_partitions boolean , sub_jobmon boolean) LANGUAGE sql STABLE SECURITY DEFINER SET search_path = @extschema@,pg_temp AS $$ WITH parent_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE n1.nspname = split_part(p_parent_table, '.', 1)::name AND c1.relname = split_part(p_parent_table, '.', 2)::name ) , child_tables AS ( SELECT n.nspname||'.'||c.relname AS tablename FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN parent_info pi ON h.inhparent = pi.oid ) -- Column order here must match the RETURNS TABLE definition SELECT DISTINCT a.sub_partition_type , a.sub_control , a.sub_partition_interval , a.sub_constraint_cols , a.sub_premake , a.sub_inherit_fk , a.sub_retention , a.sub_retention_schema , a.sub_retention_keep_table , a.sub_retention_keep_index , a.sub_use_run_maintenance , a.sub_epoch , a.sub_optimize_trigger , a.sub_optimize_constraint , a.sub_infinite_time_partitions , a.sub_jobmon FROM @extschema@.part_config_sub a JOIN child_tables b on a.sub_parent = b.tablename; $$; /* * Create the trigger function for the parent table of an id-based partition set */ CREATE OR REPLACE FUNCTION create_function_id(p_parent_table text, p_job_id bigint DEFAULT NULL) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_control text; v_count int; v_current_partition_name text; v_current_partition_id bigint; v_datetime_string text; v_final_partition_id bigint; v_function_name 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_position int; v_job_id bigint; v_jobmon text; v_jobmon_schema text; v_last_partition text; v_max bigint; v_next_partition_id bigint; v_next_partition_name text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_premake int; v_prev_partition_id bigint; v_prev_partition_name text; v_row_max_id record; v_run_maint boolean; v_step_id bigint; v_top_parent text := p_parent_table; v_trig_func text; v_trigger_exception_handling boolean; v_optimize_trigger int; BEGIN SELECT partition_interval::bigint , control , premake , optimize_trigger , use_run_maintenance , jobmon , trigger_exception_handling INTO v_partition_interval , v_control , v_premake , v_optimize_trigger , v_run_maint , v_jobmon , v_trigger_exception_handling FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE FUNCTION: %s', p_parent_table)); ELSE v_job_id = p_job_id; END IF; v_step_id := add_step(v_job_id, format('Creating partition function for table %s', p_parent_table)); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); -- Get the highest level top parent if multi-level partitioned in order to get proper max() value below 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 INTO v_higher_parent_schema, v_higher_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 WHERE p.partition_type = 'id'; IF v_higher_parent_table IS NOT NULL THEN -- initially set in DECLARE v_top_parent := v_higher_parent_schema||'.'||v_higher_parent_table; END IF; END LOOP; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_top_parent, 'DESC') LOOP EXECUTE format('SELECT max(%I) FROM %I.%I', v_control, v_row_max_id.partition_schemaname, v_row_max_id.partition_tablename) INTO v_max; IF v_max IS NOT NULL THEN EXIT; END IF; END LOOP; IF v_max IS NULL THEN v_max := 0; END IF; v_current_partition_id = v_max - (v_max % v_partition_interval); v_next_partition_id := v_current_partition_id + v_partition_interval; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_current_partition_id::text, TRUE); v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_current_partition_id bigint; v_current_partition_name text; v_id_position int; v_last_partition text := %L; v_next_partition_id bigint; v_next_partition_name text; v_partition_created boolean; BEGIN IF TG_OP = ''INSERT'' THEN IF NEW.%I >= %s AND NEW.%I < %s THEN ' , v_parent_schema , v_function_name , v_last_partition , v_control , v_current_partition_id , v_control , v_next_partition_id ); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_current_partition_name::name; IF v_count > 0 THEN v_trig_func := v_trig_func || format(' INSERT INTO %I.%I VALUES (NEW.*); ', v_parent_schema, v_current_partition_name); ELSE v_trig_func := v_trig_func || ' -- Child table for current values does not exist in this partition set, so write to parent RETURN NEW;'; END IF; FOR i IN 1..v_optimize_trigger LOOP v_prev_partition_id := v_current_partition_id - (v_partition_interval * i); v_next_partition_id := v_current_partition_id + (v_partition_interval * i); v_final_partition_id := v_next_partition_id + v_partition_interval; v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, v_prev_partition_id::text, TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, v_next_partition_id::text, TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles optimize_trigger being larger than premake (to go back in time further) and edge case of changing optimize_trigger immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_prev_partition_name::name; IF v_count > 0 THEN -- Only handle previous partitions if they're starting above zero IF v_prev_partition_id >= 0 THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %s AND NEW.%I < %s THEN INSERT INTO %I.%I VALUES (NEW.*); ' , v_control , v_prev_partition_id , v_control , v_prev_partition_id + v_partition_interval , v_parent_schema , v_prev_partition_name ); END IF; END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_next_partition_name::name; IF v_count > 0 THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %s AND NEW.%I < %s THEN INSERT INTO %I.%I VALUES (NEW.*);' , v_control , v_next_partition_id , v_control , v_final_partition_id , v_parent_schema , v_next_partition_name ); END IF; END LOOP; v_trig_func := v_trig_func ||format(' ELSE v_current_partition_id := NEW.%I - (NEW.%I %% %s); v_current_partition_name := @extschema@.check_name_length(%L, v_current_partition_id::text, TRUE); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = %L::name AND tablename = v_current_partition_name::name; IF v_count > 0 THEN EXECUTE format(''INSERT INTO %%I.%%I VALUES($1.*)'', %L, v_current_partition_name) USING NEW; ELSE RETURN NEW; END IF; END IF;' , v_control , v_control , v_partition_interval , v_parent_tablename , v_parent_schema , v_parent_schema ); IF v_run_maint IS FALSE THEN v_trig_func := v_trig_func ||format(' v_current_partition_id := NEW.%I - (NEW.%I %% %s); IF (NEW.%I %% %s) > (%s / 2) THEN v_id_position := (length(v_last_partition) - position(''p_'' in reverse(v_last_partition))) + 2; v_next_partition_id := (substring(v_last_partition from v_id_position)::bigint) + %s; WHILE ((v_next_partition_id - v_current_partition_id) / %s) <= %s LOOP v_partition_created := @extschema@.create_partition_id(%L, ARRAY[v_next_partition_id]); IF v_partition_created THEN PERFORM @extschema@.create_function_id(%L); PERFORM @extschema@.apply_constraints(%L); END IF; v_next_partition_id := v_next_partition_id + %s; END LOOP; END IF;' , v_control , v_control , v_partition_interval , v_control , v_partition_interval , v_partition_interval , v_partition_interval , v_partition_interval , v_premake , p_parent_table , p_parent_table , p_parent_table , v_partition_interval ); END IF; v_trig_func := v_trig_func ||' END IF; RETURN NULL;'; IF v_trigger_exception_handling THEN v_trig_func := v_trig_func ||' EXCEPTION WHEN OTHERS THEN RAISE WARNING ''pg_partman insert into child table failed, row inserted into parent (%.%). ERROR: %'', TG_TABLE_SCHEMA, TG_TABLE_NAME, COALESCE(SQLERRM, ''unknown''); RETURN NEW;'; END IF; v_trig_func := v_trig_func ||' END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for current id interval: %s to %s', v_current_partition_id, v_final_partition_id-1)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); 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 FUNCTION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''Partition function maintenance for table %s 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 $$; /* * Create the trigger function for the parent table of a time-based partition set */ CREATE OR REPLACE FUNCTION create_function_time(p_parent_table text, p_job_id bigint DEFAULT NULL) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_control text; v_count int; v_current_partition_name text; v_current_partition_timestamp timestamptz; v_datetime_string text; v_epoch boolean; v_final_partition_timestamp timestamptz; v_function_name text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_new_length int; v_next_partition_name text; v_next_partition_timestamp timestamptz; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_prev_partition_name text; v_prev_partition_timestamp timestamptz; v_step_id bigint; v_trig_func text; v_optimize_trigger int; v_trigger_exception_handling boolean; v_type text; BEGIN SELECT partition_type , partition_interval::interval , epoch , control , optimize_trigger , datetime_string , jobmon , trigger_exception_handling INTO v_type , v_partition_interval , v_epoch , v_control , v_optimize_trigger , v_datetime_string , v_jobmon , v_trigger_exception_handling FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE FUNCTION: %s', p_parent_table)); ELSE v_job_id = p_job_id; END IF; v_step_id := add_step(v_job_id, format('Creating partition function for table %s', p_parent_table)); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); IF v_type = 'time' THEN v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_partition_name text; v_partition_timestamp timestamptz; BEGIN IF TG_OP = ''INSERT'' THEN ' , v_parent_schema , v_function_name); IF v_epoch = false THEN CASE WHEN v_partition_interval = '15 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', NEW.%I) + ''15min''::interval * floor(date_part(''minute'', NEW.%I) / 15.0);' , v_control , v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', NEW.%I) + ''30min''::interval * floor(date_part(''minute'', NEW.%I) / 30.0);' , v_control , v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 day' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''day'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 week' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''week'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 month' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''month'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_partition_interval = '3 months' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''quarter'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 year' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''year'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; ELSE -- epoch is true CASE WHEN v_partition_interval = '15 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', to_timestamp(NEW.%I)) + ''15min''::interval * floor(date_part(''minute'', NEW.%I) / 15.0);' , v_control , v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', to_timestamp(NEW.%I)) + ''30min''::interval * floor(date_part(''minute'', NEW.%I) / 30.0);' , v_control , v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', to_timestamp(NEW.%I));', v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 day' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''day'', to_timestamp(NEW.%I));', v_control); v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 week' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''week'', to_timestamp(NEW.%I));', v_control); v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 month' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''month'', to_timestamp(NEW.%I));', v_control); v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_partition_interval = '3 months' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''quarter'', to_timestamp(NEW.%I));', v_control); v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 year' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''year'', to_timestamp(NEW.%I));', v_control); v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; END IF; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, to_char(v_current_partition_timestamp, v_datetime_string), TRUE); v_next_partition_timestamp := v_current_partition_timestamp + v_partition_interval::interval; IF v_epoch = false THEN v_trig_func := v_trig_func ||format(' IF NEW.%I >= %L AND NEW.%I < %L THEN ' , v_control , v_current_partition_timestamp , v_control , v_next_partition_timestamp); ELSE v_trig_func := v_trig_func ||format(' IF to_timestamp(NEW.%I) >= %L AND to_timestamp(NEW.%I) < %L THEN ' , v_control , v_current_partition_timestamp , v_control , v_next_partition_timestamp); END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_current_partition_name::name; IF v_count > 0 THEN v_trig_func := v_trig_func || format(' INSERT INTO %I.%I VALUES (NEW.*); ', v_parent_schema, v_current_partition_name); ELSE v_trig_func := v_trig_func || ' -- Child table for current values does not exist in this partition set, so write to parent RETURN NEW;'; END IF; FOR i IN 1..v_optimize_trigger LOOP v_prev_partition_timestamp := v_current_partition_timestamp - (v_partition_interval::interval * i); v_next_partition_timestamp := v_current_partition_timestamp + (v_partition_interval::interval * i); v_final_partition_timestamp := v_next_partition_timestamp + (v_partition_interval::interval); v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, to_char(v_prev_partition_timestamp, v_datetime_string), TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, to_char(v_next_partition_timestamp, v_datetime_string), TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles optimize_trigger being larger than premake (to go back in time further) and edge case of changing optimize_trigger immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_prev_partition_name::name; IF v_count > 0 THEN IF v_epoch = false THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %L AND NEW.%I < %L THEN INSERT INTO %I.%I VALUES (NEW.*);' , v_control , v_prev_partition_timestamp , v_control , v_prev_partition_timestamp + v_partition_interval::interval , v_parent_schema , v_prev_partition_name); ELSE v_trig_func := v_trig_func ||format(' ELSIF to_timestamp(NEW.%I) >= %L AND to_timestamp(NEW.%I) < %L THEN INSERT INTO %I.%I VALUES (NEW.*);' , v_control , v_prev_partition_timestamp , v_control , v_prev_partition_timestamp + v_partition_interval::interval , v_parent_schema , v_prev_partition_name); END IF; END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_next_partition_name::name; IF v_count > 0 THEN IF v_epoch = false THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %L AND NEW.%I < %L THEN INSERT INTO %I.%I VALUES (NEW.*);' , v_control , v_next_partition_timestamp , v_control , v_final_partition_timestamp , v_parent_schema , v_next_partition_name); ELSE v_trig_func := v_trig_func ||format(' ELSIF to_timestamp(NEW.%I) >= %L AND to_timestamp(NEW.%I) < %L THEN INSERT INTO %I.%I VALUES (NEW.*);' , v_control , v_next_partition_timestamp , v_control , v_final_partition_timestamp , v_parent_schema , v_next_partition_name); END IF; END IF; END LOOP; v_trig_func := v_trig_func||format(' ELSE v_partition_name := @extschema@.check_name_length(%L, to_char(v_partition_timestamp, %L), TRUE); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = %L::name AND tablename = v_partition_name::name; IF v_count > 0 THEN EXECUTE format(''INSERT INTO %%I.%%I VALUES($1.*)'', %L, v_partition_name) USING NEW; ELSE RETURN NEW; END IF; END IF;' , v_parent_tablename , v_datetime_string , v_parent_schema , v_parent_schema); v_trig_func := v_trig_func ||' END IF; RETURN NULL;'; IF v_trigger_exception_handling THEN v_trig_func := v_trig_func ||' EXCEPTION WHEN OTHERS THEN RAISE WARNING ''pg_partman insert into child table failed, row inserted into parent (%.%). ERROR: %'', TG_TABLE_SCHEMA, TG_TABLE_NAME, COALESCE(SQLERRM, ''unknown''); RETURN NEW;'; END IF; v_trig_func := v_trig_func ||' END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for current time interval: %s to %s' , v_current_partition_timestamp , v_final_partition_timestamp-'1sec'::interval)); END IF; ELSIF v_type = 'time-custom' THEN v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_child_schemaname text; v_child_table text; v_child_tablename text; BEGIN ' , v_parent_schema , v_function_name); IF v_epoch = false THEN v_trig_func := v_trig_func || format(' SELECT child_table INTO v_child_table FROM @extschema@.custom_time_partitions WHERE partition_range @> NEW.%I AND parent_table = %L;' , v_control , v_parent_schema||'.'||v_parent_tablename); ELSE -- epoch true v_trig_func := v_trig_func || format(' SELECT child_table INTO v_child_table FROM @extschema@.custom_time_partitions WHERE partition_range @> to_timestamp(NEW.%I) AND parent_table = %L;' , v_control , v_parent_schema||'.'||v_parent_tablename); END IF; v_trig_func := v_trig_func || ' SELECT schemaname, tablename INTO v_child_schemaname, v_child_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(v_child_table, ''.'', 1)::name AND tablename = split_part(v_child_table, ''.'', 2)::name; IF v_child_schemaname IS NOT NULL AND v_child_tablename IS NOT NULL THEN EXECUTE format(''INSERT INTO %I.%I VALUES ($1.*)'', v_child_schemaname, v_child_tablename) USING NEW; ELSE RETURN NEW; END IF; RETURN NULL;'; IF v_trigger_exception_handling THEN v_trig_func := v_trig_func ||' EXCEPTION WHEN OTHERS THEN RAISE WARNING ''pg_partman insert into child table failed, row inserted into parent (%.%). ERROR: %'', TG_TABLE_SCHEMA, TG_TABLE_NAME, COALESCE(SQLERRM, ''unknown''); RETURN NEW;'; END IF; v_trig_func := v_trig_func ||' END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for custom time table: %s', p_parent_table)); END IF; ELSE RAISE EXCEPTION 'ERROR: Invalid time partitioning type given: %', v_type; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); 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 FUNCTION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''Partition function maintenance for table %s 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 $$; /* * Function to turn a table into the parent of a partition set */ CREATE OR REPLACE FUNCTION 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_use_run_maintenance boolean DEFAULT NULL , p_start_partition text DEFAULT NULL , p_inherit_fk boolean DEFAULT true , p_epoch boolean DEFAULT false , p_jobmon boolean DEFAULT true , p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_base_timestamp timestamp; v_count int := 1; v_datetime_string 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_job_id bigint; v_jobmon_schema text; v_last_partition_created boolean; v_max bigint; v_notnull boolean; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_partition_id bigint; v_parent_partition_timestamp timestamp; v_parent_schema text; v_parent_tablename text; v_partition_time timestamp; v_partition_time_array timestamp[]; v_partition_id_array bigint[]; v_row record; v_run_maint boolean; v_sql text; v_start_time timestamp; v_starting_partition_id bigint; v_step_id bigint; v_step_overflow_id bigint; v_sub_parent text; v_success boolean := false; 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); BEGIN IF position('.' in p_parent_table) = 0 THEN RAISE EXCEPTION 'Parent table must be schema qualified'; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = 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 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; IF p_type = 'id' AND p_epoch = true THEN RAISE EXCEPTION 'p_epoch can only be used with time-based partitioning'; END IF; IF NOT @extschema@.check_partition_type(p_type) THEN RAISE EXCEPTION '% is not a valid partitioning type', p_type; 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'); IF p_use_run_maintenance IS NOT NULL THEN IF p_use_run_maintenance IS FALSE AND (p_type = 'time' OR p_type = 'time-custom') THEN RAISE EXCEPTION 'p_run_maintenance cannot be set to false for time based partitioning'; END IF; v_run_maint := p_use_run_maintenance; ELSIF p_type = 'time' OR p_type = 'time-custom' THEN v_run_maint := TRUE; ELSIF p_type = 'id' THEN v_run_maint := FALSE; ELSE RAISE EXCEPTION 'use_run_maintenance value cannot be set NULL'; END IF; 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 ) 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_use_run_maintenance , sub_epoch , sub_optimize_trigger , sub_optimize_constraint , sub_infinite_time_partitions , sub_jobmon , sub_trigger_exception_handling 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_use_run_maintenance , sub_epoch , sub_optimize_trigger , sub_optimize_constraint , sub_infinite_time_partitions , sub_jobmon , sub_trigger_exception_handling) 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_use_run_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); END LOOP; IF p_type = 'time' OR p_type = 'time-custom' 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 <> 'time-custom' THEN RAISE EXCEPTION 'Must use a predefined time interval if not using type "time-custom". See documentation.'; 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::timestamp, 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))::timestamp; 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 , use_run_maintenance , inherit_fk , jobmon) VALUES ( p_parent_table , p_type , v_time_interval , p_epoch , p_control , p_premake , p_constraint_cols , v_datetime_string , v_run_maint , p_inherit_fk , p_jobmon); 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 -- Currently unknown edge case if code gets here RAISE EXCEPTION 'No child tables created. Unexpected edge case encountered. Please report this error to author with conditions that led to it.'; END IF; END 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 p_type = 'id' THEN v_id_interval := p_interval::bigint; IF v_id_interval < 10 THEN RAISE EXCEPTION 'Interval for serial 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 INTO v_higher_parent_schema, v_higher_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 WHERE p.partition_type = 'id'; IF v_higher_parent_table IS NOT NULL THEN -- 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 , use_run_maintenance , inherit_fk , jobmon) VALUES ( p_parent_table , p_type , v_id_interval , p_control , p_premake , p_constraint_cols , v_run_maint , p_inherit_fk , p_jobmon); 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 WHERE p.partition_type = 'id'; 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 EXCEPTION 'No child tables created. Unexpected edge case encountered. Please report this error to author with conditions that led to it.'; END IF; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating partition function'); END IF; IF p_type = 'time' OR p_type = 'time-custom' 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 p_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); 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 $$; /* * Function to create id partitions */ CREATE OR REPLACE FUNCTION create_partition_id(p_parent_table text, p_partition_ids bigint[], p_analyze boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_exists text; v_grantees text[]; v_hasoids boolean; v_id bigint; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_parent_tablespace text; v_partition_interval bigint; v_partition_created boolean := false; v_partition_name text; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_sub_id_max bigint; v_sub_id_min bigint; v_unlogged char; BEGIN SELECT control , partition_interval , inherit_fk , jobmon INTO v_control , v_partition_interval , v_inherit_fk , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'id'); SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; FOREACH v_id IN ARRAY p_partition_ids LOOP -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_id_min IS NOT NULL THEN IF v_id < v_sub_id_min OR v_id > v_sub_id_max THEN CONTINUE; END IF; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_id::text, TRUE); -- If child table already exists, skip creation SELECT tablename INTO v_exists FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_partition_name::name; IF v_exists IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_id||' to '||(v_id + v_partition_interval)-1); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c 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; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c 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; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %s AND %I < %s )' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_control , v_id , v_control , v_id + v_partition_interval); EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I', v_parent_schema, v_partition_name, v_parent_schema, v_parent_tablename); PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_use_run_maintenance , sub_infinite_time_partitions , sub_jobmon , sub_trigger_exception_handling FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Subpartitioning '||v_partition_name); END IF; 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_use_run_maintenance := %L , p_inherit_fk := %L , p_epoch := %L , p_jobmon := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_use_run_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions , trigger_exception_handling = v_row.sub_trigger_exception_handling WHERE parent_table = v_parent_schema||'.'||v_partition_name; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; -- end sub partitioning LOOP -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table, p_job_id := v_job_id, p_debug := p_debug); v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s', p_parent_table)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to create a child table in a time-based partition set */ CREATE OR REPLACE FUNCTION create_partition_time(p_parent_table text, p_partition_times timestamp[], p_analyze boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_datetime_string text; v_exists text; v_epoch boolean; v_grantees text[]; v_hasoids boolean; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_partition_created boolean := false; v_partition_name text; v_partition_suffix text; v_parent_tablespace text; v_partition_interval interval; v_partition_timestamp_end timestamp; v_partition_timestamp_start timestamp; v_quarter text; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_sub_timestamp_max timestamp; v_sub_timestamp_min timestamp; v_trunc_value text; v_time timestamp; v_type text; v_unlogged char; v_year text; BEGIN SELECT partition_type , control , partition_interval , epoch , inherit_fk , jobmon , datetime_string INTO v_type , v_control , v_partition_interval , v_epoch , v_inherit_fk , v_jobmon , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'time' OR partition_type = 'time-custom'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::timestamp, sub_max::timestamp INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'time'); SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_timestamp_start := v_time; BEGIN v_partition_timestamp_end := v_time + v_partition_interval; 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_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_time||' skipped'); CONTINUE; END; -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_timestamp_min IS NOT NULL THEN IF v_time < v_sub_timestamp_min OR v_time > v_sub_timestamp_max THEN CONTINUE; END IF; END IF; -- This suffix generation code is in partition_data_time() as well v_partition_suffix := to_char(v_time, v_datetime_string); v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); SELECT tablename INTO v_exists FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_partition_name::name; IF v_exists IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Creating new partition %s.%s with interval from %s to %s' , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end-'1sec'::interval)); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c 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; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c 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; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; IF v_epoch = false THEN EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_control , v_partition_timestamp_start , v_control , v_partition_timestamp_end); ELSE EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (to_timestamp(%I) >= %L AND to_timestamp(%I) < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_time_check' , v_control , v_partition_timestamp_start , v_control , v_partition_timestamp_end); END IF; EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); -- If custom time, set extra config options. IF v_type = 'time-custom' THEN INSERT INTO @extschema@.custom_time_partitions (parent_table, child_table, partition_range) VALUES ( p_parent_table, v_parent_schema||'.'||v_partition_name, tstzrange(v_partition_timestamp_start, v_partition_timestamp_end, '[)') ); END IF; PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_use_run_maintenance , sub_infinite_time_partitions , sub_jobmon , sub_trigger_exception_handling FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Subpartitioning %s.%s', v_parent_schema, v_partition_name)); END IF; 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_use_run_maintenance := %L , p_inherit_fk := %L , p_epoch := %L , p_jobmon := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_use_run_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions , trigger_exception_handling = v_row.sub_trigger_exception_handling WHERE parent_table = v_parent_schema||'.'||v_partition_name; END LOOP; -- end sub partitioning LOOP -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table, p_job_id := v_job_id, p_debug := p_debug); v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s. Attempted intervals: %s', p_parent_table, p_partition_times)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; 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_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * 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. * To avoid logical complications and contention issues, ALL subpartitions must be maintained using run_maintenance(). * This means the automatic, trigger based partition creation for serial partitioning will not work if it is a subpartition. */ CREATE OR REPLACE FUNCTION create_sub_parent( p_top_parent text , p_control text , p_type text , p_interval text , 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 boolean DEFAULT false , p_jobmon boolean DEFAULT true , p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_last_partition text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_interval text; v_parent_type text; v_row record; v_row_last_part record; v_run_maint boolean; v_sql text; v_success boolean := false; v_top_type text; BEGIN SELECT use_run_maintenance INTO v_run_maint FROM @extschema@.part_config WHERE parent_table = p_top_parent; IF v_run_maint 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; ELSIF v_run_maint = false THEN RAISE EXCEPTION 'Any parent table that will be part of a sub-partitioned set (on any level) must have use_run_maintenance set to true in part_config table, even for serial partitioning. See documentation for more info.'; END IF; 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'); FOR v_row IN -- Loop through all current children to turn them into partitioned tables SELECT partition_schemaname||'.'||partition_tablename AS child_table FROM @extschema@.show_partitions(p_top_parent) LOOP SELECT partition_type, partition_interval INTO v_parent_type, v_parent_interval FROM @extschema@.part_config WHERE parent_table = v_row.child_table; IF v_parent_interval = p_interval THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RAISE EXCEPTION 'Sub-partition interval cannot be equal to parent interval'; END IF; IF (v_parent_type = 'time' OR v_parent_type = 'time-custom') AND (p_type = 'time' OR p_type = 'time-custom') THEN IF p_interval::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 the given parent interval'; END IF; IF p_interval = 'weekly' 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_parent_type = 'id' 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 the given parent interval'; END IF; END IF; -- Just call existing create_parent() function but add the given parameters to the part_config_sub table as well 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_use_run_maintenance := %L , p_start_partition := %L , p_inherit_fk := %L , p_epoch := %L , p_jobmon := %L , p_debug := %L )' , v_row.child_table , p_control , p_type , p_interval , p_constraint_cols , p_premake , true , p_start_partition , p_inherit_fk , p_epoch , p_jobmon , p_debug); EXECUTE v_sql; END LOOP; 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_use_run_maintenance , sub_epoch , sub_jobmon) VALUES ( p_top_parent , p_control , p_type , p_interval , p_constraint_cols , p_premake , p_inherit_fk , true , p_epoch , p_jobmon); v_success := true; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_success; END $$; /* * Function to create partitioning trigger on parent table */ CREATE OR REPLACE FUNCTION create_trigger(p_parent_table text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_function_name text; v_new_length int; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_trig_name text; v_trig_sql text; BEGIN 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'); SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); -- Ensure function name matches the naming pattern v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); v_trig_sql := format('CREATE TRIGGER %I BEFORE INSERT ON %I.%I FOR EACH ROW EXECUTE PROCEDURE %I.%I()' , v_trig_name , v_parent_schema , v_parent_tablename , v_parent_schema , v_function_name); EXECUTE v_trig_sql; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END $$; /* * Function to drop child tables from an id-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ CREATE OR REPLACE FUNCTION drop_partition_id(p_parent_table text, p_retention bigint DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_control text; v_drop_count int := 0; v_index record; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_max bigint; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_partition_id bigint; v_retention bigint; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_row record; v_row_max_id record; v_step_id bigint; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman drop_partition_id')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_id already running.'; RETURN 0; END IF; -- Allow override of configuration options IF p_retention IS NULL THEN SELECT partition_interval::bigint , control , retention::bigint , retention_keep_table , retention_keep_index , retention_schema , jobmon INTO v_partition_interval , v_control , v_retention , v_retention_keep_table , v_retention_keep_index , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id' AND retention IS NOT NULL; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE SELECT partition_interval::bigint , control , retention_keep_table , retention_keep_index , retention_schema , jobmon INTO v_partition_interval , v_control , v_retention_keep_table , v_retention_keep_index , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; v_retention := p_retention; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%I) FROM %I.%I', v_control, v_row_max_id.partition_schemaname, v_row_max_id.partition_tablename) INTO v_max; IF v_max IS NOT NULL THEN EXIT; END IF; END LOOP; -- Loop through child tables of the given parent FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'ASC') LOOP SELECT child_start_id INTO v_partition_id FROM @extschema@.show_partition_info(v_row.partition_schemaname||'.'||v_row.partition_tablename , v_partition_interval::text , p_parent_table); -- Add one interval since partition names contain the start of the constraint period IF v_retention <= (v_max - (v_partition_id + v_partition_interval)) THEN -- Only create a jobmon entry if there's actual retention work done IF v_jobmon_schema IS NOT NULL AND v_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN DROP ID PARTITION: %s', p_parent_table)); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Uninherit table %s.%s from %s', v_row.partition_schemaname, v_row.partition_tablename, p_parent_table)); END IF; EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_row.partition_schemaname , v_row.partition_tablename , v_parent_schema , v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop table %s.%s', v_row.partition_schemaname, v_row.partition_tablename)); END IF; EXECUTE format('DROP TABLE %I.%I CASCADE', v_row.partition_schemaname, v_row.partition_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN FOR v_index IN WITH child_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE c1.relname = v_row.partition_tablename::name AND n1.nspname = v_row.partition_schema::name ) SELECT c.relname as name , con.conname FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid LEFT JOIN pg_catalog.pg_constraint con ON i.indexrelid = con.conindid JOIN child_info ON i.indrelid = child_info.oid LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop index %s from %s.%s' , v_index.name , v_row.partition_schemaname , v_row.partition_tablename)); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I DROP CONSTRAINT %I', v_row.partition_schemaname, v_row.partition_tablename, v_index.conname); ELSE EXECUTE format('DROP INDEX %I.%I', v_row.partition_schemaname, v_index.name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Moving table %s.%s to schema %s' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema)); END IF; EXECUTE format('ALTER TABLE %I.%I SET SCHEMA %I' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if -- If child table is a subpartition, remove it from part_config & part_config_sub (should cascade due to FK) DELETE FROM @extschema@.part_config WHERE parent_table = v_row.partition_schemaname ||'.'||v_row.partition_tablename; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', format('%s partitions dropped.', v_drop_count)); 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_drop_count; 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 DROP ID PARTITION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to drop child tables from a time-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ CREATE OR REPLACE FUNCTION drop_partition_time(p_parent_table text, p_retention interval DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_datetime_string text; v_drop_count int := 0; v_index record; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_partition_timestamp timestamp; v_retention interval; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_row record; v_step_id bigint; v_type text; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman drop_partition_time')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_time already running.'; RETURN 0; END IF; -- Allow override of configuration options IF p_retention IS NULL THEN SELECT partition_type , partition_interval::interval , retention::interval , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_type , v_partition_interval , v_retention , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom') AND retention IS NOT NULL; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE SELECT partition_type , partition_interval::interval , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_type , v_partition_interval , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); v_retention := p_retention; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- Loop through child tables of the given parent FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'DESC') LOOP -- pull out datetime portion of partition's tablename to make the next one SELECT child_start_time INTO v_partition_timestamp FROM @extschema@.show_partition_info(v_row.partition_schemaname||'.'||v_row.partition_tablename , v_partition_interval::text , p_parent_table); -- Add one interval since partition names contain the start of the constraint period IF v_retention < (CURRENT_TIMESTAMP - (v_partition_timestamp + v_partition_interval)) THEN -- Only create a jobmon entry if there's actual retention work done IF v_jobmon_schema IS NOT NULL AND v_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN DROP TIME PARTITION: %s', p_parent_table)); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Uninherit table %s.%s from %s' , v_row.partition_schemaname , v_row.partition_tablename , p_parent_table)); END IF; EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_row.partition_schemaname , v_row.partition_tablename , v_parent_schema , v_parent_tablename); IF v_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_row.partition_schemaname||'.'||v_row.partition_tablename; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop table %s.%s', v_row.partition_schemaname, v_row.partition_tablename)); END IF; EXECUTE format('DROP TABLE %I.%I CASCADE', v_row.partition_schemaname, v_row.partition_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN FOR v_index IN WITH child_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE c1.relname = v_row.partition_tablename::name AND n1.nspname = v_row.partition_schemaname::name ) SELECT c.relname as name , con.conname FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid LEFT JOIN pg_catalog.pg_constraint con ON i.indexrelid = con.conindid JOIN child_info ON i.indrelid = child_info.oid LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop index %s from %s.%s' , v_index.name , v_row.partition_schemaname , v_row.partition_tablename)); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I DROP CONSTRAINT %I' , v_row.partition_schemaname , v_row.partition_tablename , v_index.conname); ELSE EXECUTE format('DROP INDEX %I.%I', v_parent_schema, v_index.name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Moving table %s.%s to schema %s' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema)); END IF; EXECUTE format('ALTER TABLE %I.%I SET SCHEMA %I', v_row.partition_schemaname, v_row.partition_tablename, v_retention_schema); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if -- If child table is a subpartition, remove it from part_config & part_config_sub (should cascade due to FK) DELETE FROM @extschema@.part_config WHERE parent_table = v_row.partition_schemaname||'.'||v_row.partition_tablename; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', format('%s partitions dropped.', v_drop_count)); 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_drop_count; 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 DROP TIME PARTITION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Populate the child table(s) of an id-based partition set with old data from the original parent */ CREATE OR REPLACE FUNCTION partition_data_id(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval int DEFAULT NULL, p_lock_wait numeric DEFAULT 0, p_order text DEFAULT 'ASC') RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_current_partition_name text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_id bigint; v_min_partition_id bigint; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_partition_id bigint[]; v_rowcount bigint; v_sql text; v_start_control bigint; v_total_rows bigint := 0; BEGIN SELECT partition_interval::bigint , control INTO v_partition_interval , v_control FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; 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'); SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; FOR i IN 1..p_batch_count LOOP IF p_order = 'ASC' THEN EXECUTE format('SELECT min(%I) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; IF v_start_control IS NULL THEN EXIT; END IF; v_min_partition_id = v_start_control - (v_start_control % v_partition_interval); v_partition_id := ARRAY[v_min_partition_id]; -- Check if custom batch interval overflows current partition maximum IF (v_start_control + p_batch_interval) >= (v_min_partition_id + v_partition_interval) THEN v_max_partition_id := v_min_partition_id + v_partition_interval; ELSE v_max_partition_id := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN EXECUTE 'SELECT max('||v_control||') FROM ONLY '||p_parent_table INTO v_start_control; IF v_start_control IS NULL THEN EXIT; END IF; v_min_partition_id = v_start_control - (v_start_control % v_partition_interval); -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_id := v_min_partition_id + v_partition_interval; v_partition_id := ARRAY[v_min_partition_id]; -- Make sure minimum doesn't underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_id THEN v_min_partition_id = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN v_sql := format('SELECT * FROM ONLY %I.%I WHERE %I >= %s AND %I < %s FOR UPDATE NOWAIT' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_id , v_control , v_max_partition_id); EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; PERFORM @extschema@.create_partition_id(p_parent_table, v_partition_id); v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_min_partition_id::text, TRUE); EXECUTE format('WITH partition_data AS ( DELETE FROM ONLY %I.%I WHERE %I >= %s AND %I < %s RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_id , v_control , v_max_partition_id , v_parent_schema , v_current_partition_name); GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; PERFORM @extschema@.create_function_id(p_parent_table); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_total_rows; END $$; /* * Populate the child table(s) of a time-based partition set with old data from the original parent */ CREATE OR REPLACE FUNCTION partition_data_time(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval interval DEFAULT NULL, p_lock_wait numeric DEFAULT 0, p_order text DEFAULT 'ASC') RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_datetime_string text; v_current_partition_name text; v_epoch boolean; v_last_partition text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_timestamp timestamp; v_min_partition_timestamp timestamp; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_partition_suffix text; v_partition_timestamp timestamp[]; v_quarter text; v_rowcount bigint; v_sql text; v_start_control timestamp; v_time_position int; v_total_rows bigint := 0; v_type text; v_year text; BEGIN 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'); SELECT partition_type , partition_interval::interval , control , datetime_string , epoch INTO v_type , v_partition_interval , v_control , v_datetime_string , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); IF NOT FOUND THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; FOR i IN 1..p_batch_count LOOP IF v_epoch = false THEN IF p_order = 'ASC' THEN EXECUTE format('SELECT min(%I) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSIF p_order = 'DESC' THEN EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; ELSE IF p_order = 'ASC' THEN EXECUTE format('SELECT to_timestamp(min(%I)) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSIF p_order = 'DESC' THEN EXECUTE format('SELECT to_timestamp(max(%I)) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; END IF; IF v_start_control IS NULL THEN EXIT; END IF; IF v_type = 'time' THEN CASE WHEN v_partition_interval = '15 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '15min'::interval * floor(date_part('minute', v_start_control) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '30min'::interval * floor(date_part('minute', v_start_control) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control); WHEN v_partition_interval = '1 day' THEN v_min_partition_timestamp := date_trunc('day', v_start_control); WHEN v_partition_interval = '1 week' THEN v_min_partition_timestamp := date_trunc('week', v_start_control); WHEN v_partition_interval = '1 month' THEN v_min_partition_timestamp := date_trunc('month', v_start_control); WHEN v_partition_interval = '3 months' THEN v_min_partition_timestamp := date_trunc('quarter', v_start_control); WHEN v_partition_interval = '1 year' THEN v_min_partition_timestamp := date_trunc('year', v_start_control); END CASE; ELSIF v_type = 'time-custom' THEN SELECT child_start_time INTO v_min_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition , v_partition_interval::text , p_parent_table); v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; LOOP IF v_start_control >= v_min_partition_timestamp AND v_start_control < v_max_partition_timestamp THEN EXIT; ELSE BEGIN IF v_start_control > v_max_partition_timestamp THEN -- Keep going forward in time, checking if child partition time interval encompasses the current v_start_control value v_min_partition_timestamp := v_max_partition_timestamp; v_max_partition_timestamp := v_max_partition_timestamp + v_partition_interval; ELSE -- Keep going backwards in time, checking if child partition time interval encompasses the current v_start_control value v_max_partition_timestamp := v_min_partition_timestamp; v_min_partition_timestamp := v_min_partition_timestamp - v_partition_interval; END IF; EXCEPTION WHEN datetime_field_overflow THEN RAISE EXCEPTION 'Attempted partition time interval is outside PostgreSQL''s supported time range. Unable to create partition with interval before timestamp % ', v_min_partition_interval; END; END IF; END LOOP; END IF; v_partition_timestamp := ARRAY[v_min_partition_timestamp]; IF p_order = 'ASC' THEN -- Ensure batch interval given as parameter doesn't cause maximum to overflow the current partition maximum IF (v_start_control + p_batch_interval) >= (v_min_partition_timestamp + v_partition_interval) THEN v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; ELSE v_max_partition_timestamp := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; -- Ensure batch interval given as parameter doesn't cause minimum to underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_timestamp THEN v_min_partition_timestamp = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN IF v_epoch = false THEN v_sql := format('SELECT * FROM ONLY %I.%I WHERE %I >= %L AND %I < %L FOR UPDATE NOWAIT' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_timestamp , v_control , v_max_partition_timestamp); ELSE v_sql := format('SELECT * FROM ONLY %I.%I WHERE to_timestamp(%I) >= %L AND to_timestamp(%I) < %L FOR UPDATE NOWAIT' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_timestamp , v_control , v_max_partition_timestamp); END IF; EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; PERFORM @extschema@.create_partition_time(p_parent_table, v_partition_timestamp); -- This suffix generation code is in create_partition_time() as well v_partition_suffix := to_char(v_min_partition_timestamp, v_datetime_string); v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); IF v_epoch = false THEN v_sql := format('WITH partition_data AS ( DELETE FROM ONLY %I.%I WHERE %I >= %L AND %I < %L RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_timestamp , v_control , v_max_partition_timestamp , v_parent_schema , v_current_partition_name); ELSE v_sql := format('WITH partition_data AS ( DELETE FROM ONLY %I.%I WHERE to_timestamp(%I) >= %L AND to_timestamp(%I) < %L RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_timestamp , v_control , v_max_partition_timestamp , v_parent_schema , v_current_partition_name); END IF; EXECUTE v_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; PERFORM @extschema@.create_function_time(p_parent_table); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_total_rows; END $$; /* * Function to re-apply ownership & privileges on all child tables in a partition set using parent table as reference */ CREATE OR REPLACE FUNCTION reapply_privileges(p_parent_table text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_row record; v_step_id bigint; BEGIN SELECT jobmon INTO v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon IS NULL THEN RAISE EXCEPTION 'Given table is not managed by this extention: %', p_parent_table; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; IF v_parent_tablename IS NULL THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RAISE EXCEPTION 'Given parent table does not exist: %', p_parent_table; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN RE-APPLYING PRIVILEGES TO ALL CHILD TABLES OF: %s', p_parent_table)); END IF; FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'ASC') LOOP PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_row.partition_schemaname, v_row.partition_tablename, v_job_id); END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); 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 RE-APPLYING PRIVILEGES TO ALL CHILD TABLES OF: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to manage pre-creation of the next partitions in a set. * Also manages dropping old partitions if the retention option is set. * If p_parent_table is passed, will only run run_maintenance() on that one table (no matter what the configuration table may have set for it) * Otherwise, will run on all tables in the config table with p_run_maintenance() set to true. * For large partition sets, running analyze can cause maintenance to take longer than expected. Can set p_analyze to false to avoid a forced analyze run. * Be aware that constraint exclusion may not work properly until an analyze on the partition set is run. */ CREATE OR REPLACE FUNCTION run_maintenance(p_parent_table text DEFAULT NULL, p_analyze boolean DEFAULT true, p_jobmon boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_check_subpart int; v_create_count int := 0; v_current_partition text; v_current_partition_id bigint; v_current_partition_timestamp timestamp; v_datetime_string text; v_drop_count int := 0; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_created boolean; v_last_partition_id bigint; v_last_partition_timestamp timestamp; v_max_id_parent bigint; v_max_time_parent timestamp; v_new_search_path text := '@extschema@,pg_temp'; v_next_partition_id bigint; v_next_partition_timestamp timestamp; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_premade_count int; v_premake_id_max bigint; v_premake_id_min bigint; v_premake_timestamp_min timestamp; v_premake_timestamp_max timestamp; v_row record; v_row_max_id record; v_row_max_time record; v_row_sub record; v_skip_maint boolean; v_step_id bigint; v_step_overflow_id bigint; v_sub_id_max bigint; v_sub_id_max_suffix bigint; v_sub_id_min bigint; v_sub_parent text; v_sub_timestamp_max timestamp; v_sub_timestamp_max_suffix timestamp; v_sub_timestamp_min timestamp; v_tablename text; v_tables_list_sql text; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman maintenance already running.'; RETURN; 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'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN RUN MAINTENANCE'); v_step_id := add_step(v_job_id, 'Running maintenance loop'); END IF; v_row := NULL; -- Ensure it's reset v_tables_list_sql := 'SELECT parent_table , partition_type , partition_interval , control , premake , datetime_string , undo_in_progress , sub_partition_set_full , epoch , infinite_time_partitions FROM @extschema@.part_config WHERE sub_partition_set_full = false'; IF p_parent_table IS NULL THEN v_tables_list_sql := v_tables_list_sql || ' AND use_run_maintenance = true'; ELSE v_tables_list_sql := v_tables_list_sql || format(' AND parent_table = %L', p_parent_table); END IF; FOR v_row IN EXECUTE v_tables_list_sql LOOP CONTINUE WHEN v_row.undo_in_progress; v_skip_maint := true; -- reset every loop -- Check for consistent data in part_config_sub table. Was unable to get this working properly as either a constraint or trigger. -- Would either delay raising an error until the next write (which I cannot predict) or disallow future edits to update a sub-partition set's configuration. -- This way at least provides a consistent way to check that I know will run. If anyone can get a working constraint/trigger, please help! -- Don't have to worry about this in the serial trigger maintenance since subpartitioning requires run_maintenance(). SELECT sub_parent INTO v_sub_parent FROM @extschema@.part_config_sub WHERE sub_parent = v_row.parent_table; IF v_sub_parent IS NOT NULL THEN SELECT count(*) INTO v_check_subpart FROM @extschema@.check_subpart_sameconfig(v_row.parent_table); IF v_check_subpart > 1 THEN RAISE EXCEPTION 'Inconsistent data in part_config_sub table. Sub-partition tables that are themselves sub-partitions cannot have differing configuration values among their siblings. Run this query: "SELECT * FROM @extschema@.check_subpart_sameconfig(''%'');" This should only return a single row or nothing. If multiple rows are returned, results are all children of the given parent. Update the differing values to be consistent for your desired values.', v_row.sub_parent; END IF; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(v_row.parent_table, '.', 1)::name AND tablename = split_part(v_row.parent_table, '.', 2)::name; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LIMIT 1; IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_last_partition: %', v_row.parent_table, v_last_partition; END IF; IF v_row.partition_type = 'time' OR v_row.partition_type = 'time-custom' THEN SELECT child_start_time INTO v_last_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_row.partition_interval, v_row.parent_table); -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_time IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP IF v_row.epoch = false THEN EXECUTE format('SELECT max(%I)::text FROM %I.%I' , v_row.control , v_row_max_time.partition_schemaname , v_row_max_time.partition_tablename ) INTO v_current_partition_timestamp; ELSE EXECUTE format('SELECT to_timestamp(max(%I))::text FROM %I.%I' , v_row.control , v_row_max_time.partition_schemaname , v_row_max_time.partition_tablename ) INTO v_current_partition_timestamp; END IF; IF v_current_partition_timestamp IS NOT NULL THEN SELECT suffix_timestamp INTO v_current_partition_timestamp FROM @extschema@.show_partition_name(v_row.parent_table, v_current_partition_timestamp::text); EXIT; END IF; END LOOP; -- Check for values in the parent table. If they are there and greater than all child values, use that instead -- This allows maintenance to continue working properly if there is a large gap in data insertion. Data will remain in parent, but new tables will be created IF v_row.epoch = false THEN EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_row.control, v_parent_schema, v_parent_tablename) INTO v_max_time_parent; ELSE EXECUTE format('SELECT to_timestamp(max(%I)) FROM ONLY %I.%I', v_row.control, v_parent_schema, v_parent_tablename) INTO v_max_time_parent; END IF; IF p_debug THEN RAISE NOTICE 'run_maint: v_current_partition_timestamp: %, v_max_time_parent: %', v_current_partition_timestamp, v_max_time_parent; END IF; IF v_max_time_parent > v_current_partition_timestamp THEN SELECT suffix_timestamp INTO v_current_partition_timestamp FROM @extschema@.show_partition_name(v_row.parent_table, v_max_time_parent::text); END IF; IF v_current_partition_timestamp IS NULL THEN -- Partition set is completely empty IF v_row.infinite_time_partitions IS TRUE THEN -- Set it to now so new partitions continue to be created v_current_partition_timestamp = CURRENT_TIMESTAMP; ELSE -- Nothing to do CONTINUE; END IF; END IF; -- If this is a subpartition, determine if the last child table has been made. If so, mark it as full so future maintenance runs can skip it SELECT sub_min::timestamp, sub_max::timestamp INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'time'); IF v_sub_timestamp_max IS NOT NULL THEN SELECT suffix_timestamp INTO v_sub_timestamp_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_timestamp_max::text); IF v_sub_timestamp_max_suffix = v_last_partition_timestamp THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; -- Check and see how many premade partitions there are. v_premade_count = round(EXTRACT('epoch' FROM age(v_last_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); v_next_partition_timestamp := v_last_partition_timestamp; IF p_debug THEN RAISE NOTICE 'run_maint before loop: current_partition_timestamp: %, v_premade_count: %, v_sub_timestamp_min: %, v_sub_timestamp_max: %' , v_current_partition_timestamp , v_premade_count , v_sub_timestamp_min , v_sub_timestamp_max; END IF; -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed WHILE (v_premade_count < v_row.premake) LOOP IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_premade_count: %, v_next_partition_timestamp: %', v_row.parent_table, v_premade_count, v_next_partition_timestamp; END IF; IF v_next_partition_timestamp < v_sub_timestamp_min OR v_next_partition_timestamp > v_sub_timestamp_max THEN -- With subpartitioning, no need to run if the timestamp is not in the parent table's range EXIT; END IF; BEGIN v_next_partition_timestamp := v_next_partition_timestamp + v_row.partition_interval::interval; EXCEPTION WHEN datetime_field_overflow THEN v_premade_count := v_row.premake; -- do this so it can exit the premake check loop and continue in the outer for loop IF v_jobmon_schema IS NOT NULL THEN 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', format('Child partition creation skipped for parent table: %s', v_partition_time)); END IF; RAISE WARNING 'Attempted partition time interval is outside PostgreSQL''s supported time range. Child partition creation skipped for parent table %', v_row.parent_table; CONTINUE; END; v_last_partition_created := @extschema@.create_partition_time(v_row.parent_table, ARRAY[v_next_partition_timestamp], p_analyze); IF v_last_partition_created THEN v_create_count := v_create_count + 1; PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); END IF; v_premade_count = round(EXTRACT('epoch' FROM age(v_next_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); END LOOP; ELSIF v_row.partition_type = 'id' THEN -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%I)::text FROM %I.%I' , v_row.control , v_row_max_id.partition_schemaname , v_row_max_id.partition_tablename) INTO v_current_partition_id; IF v_current_partition_id IS NOT NULL THEN SELECT suffix_id INTO v_current_partition_id FROM @extschema@.show_partition_name(v_row.parent_table, v_current_partition_id::text); EXIT; END IF; END LOOP; -- Check for values in the parent table. If they are there and greater than all child values, use that instead -- This allows maintenance to continue working properly if there is a large gap in data insertion. Data will remain in parent, but new tables will be created EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_row.control, v_parent_schema, v_parent_tablename) INTO v_max_id_parent; IF v_max_id_parent > v_current_partition_id THEN SELECT suffix_id INTO v_current_partition_id FROM @extschema@.show_partition_name(v_row.parent_table, v_max_id_parent::text); END IF; IF v_current_partition_id IS NULL THEN -- Partition set is completely empty. Nothing to do CONTINUE; END IF; SELECT child_start_id INTO v_last_partition_id FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_row.partition_interval, v_row.parent_table); -- Determine if this table is a child of a subpartition parent. If so, get limits to see if run_maintenance even needs to run for it. SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'id'); IF v_sub_id_max IS NOT NULL THEN SELECT suffix_id INTO v_sub_id_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_id_max::text); IF v_sub_id_max_suffix = v_last_partition_id THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; v_next_partition_id := v_last_partition_id; v_premade_count := ((v_last_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed. WHILE (v_premade_count < v_row.premake) LOOP IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_premade_count: %, v_next_partition_id: %', v_row.parent_table, v_premade_count, v_next_partition_id; END IF; IF v_next_partition_id < v_sub_id_min OR v_next_partition_id > v_sub_id_max THEN -- With subpartitioning, no need to run if the id is not in the parent table's range EXIT; END IF; v_next_partition_id := v_next_partition_id + v_row.partition_interval::bigint; v_last_partition_created := @extschema@.create_partition_id(v_row.parent_table, ARRAY[v_next_partition_id], p_analyze); IF v_last_partition_created THEN v_create_count := v_create_count + 1; PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); END IF; v_premade_count := ((v_next_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); END LOOP; END IF; -- end main IF check for time or id END LOOP; -- end of creation loop -- Manage dropping old partitions if retention option is set FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND (partition_type = 'time' OR partition_type = 'time-custom') LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); END IF; END IF; IF v_drop_count > 0 THEN PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); END IF; END LOOP; FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND partition_type = 'id' LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); END IF; END IF; IF v_drop_count > 0 THEN PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Partition maintenance finished. %s partitions made. %s partitions dropped.', v_create_count, v_drop_count)); 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'); 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 RUN MAINTENANCE'')', v_jobmon_schema) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Show the data boundries for a given child table as well as the suffix that will be used. * Passing the parent table argument improves performance by avoiding a catalog lookup. * Passing an interval lets you set one different than the default configured one if desired. */ CREATE OR REPLACE FUNCTION show_partition_info(p_child_table text , p_partition_interval text DEFAULT NULL , p_parent_table text DEFAULT NULL , OUT child_start_time timestamp , OUT child_end_time timestamp , OUT child_start_id bigint , OUT child_end_id bigint , OUT suffix text) RETURNS record LANGUAGE plpgsql STABLE SECURITY DEFINER AS $$ DECLARE v_child_schema text; v_child_tablename text; v_datetime_string text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_table text; v_partition_interval text; v_partition_type text; v_quarter text; v_suffix text; v_suffix_position int; v_year text; BEGIN 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'); SELECT schemaname, tablename INTO v_child_schema, v_child_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_child_table, '.', 1)::name AND tablename = split_part(p_child_table, '.', 2)::name; IF v_child_tablename IS NULL THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RAISE EXCEPTION 'Child table given does not exist (%)', p_child_table; END IF; IF p_parent_table IS NULL THEN SELECT n.nspname||'.'|| c.relname INTO v_parent_table FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhparent JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhrelid::regclass = p_child_table::regclass; ELSE v_parent_table := p_parent_table; END IF; IF p_partition_interval IS NULL THEN SELECT partition_interval, partition_type, datetime_string INTO v_partition_interval, v_partition_type, v_datetime_string FROM @extschema@.part_config WHERE parent_table = v_parent_table; ELSE v_partition_interval := p_partition_interval; SELECT partition_type, datetime_string INTO v_partition_type, v_datetime_string FROM @extschema@.part_config WHERE parent_table = v_parent_table; END IF; IF v_partition_type IS NULL THEN RAISE EXCEPTION 'Parent table of given child not managed by pg_partman: %', v_parent_table; END IF; v_suffix_position := (length(v_child_tablename) - position('p_' in reverse(v_child_tablename))) + 2; v_suffix := substring(v_child_tablename from v_suffix_position); IF v_partition_type = 'time' OR v_partition_type = 'time-custom' THEN IF v_partition_interval::interval <> '3 months' OR (v_partition_interval::interval = '3 months' AND v_partition_type = 'time-custom') THEN child_start_time := to_timestamp(v_suffix, v_datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(v_suffix, 'q', 1); v_quarter := split_part(v_suffix, 'q', 2); CASE WHEN v_quarter = '1' THEN child_start_time := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN child_start_time := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN child_start_time := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN child_start_time := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; child_end_time := (child_start_time + v_partition_interval::interval) - '1 second'::interval; ELSIF v_partition_type = 'id' THEN child_start_id := v_suffix::bigint; child_end_id := (child_start_id + v_partition_interval::bigint) - 1; ELSE RAISE EXCEPTION 'Invalid partition type encountered in show_partition_info()'; END IF; suffix = v_suffix; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN; END $$; /* * Function to list all child partitions in a set in logical order. */ CREATE OR REPLACE FUNCTION show_partitions (p_parent_table text, p_order text DEFAULT 'ASC') RETURNS TABLE (partition_schemaname text, partition_tablename text) LANGUAGE plpgsql STABLE SECURITY DEFINER AS $$ DECLARE v_datetime_string text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval text; v_quarter text; v_type text; v_year text; BEGIN 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'); IF upper(p_order) NOT IN ('ASC', 'DESC') THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RAISE EXCEPTION 'p_order paramter must be one of the following values: ASC, DESC'; END IF; SELECT partition_type , partition_interval , datetime_string INTO v_type , v_partition_interval , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; IF v_type IN ('time', 'time-custom') THEN IF v_partition_interval::interval <> '3 months' OR (v_partition_interval::interval = '3 months' AND v_type = 'time-custom') THEN RETURN QUERY EXECUTE format('SELECT n.nspname::text AS partition_schemaname, c.relname::text AS partition_name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = ''%I.%I''::regclass ORDER BY to_timestamp(substring(c.relname from ((length(c.relname) - position(''p_'' in reverse(c.relname))) + 2) ), %L) %s' , v_parent_schema , v_parent_tablename , v_datetime_string , p_order); ELSE -- For quarterly, to_timestamp() doesn't recognize "Q" in datetime string. -- First order by just the year, then order by the quarter number (should be last character in table name) RETURN QUERY EXECUTE format('SELECT n.nspname::text AS partition_schemaname, c.relname::text AS partition_name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = ''%I.%I''::regclass ORDER BY to_timestamp(substring(c.relname from ((length(c.relname) - position(''p_'' in reverse(c.relname))) + 2) for 4), ''YYYY'') %s , substring(reverse(c.relname) from 1 for 1) %s' , v_parent_schema , v_parent_tablename , p_order , p_order); END IF; ELSIF v_type = 'id' THEN RETURN QUERY EXECUTE format('SELECT n.nspname::text AS partition_schemaname, c.relname::text AS partition_name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = ''%I.%I''::regclass ORDER BY substring(c.relname from ((length(c.relname) - position(''p_'' in reverse(c.relname))) + 2) )::bigint %s' , v_parent_schema , v_parent_tablename , p_order); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END $$; /* * Function to undo partitioning. Copies data to parent without removing any data from children. * Will actually work on any parent/child table set, not just ones created by pg_partman. */ CREATE OR REPLACE FUNCTION undo_partition(p_parent_table text, p_batch_count int DEFAULT 1, p_keep_table boolean DEFAULT true, p_jobmon boolean DEFAULT true, p_lock_wait numeric DEFAULT 0) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_batch_loop_count bigint := 0; v_child_count bigint; v_child_table text; v_copy_sql text; v_function_name text; v_job_id bigint; v_jobmon_schema text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_rowcount bigint; v_step_id bigint; v_total bigint := 0; v_trig_name text; v_type text; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition already running.'; RETURN 0; 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'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN UNDO PARTITIONING: %s', p_parent_table)); v_step_id := add_step(v_job_id, format('Undoing partitioning for table %s', p_parent_table)); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables and stop new id partitions from being made. v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t JOIN pg_catalog.pg_class c ON t.tgrelid = c.oid WHERE tgname = v_trig_name::name AND c.relname = v_parent_tablename::name; SELECT proname INTO v_function_name FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid WHERE n.nspname = v_parent_schema::name AND proname = v_function_name::name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_parent_tablename); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; RETURN -1; END IF; END IF; -- END p_lock_wait IF EXECUTE format('DROP TRIGGER IF EXISTS %I ON %I.%I', v_trig_name, v_parent_schema, v_parent_tablename); END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later IF v_function_name IS NOT NULL THEN EXECUTE format('DROP FUNCTION IF EXISTS %I.%I()', v_parent_schema, v_function_name); END IF; IF v_jobmon_schema IS NOT NULL THEN IF (v_trig_name IS NOT NULL OR v_function_name IS NOT NULL) THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); ELSE PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process.'); END IF; END IF; WHILE v_batch_loop_count < p_batch_count LOOP -- Get ordered list of child table in set. Store in variable one at a time per loop until none are left. -- Not using show_partitions() so it can work on non-pg_partman partition sets WITH parent_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE c1.relname = v_parent_tablename::name AND n1.nspname = v_parent_schema::name ) SELECT c.relname INTO v_child_table FROM pg_catalog.pg_inherits i JOIN pg_catalog.pg_class c ON i.inhrelid = c.oid JOIN parent_info p ON i.inhparent = p.oid ORDER BY i.inhrelid ASC; EXIT WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Removing child partition: %s.%s', v_parent_schema, v_child_table)); END IF; -- lockwait timeout for table drop v_lock_obtained := FALSE; -- reset for reuse in loop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_child_table); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; RETURN -1; END IF; END IF; -- END p_lock_wait IF v_copy_sql := format('INSERT INTO %I.%I SELECT * FROM %I.%I' , v_parent_schema , v_parent_tablename , v_parent_schema , v_child_table); EXECUTE v_copy_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_parent_schema , v_child_table , v_parent_schema , v_parent_tablename); IF p_keep_table = false THEN EXECUTE format('DROP TABLE %I.%I', v_parent_schema, v_child_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table DROPPED. Moved %s rows to parent', v_rowcount)); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table UNINHERITED, not DROPPED. Copied %s rows to parent', v_rowcount)); END IF; END IF; SELECT partition_type INTO v_type FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_parent_schema||'.'||v_child_table; END IF; v_batch_loop_count := v_batch_loop_count + 1; v_undo_count := v_undo_count + 1; END LOOP; -- v_batch_loop_count IF v_undo_count = 0 THEN -- FOR loop never ran, so there's no child tables left. DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman (if it existed)'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) from % child table(s) to the parent: %', v_total, v_undo_count, p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', format('Copied %s row(s) from %s child table(s) to the parent', v_total, v_undo_count)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_total; 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 UNDO PARTITIONING: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to undo id-based partitioning created by this extension */ CREATE OR REPLACE FUNCTION undo_partition_id(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval bigint DEFAULT NULL, p_keep_table boolean DEFAULT true, p_lock_wait numeric DEFAULT 0) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_batch_loop_count int := 0; v_child_loop_total bigint := 0; v_child_min bigint; v_child_table text; v_control text; v_exists int; v_function_name text; v_inner_loop_count int; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_move_sql text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_row record; v_rowcount bigint; v_step_id bigint; v_sub_count int; v_trig_name text; v_total bigint := 0; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition_id')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition_id already running.'; RETURN 0; END IF; SELECT partition_interval::bigint , control , jobmon INTO v_partition_interval , v_control , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Check if any child tables are themselves partitioned or part of an inheritance tree. Prevent undo at this level if so. -- Need to either lock child tables at all levels or handle the proper removal of triggers on all child tables first -- before multi-level undo can be performed safely. FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table) LOOP SELECT count(*) INTO v_sub_count FROM pg_catalog.pg_inherits i JOIN pg_catalog.pg_class c ON i.inhparent = c.oid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_row.partition_tablename::name AND n.nspname = v_row.partition_schemaname::name; IF v_sub_count > 0 THEN RAISE EXCEPTION 'Child table for this parent has child table(s) itself (%). Run undo partitioning on this table or remove inheritance first to ensure all data is properly moved to parent', v_row.partition_schemaname||'.'||v_row.partition_tablename; END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN UNDO PARTITIONING: %s', p_parent_table)); v_step_id := add_step(v_job_id, format('Undoing partitioning for table %s', p_parent_table)); END IF; IF p_batch_interval IS NULL THEN p_batch_interval := v_partition_interval; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables and stop new id partitions from being made. v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t JOIN pg_catalog.pg_class c ON t.tgrelid = c.oid WHERE tgname = v_trig_name::name AND c.relname = v_parent_tablename::name; SELECT proname INTO v_function_name FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid WHERE n.nspname = v_parent_schema::name AND proname = v_function_name::name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_parent_tablename); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; RETURN -1; END IF; END IF; -- END p_lock_wait IF EXECUTE format('DROP TRIGGER IF EXISTS %I ON %I.%I', v_trig_name, v_parent_schema, v_parent_tablename); END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later IF v_function_name IS NOT NULL THEN EXECUTE format('DROP FUNCTION IF EXISTS %I.%I()', v_parent_schema, v_function_name); END IF; IF v_jobmon_schema IS NOT NULL THEN IF (v_trig_name IS NOT NULL OR v_function_name IS NOT NULL) THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); ELSE PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process.'); END IF; END IF; <> LOOP -- Get ordered list of child table in set. Store in variable one at a time per loop until none are left or batch count is reached. -- This easily allows it to loop over same child table until empty or move onto next child table after it's dropped SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC') LIMIT 1; EXIT WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Removing child partition: %s.%s', v_parent_schema, v_child_table)); END IF; EXECUTE format('SELECT min(%I) FROM %I.%I', v_control, v_parent_schema, v_child_table) INTO v_child_min; IF v_child_min IS NULL THEN -- No rows left in this child table. Remove from partition set. -- lockwait timeout for table drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_child_table); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; RETURN -1; END IF; END IF; -- END p_lock_wait IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_parent_schema , v_child_table , v_parent_schema , v_parent_tablename); IF p_keep_table = false THEN EXECUTE format('DROP TABLE %I.%I', v_parent_schema, v_child_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table DROPPED. Moved %s rows to parent', v_child_loop_total)); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table UNINHERITED, not DROPPED. Moved %s rows to parent', v_child_loop_total)); END IF; END IF; v_undo_count := v_undo_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached CONTINUE outer_child_loop; -- skip data moving steps below END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP -- lockwait timeout for row batches IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM %I.%I WHERE %I <= %s FOR UPDATE NOWAIT' , v_parent_schema , v_child_table , v_control , v_child_min + (p_batch_interval * v_inner_loop_count)); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; RETURN -1; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval v_move_sql := format('WITH move_data AS ( DELETE FROM %I.%I WHERE %I <= %s RETURNING *) INSERT INTO %I.%I SELECT * FROM move_data' , v_parent_schema , v_child_table , v_control , v_child_min + (p_batch_interval * v_inner_loop_count) , v_parent_schema , v_parent_tablename); EXECUTE v_move_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Moved %s rows to parent.', v_child_loop_total)); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; -- Check again if table is empty and go to outer loop again to drop it if so EXECUTE format('SELECT min(%I) FROM %I.%I', v_control, v_parent_schema, v_child_table) INTO v_child_min; CONTINUE outer_child_loop WHEN v_child_min IS NULL; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC') LIMIT 1; IF v_child_table IS NULL THEN DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) to the parent. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', format('Copied %s row(s) to the parent. Removed %s partitions.', v_total, v_undo_count)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_total; 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 UNDO PARTITIONING: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to undo time-based partitioning created by this extension */ CREATE OR REPLACE FUNCTION undo_partition_time(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval interval DEFAULT NULL, p_keep_table boolean DEFAULT true, p_lock_wait numeric DEFAULT 0) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_batch_loop_count int := 0; v_child_loop_total bigint := 0; v_child_min timestamptz; v_child_table text; v_control text; v_epoch boolean; v_function_name text; v_inner_loop_count int; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_move_sql text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_row record; v_rowcount bigint; v_step_id bigint; v_sub_count int; v_total bigint := 0; v_trig_name text; v_type text; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition_time')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition_time already running.'; RETURN 0; END IF; SELECT partition_type , partition_interval::interval , control , jobmon , epoch INTO v_type , v_partition_interval , v_control , v_jobmon , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Check if any child tables are themselves partitioned or part of an inheritance tree. Prevent undo at this level if so. -- Need to either lock child tables at all levels or handle the proper removal of triggers on all child tables first -- before multi-level undo can be performed safely. FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table) LOOP SELECT count(*) INTO v_sub_count FROM pg_catalog.pg_inherits i JOIN pg_catalog.pg_class c ON i.inhparent = c.oid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_row.partition_tablename::name AND n.nspname = v_row.partition_schemaname::name; IF v_sub_count > 0 THEN RAISE EXCEPTION 'Child table for this parent has child table(s) itself (%). Run undo partitioning on this table or remove inheritance first to ensure all data is properly moved to parent', v_row.partition_schemaname||'.'||v_row.partition_tablename; END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN UNDO PARTITIONING: %s', p_parent_table)); v_step_id := add_step(v_job_id, format('Undoing partitioning for table %s', p_parent_table)); END IF; IF p_batch_interval IS NULL THEN p_batch_interval := v_partition_interval; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables. v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t JOIN pg_catalog.pg_class c ON t.tgrelid = c.oid WHERE tgname = v_trig_name::name AND c.relname = v_parent_tablename::name; SELECT proname INTO v_function_name FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid WHERE n.nspname = v_parent_schema::name AND proname = v_function_name::name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_parent_tablename); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; RETURN -1; END IF; END IF; -- END p_lock_wait IF EXECUTE format('DROP TRIGGER IF EXISTS %I ON %I.%I', v_trig_name, v_parent_schema, v_parent_tablename); END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later IF v_function_name IS NOT NULL THEN EXECUTE format('DROP FUNCTION IF EXISTS %I.%I()', v_parent_schema, v_function_name); END IF; IF v_jobmon_schema IS NOT NULL THEN IF (v_trig_name IS NOT NULL OR v_function_name IS NOT NULL) THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); ELSE PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process.'); END IF; END IF; <> LOOP -- Get ordered list of child table in set. Store in variable one at a time per loop until none are left or batch count is reached. -- This easily allows it to loop over same child table until empty or move onto next child table after it's dropped SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC') LIMIT 1; EXIT outer_child_loop WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Removing child partition: %s.%s', v_parent_schema, v_child_table)); END IF; IF v_epoch = false THEN EXECUTE format('SELECT min(%I) FROM %I.%I', v_control, v_parent_schema, v_child_table) INTO v_child_min; ELSE EXECUTE format('SELECT to_timestamp(min(%I)) FROM %I.%I', v_control, v_parent_schema, v_child_table) INTO v_child_min; END IF; IF v_child_min IS NULL THEN -- No rows left in this child table. Remove from partition set. -- lockwait timeout for table drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_child_table); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; RETURN -1; END IF; END IF; -- END p_lock_wait IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_parent_schema , v_child_table , v_parent_schema , v_parent_tablename); IF p_keep_table = false THEN EXECUTE format('DROP TABLE %I.%I', v_parent_schema, v_child_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table DROPPED. Moved %s rows to parent', v_child_loop_total)); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table UNINHERITED, not DROPPED. Moved %s rows to parent', v_child_loop_total)); END IF; END IF; IF v_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_parent_schema||'.'||v_child_table; END IF; v_undo_count := v_undo_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached CONTINUE outer_child_loop; -- skip data moving steps below END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM %I.%I WHERE %I <= %L FOR UPDATE NOWAIT' , v_parent_schema , v_child_table , v_control , v_child_min + (p_batch_interval * v_inner_loop_count)); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; RETURN -1; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval IF v_epoch = false THEN v_move_sql := format('WITH move_data AS ( DELETE FROM %I.%I WHERE %I <= %L RETURNING *) INSERT INTO %I.%I SELECT * FROM move_data' , v_parent_schema , v_child_table , v_control , v_child_min + (p_batch_interval * v_inner_loop_count) , v_parent_schema , v_parent_tablename); ELSE v_move_sql := format('WITH move_data AS ( DELETE FROM %I.%I WHERE to_timestamp(%I) <= %L RETURNING *) INSERT INTO %I.%I SELECT * FROM move_data' , v_parent_schema , v_child_table , v_control , v_child_min + (p_batch_interval * v_inner_loop_count) , v_parent_schema , v_parent_tablename); END IF; EXECUTE v_move_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Moved %s rows to parent.', v_child_loop_total)); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; -- Check again if table is empty and go to outer loop again to drop it if so IF v_epoch = false THEN EXECUTE format('SELECT min(%I) FROM %I.%I', v_control, v_parent_schema, v_child_table) INTO v_child_min; ELSE EXECUTE format('SELECT to_timestamp(min(%I)) FROM %I.%I', v_control, v_parent_schema, v_child_table) INTO v_child_min; END IF; CONTINUE outer_child_loop WHEN v_child_min IS NULL; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC') LIMIT 1; IF v_child_table IS NULL THEN DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) to the parent. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', format('Copied %s row(s) to the parent. Removed %s partitions.', v_total, v_undo_count)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_total; 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 UNDO PARTITIONING: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; -- Restore dropped object privileges DO $$ DECLARE v_row record; BEGIN FOR v_row IN SELECT statement FROM partman_preserve_privs_temp LOOP IF v_row.statement IS NOT NULL THEN EXECUTE v_row.statement; END IF; END LOOP; END $$; DROP TABLE IF EXISTS partman_preserve_privs_temp; pg_partman-4.6.0/updates/pg_partman--2.3.4--2.4.0.sql000066400000000000000000003241451412761706100214400ustar00rootroot00000000000000-- IMPORTANT NOTICE: As of this version (2.4.0) support for the 1.x series has been dropped. No further fixes (bugs, features, etc) will be backported and 1.8.8 will be the last version in the 1.x series. If you encounter a bug, the only supported method of fixing it will be to upgrade to the 2.x series where it may already have been fixed or you can submit an Issue on Github. -- Change data type of time-based variables in many functions from "timestamp without time zone (timestamp)" to "timestamp with timezone (timestamptz)". Previously there was a mix of both being used and was causing inconsistencies on timezone edge cases. This was also affecting the data type used when creating the constraints on child tables (they may have had improper timezone info). This should allow constraints on child tables to work more consistently whether the partition column is either timestamp or timestamptz (Github Issue #118). Note this will not recreate the constraints on existing child tables, but if you weren't having any issues before, it shouldn't cause any problems. If you need to have the constraints fixed, you can either do it manually for all existing child tables or unpartition and repartition the set again. -- If you're concerned about this causing issues, you can set the "trigger_exception_handling" configuration option to true which causes any errors encountered when inserting data to child tables to put the data into the parent. Note this will override any other exception handling that uses these tables, but should ensure no data goes missing for normal writes. -- Allow constraint exclusion to work better with epoch partitioning. Previously the only constraint was based around the time value of the epoch column and constraint exclusion would not work if the integer value was queried. This adds an additional constraint to the child tables so that exclusion should work for both time & integer based queries when epoch partitioning is used.(Github Issue #108). -- Fixed bug where if any time-custom partitions were set up, their configuration could possibly be used for any time based partition set (Github Pull Request #120). CREATE TEMP TABLE partman_preserve_privs_temp (statement text); INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_partition_time(text, timestamptz[], boolean, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_partition_time'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.show_partition_info(text, text, text) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'show_partition_info'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.show_partition_name(text, text) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'show_partition_name(text, text)'; DROP FUNCTION create_partition_time(text, timestamp[], boolean, boolean); DROP FUNCTION show_partition_info(text, text, text); DROP FUNCTION show_partition_name(text, text); /* * Function to create a child table in a time-based partition set */ CREATE FUNCTION create_partition_time(p_parent_table text, p_partition_times timestamptz[], p_analyze boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_datetime_string text; v_exists text; v_epoch boolean; v_grantees text[]; v_hasoids boolean; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_partition_created boolean := false; v_partition_name text; v_partition_suffix text; v_parent_tablespace text; v_partition_interval interval; v_partition_timestamp_end timestamptz; v_partition_timestamp_start timestamptz; v_quarter text; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_sub_timestamp_max timestamptz; v_sub_timestamp_min timestamptz; v_trunc_value text; v_time timestamptz; v_type text; v_unlogged char; v_year text; BEGIN SELECT partition_type , control , partition_interval , epoch , inherit_fk , jobmon , datetime_string INTO v_type , v_control , v_partition_interval , v_epoch , v_inherit_fk , v_jobmon , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::timestamptz, sub_max::timestamptz INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'time'); SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_timestamp_start := v_time; BEGIN v_partition_timestamp_end := v_time + v_partition_interval; 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_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_time||' skipped'); CONTINUE; END; -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_timestamp_min IS NOT NULL THEN IF v_time < v_sub_timestamp_min OR v_time > v_sub_timestamp_max THEN CONTINUE; END IF; END IF; -- This suffix generation code is in partition_data_time() as well v_partition_suffix := to_char(v_time, v_datetime_string); v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); SELECT tablename INTO v_exists FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_partition_name::name; IF v_exists IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Creating new partition %s.%s with interval from %s to %s' , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end-'1sec'::interval)); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c 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; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c 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; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; IF v_epoch = false THEN EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_control , v_partition_timestamp_start , v_control , v_partition_timestamp_end); ELSE EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (to_timestamp(%I) >= %L AND to_timestamp(%I) < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_time_check' , v_control , v_partition_timestamp_start , v_control , v_partition_timestamp_end); EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_int_check' , v_control , EXTRACT('epoch' from v_partition_timestamp_start) , v_control , EXTRACT('epoch' from v_partition_timestamp_end) ); END IF; EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); -- If custom time, set extra config options. IF v_type = 'time-custom' THEN INSERT INTO @extschema@.custom_time_partitions (parent_table, child_table, partition_range) VALUES ( p_parent_table, v_parent_schema||'.'||v_partition_name, tstzrange(v_partition_timestamp_start, v_partition_timestamp_end, '[)') ); END IF; PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_use_run_maintenance , sub_infinite_time_partitions , sub_jobmon , sub_trigger_exception_handling FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Subpartitioning %s.%s', v_parent_schema, v_partition_name)); END IF; 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_use_run_maintenance := %L , p_inherit_fk := %L , p_epoch := %L , p_jobmon := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_use_run_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions , trigger_exception_handling = v_row.sub_trigger_exception_handling WHERE parent_table = v_parent_schema||'.'||v_partition_name; END LOOP; -- end sub partitioning LOOP -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table, p_job_id := v_job_id, p_debug := p_debug); v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s. Attempted intervals: %s', p_parent_table, p_partition_times)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; 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_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to drop child tables from a time-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ CREATE OR REPLACE FUNCTION drop_partition_time(p_parent_table text, p_retention interval DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_datetime_string text; v_drop_count int := 0; v_index record; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_partition_timestamp timestamptz; v_retention interval; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_row record; v_step_id bigint; v_type text; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman drop_partition_time')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_time already running.'; RETURN 0; END IF; -- Allow override of configuration options IF p_retention IS NULL THEN SELECT partition_type , partition_interval::interval , retention::interval , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_type , v_partition_interval , v_retention , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom') AND retention IS NOT NULL; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE SELECT partition_type , partition_interval::interval , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_type , v_partition_interval , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); v_retention := p_retention; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- Loop through child tables of the given parent FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'DESC') LOOP -- pull out datetime portion of partition's tablename to make the next one SELECT child_start_time INTO v_partition_timestamp FROM @extschema@.show_partition_info(v_row.partition_schemaname||'.'||v_row.partition_tablename , v_partition_interval::text , p_parent_table); -- Add one interval since partition names contain the start of the constraint period IF v_retention < (CURRENT_TIMESTAMP - (v_partition_timestamp + v_partition_interval)) THEN -- Only create a jobmon entry if there's actual retention work done IF v_jobmon_schema IS NOT NULL AND v_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN DROP TIME PARTITION: %s', p_parent_table)); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Uninherit table %s.%s from %s' , v_row.partition_schemaname , v_row.partition_tablename , p_parent_table)); END IF; EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_row.partition_schemaname , v_row.partition_tablename , v_parent_schema , v_parent_tablename); IF v_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_row.partition_schemaname||'.'||v_row.partition_tablename; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop table %s.%s', v_row.partition_schemaname, v_row.partition_tablename)); END IF; EXECUTE format('DROP TABLE %I.%I CASCADE', v_row.partition_schemaname, v_row.partition_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN FOR v_index IN WITH child_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE c1.relname = v_row.partition_tablename::name AND n1.nspname = v_row.partition_schemaname::name ) SELECT c.relname as name , con.conname FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid LEFT JOIN pg_catalog.pg_constraint con ON i.indexrelid = con.conindid JOIN child_info ON i.indrelid = child_info.oid LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop index %s from %s.%s' , v_index.name , v_row.partition_schemaname , v_row.partition_tablename)); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I DROP CONSTRAINT %I' , v_row.partition_schemaname , v_row.partition_tablename , v_index.conname); ELSE EXECUTE format('DROP INDEX %I.%I', v_parent_schema, v_index.name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Moving table %s.%s to schema %s' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema)); END IF; EXECUTE format('ALTER TABLE %I.%I SET SCHEMA %I', v_row.partition_schemaname, v_row.partition_tablename, v_retention_schema); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if -- If child table is a subpartition, remove it from part_config & part_config_sub (should cascade due to FK) DELETE FROM @extschema@.part_config WHERE parent_table = v_row.partition_schemaname||'.'||v_row.partition_tablename; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', format('%s partitions dropped.', v_drop_count)); 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_drop_count; 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 DROP TIME PARTITION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Apply constraints managed by partman extension */ CREATE OR REPLACE FUNCTION apply_constraints(p_parent_table text, p_child_table text DEFAULT NULL, p_analyze boolean DEFAULT FALSE, p_job_id bigint DEFAULT NULL, p_debug boolean DEFAULT FALSE) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_child_exists text; v_child_tablename text; v_col text; v_constraint_cols text[]; v_constraint_col_type text; v_constraint_name text; v_constraint_values record; v_control text; v_datetime_string text; v_existing_constraint_name text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_id bigint; v_last_partition_timestamp timestamptz; v_max_id bigint; v_max_timestamp timestamptz; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_optimize_constraint int; v_parent_schema text; v_parent_table text; v_parent_tablename text; v_partition_interval text; v_partition_suffix text; v_premake int; v_sql text; v_step_id bigint; v_suffix_position int; v_type text; BEGIN SELECT parent_table , partition_type , control , premake , partition_interval , optimize_constraint , datetime_string , constraint_cols , jobmon INTO v_parent_table , v_type , v_control , v_premake , v_partition_interval , v_optimize_constraint , v_datetime_string , v_constraint_cols , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND constraint_cols IS NOT NULL; IF v_constraint_cols IS NULL THEN IF p_debug THEN RAISE NOTICE 'Given parent table (%) not set up for constraint management (constraint_cols is NULL)', p_parent_table; END IF; -- Returns silently to allow this function to be simply called by maintenance processes without having to check if config options are set. RETURN; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(v_parent_table, '.', 1)::name AND tablename = split_part(v_parent_table, '.', 2)::name; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE CONSTRAINT: %s', v_parent_table)); ELSE v_job_id = p_job_id; END IF; END IF; -- If p_child_table is null, figure out the partition that is the one right before the optimize_constraint value backwards. IF p_child_table IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying additional constraints: Automatically determining most recent child on which to apply constraints'); END IF; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(v_parent_table, 'DESC') LIMIT 1; IF v_type IN ('time', 'time-custom') THEN SELECT child_start_time INTO v_last_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_partition_interval, v_parent_table); v_partition_suffix := to_char(v_last_partition_timestamp - (v_partition_interval::interval * (v_optimize_constraint + v_premake + 1) ), v_datetime_string); ELSIF v_type = 'id' THEN SELECT child_start_id INTO v_last_partition_id FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_partition_interval, v_parent_table); v_partition_suffix := (v_last_partition_id - (v_partition_interval::int * (v_optimize_constraint + v_premake + 1) ))::text; END IF; v_child_tablename := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); IF p_debug THEN RAISE NOTICE 'apply_constraint: v_parent_tablename: % , v_partition_suffix: %', v_parent_tablename, v_partition_suffix; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Target child table: %s.%s', v_parent_schema, v_child_tablename)); END IF; ELSE v_child_tablename = split_part(p_child_table, '.', 2); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying additional constraints: Checking if target child table exists'); END IF; SELECT tablename FROM pg_catalog.pg_tables INTO v_child_exists WHERE schemaname = v_parent_schema::name AND tablename = v_child_tablename::name; IF v_child_exists IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Target child table (%s) does not exist. Skipping constraint creation.', v_child_tablename)); IF p_job_id IS NULL THEN PERFORM close_job(v_job_id); END IF; END IF; IF p_debug THEN RAISE NOTICE 'Target child table (%) does not exist. Skipping constraint creation.', v_child_tablename; END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; FOREACH v_col IN ARRAY v_constraint_cols LOOP SELECT con.conname INTO v_existing_constraint_name FROM pg_catalog.pg_constraint con JOIN pg_class c ON c.oid = con.conrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN pg_catalog.pg_attribute a ON con.conrelid = a.attrelid WHERE c.relname = v_child_tablename::name AND n.nspname = v_parent_schema::name AND con.conname LIKE 'partmanconstr_%' AND con.contype = 'c' AND a.attname = v_col::name AND ARRAY[a.attnum] OPERATOR(pg_catalog.<@) con.conkey AND a.attisdropped = false; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Applying additional constraints: Applying new constraint on column: %s', v_col)); END IF; IF v_existing_constraint_name IS NOT NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Partman managed constraint already exists on this table (%s) and column (%s). Skipping creation.', v_child_tablename, v_col)); END IF; IF p_debug THEN RAISE NOTICE 'Partman managed constraint already exists on this table (%) and column (%). Skipping creation.', v_child_tablename, v_col ; END IF; CONTINUE; END IF; -- Ensure column name gets put on end of constraint name to help avoid naming conflicts v_constraint_name := @extschema@.check_name_length('partmanconstr_'||v_child_tablename, p_suffix := '_'||v_col); EXECUTE format('SELECT min(%I)::text AS min, max(%I)::text AS max FROM %I.%I', v_col, v_col, v_parent_schema, v_child_tablename) INTO v_constraint_values; IF v_constraint_values IS NOT NULL THEN v_sql := format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I <= %L)' , v_parent_schema , v_child_tablename , v_constraint_name , v_col , v_constraint_values.min , v_col , v_constraint_values.max); IF p_debug THEN RAISE NOTICE 'Constraint creation query: %', v_sql; END IF; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('New constraint created: %s', v_sql)); END IF; ELSE IF p_debug THEN RAISE NOTICE 'Given column (%) contains all NULLs. No constraint created', v_col; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Given column (%s) contains all NULLs. No constraint created', v_col)); END IF; END IF; END LOOP; IF p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Applying additional constraints: Running analyze on partition set: %s', v_parent_table)); END IF; IF p_debug THEN RAISE NOTICE 'Running analyze on partition set: %', v_parent_table; END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); 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 CONSTRAINT: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Check if parent table is a subpartition of an already existing partition set managed by pg_partman * If so, return the limits of what child tables can be created under the given parent table based on its own suffix */ CREATE OR REPLACE FUNCTION check_subpartition_limits(p_parent_table text, p_type text, OUT sub_min text, OUT sub_max text) RETURNS record LANGUAGE plpgsql AS $$ DECLARE v_datetime_string text; v_id_position int; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_quarter text; v_sub_id_max bigint; v_sub_id_min bigint; v_sub_timestamp_max timestamptz; v_sub_timestamp_min timestamptz; v_time_position int; v_top_datetime_string text; v_top_interval text; v_top_parent text; v_top_type text; v_year text; BEGIN SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- CTE query is done individually for each type (time, id) because it should return NULL if the top parent is not the same type in a subpartition set (id->time or time->id) IF p_type = 'id' THEN WITH top_oid AS ( SELECT i.inhparent AS top_parent_oid FROM pg_catalog.pg_class c JOIN pg_catalog.pg_inherits i ON c.oid = i.inhrelid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = v_parent_schema AND c.relname = v_parent_tablename ) SELECT n.nspname||'.'||c.relname, p.datetime_string, p.partition_interval, p.partition_type INTO v_top_parent, v_top_datetime_string, v_top_interval, v_top_type FROM pg_catalog.pg_class c JOIN top_oid t ON c.oid = t.top_parent_oid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace JOIN @extschema@.part_config p ON p.parent_table = n.nspname||'.'||c.relname WHERE c.oid = t.top_parent_oid AND p.partition_type = 'id'; IF v_top_parent IS NOT NULL THEN SELECT child_start_id::text, child_end_id::text INTO sub_min, sub_max FROM @extschema@.show_partition_info(p_parent_table, v_top_interval, v_top_parent); END IF; ELSIF p_type = 'time' THEN WITH top_oid AS ( SELECT i.inhparent AS top_parent_oid FROM pg_catalog.pg_class c JOIN pg_catalog.pg_inherits i ON c.oid = i.inhrelid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = v_parent_schema AND c.relname = v_parent_tablename ) SELECT n.nspname||'.'||c.relname, p.datetime_string, p.partition_interval, p.partition_type INTO v_top_parent, v_top_datetime_string, v_top_interval, v_top_type FROM pg_catalog.pg_class c JOIN top_oid t ON c.oid = t.top_parent_oid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace JOIN @extschema@.part_config p ON p.parent_table = n.nspname||'.'||c.relname WHERE c.oid = t.top_parent_oid AND p.partition_type = 'time' OR p.partition_type = 'time-custom'; IF v_top_parent IS NOT NULL THEN SELECT child_start_time::text, child_end_time::text INTO sub_min, sub_max FROM @extschema@.show_partition_info(p_parent_table, v_top_interval, v_top_parent); END IF; ELSE RAISE EXCEPTION 'Invalid type given as parameter to check_subpartition_limits()'; END IF; RETURN; END $$; /* * Function to turn a table into the parent of a partition set */ CREATE OR REPLACE FUNCTION 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_use_run_maintenance boolean DEFAULT NULL , p_start_partition text DEFAULT NULL , p_inherit_fk boolean DEFAULT true , p_epoch boolean DEFAULT false , p_jobmon boolean DEFAULT true , p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_base_timestamp timestamptz; v_count int := 1; v_datetime_string 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_job_id bigint; v_jobmon_schema text; v_last_partition_created boolean; v_max bigint; v_notnull boolean; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_partition_id bigint; v_parent_partition_timestamp timestamptz; v_parent_schema text; v_parent_tablename text; v_partition_time timestamptz; v_partition_time_array timestamptz[]; v_partition_id_array bigint[]; v_row record; v_run_maint boolean; 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_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); BEGIN IF position('.' in p_parent_table) = 0 THEN RAISE EXCEPTION 'Parent table must be schema qualified'; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = 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 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; IF p_type = 'id' AND p_epoch = true THEN RAISE EXCEPTION 'p_epoch can only be used with time-based partitioning'; END IF; IF NOT @extschema@.check_partition_type(p_type) THEN RAISE EXCEPTION '% is not a valid partitioning type', p_type; 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'); IF p_use_run_maintenance IS NOT NULL THEN IF p_use_run_maintenance IS FALSE AND (p_type = 'time' OR p_type = 'time-custom') THEN RAISE EXCEPTION 'p_run_maintenance cannot be set to false for time based partitioning'; END IF; v_run_maint := p_use_run_maintenance; ELSIF p_type = 'time' OR p_type = 'time-custom' THEN v_run_maint := TRUE; ELSIF p_type = 'id' THEN v_run_maint := FALSE; ELSE RAISE EXCEPTION 'use_run_maintenance value cannot be set NULL'; END IF; 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 ) 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_use_run_maintenance , sub_epoch , sub_optimize_trigger , sub_optimize_constraint , sub_infinite_time_partitions , sub_jobmon , sub_trigger_exception_handling 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_use_run_maintenance , sub_epoch , sub_optimize_trigger , sub_optimize_constraint , sub_infinite_time_partitions , sub_jobmon , sub_trigger_exception_handling) 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_use_run_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); END LOOP; IF p_type = 'time' OR p_type = 'time-custom' 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 <> 'time-custom' THEN RAISE EXCEPTION 'Must use a predefined time interval if not using type "time-custom". See documentation.'; 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 , use_run_maintenance , inherit_fk , jobmon) VALUES ( p_parent_table , p_type , v_time_interval , p_epoch , p_control , p_premake , p_constraint_cols , v_datetime_string , v_run_maint , p_inherit_fk , p_jobmon); 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 -- Currently unknown edge case if code gets here RAISE EXCEPTION 'No child tables created. Unexpected edge case encountered. Please report this error to author with conditions that led to it.'; END IF; END 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 p_type = 'id' THEN v_id_interval := p_interval::bigint; IF v_id_interval < 10 THEN RAISE EXCEPTION 'Interval for serial 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 INTO v_higher_parent_schema, v_higher_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 WHERE p.partition_type = 'id'; IF v_higher_parent_table IS NOT NULL THEN -- 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 , use_run_maintenance , inherit_fk , jobmon) VALUES ( p_parent_table , p_type , v_id_interval , p_control , p_premake , p_constraint_cols , v_run_maint , p_inherit_fk , p_jobmon); 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 WHERE p.partition_type = 'id'; 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 EXCEPTION 'No child tables created. Unexpected edge case encountered. Please report this error to author with conditions that led to it.'; END IF; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating partition function'); END IF; IF p_type = 'time' OR p_type = 'time-custom' 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 p_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); 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 $$; /* * Populate the child table(s) of a time-based partition set with old data from the original parent */ CREATE OR REPLACE FUNCTION partition_data_time(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval interval DEFAULT NULL, p_lock_wait numeric DEFAULT 0, p_order text DEFAULT 'ASC') RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_datetime_string text; v_current_partition_name text; v_epoch boolean; v_last_partition text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_timestamp timestamptz; v_min_partition_timestamp timestamptz; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_partition_suffix text; v_partition_timestamp timestamptz[]; v_quarter text; v_rowcount bigint; v_sql text; v_start_control timestamptz; v_time_position int; v_total_rows bigint := 0; v_type text; v_year text; BEGIN 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'); SELECT partition_type , partition_interval::interval , control , datetime_string , epoch INTO v_type , v_partition_interval , v_control , v_datetime_string , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); IF NOT FOUND THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; FOR i IN 1..p_batch_count LOOP IF v_epoch = false THEN IF p_order = 'ASC' THEN EXECUTE format('SELECT min(%I) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSIF p_order = 'DESC' THEN EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; ELSE IF p_order = 'ASC' THEN EXECUTE format('SELECT to_timestamp(min(%I)) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSIF p_order = 'DESC' THEN EXECUTE format('SELECT to_timestamp(max(%I)) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; END IF; IF v_start_control IS NULL THEN EXIT; END IF; IF v_type = 'time' THEN CASE WHEN v_partition_interval = '15 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '15min'::interval * floor(date_part('minute', v_start_control) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '30min'::interval * floor(date_part('minute', v_start_control) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control); WHEN v_partition_interval = '1 day' THEN v_min_partition_timestamp := date_trunc('day', v_start_control); WHEN v_partition_interval = '1 week' THEN v_min_partition_timestamp := date_trunc('week', v_start_control); WHEN v_partition_interval = '1 month' THEN v_min_partition_timestamp := date_trunc('month', v_start_control); WHEN v_partition_interval = '3 months' THEN v_min_partition_timestamp := date_trunc('quarter', v_start_control); WHEN v_partition_interval = '1 year' THEN v_min_partition_timestamp := date_trunc('year', v_start_control); END CASE; ELSIF v_type = 'time-custom' THEN SELECT child_start_time INTO v_min_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition , v_partition_interval::text , p_parent_table); v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; LOOP IF v_start_control >= v_min_partition_timestamp AND v_start_control < v_max_partition_timestamp THEN EXIT; ELSE BEGIN IF v_start_control > v_max_partition_timestamp THEN -- Keep going forward in time, checking if child partition time interval encompasses the current v_start_control value v_min_partition_timestamp := v_max_partition_timestamp; v_max_partition_timestamp := v_max_partition_timestamp + v_partition_interval; ELSE -- Keep going backwards in time, checking if child partition time interval encompasses the current v_start_control value v_max_partition_timestamp := v_min_partition_timestamp; v_min_partition_timestamp := v_min_partition_timestamp - v_partition_interval; END IF; EXCEPTION WHEN datetime_field_overflow THEN RAISE EXCEPTION 'Attempted partition time interval is outside PostgreSQL''s supported time range. Unable to create partition with interval before timestamp % ', v_min_partition_interval; END; END IF; END LOOP; END IF; v_partition_timestamp := ARRAY[v_min_partition_timestamp]; IF p_order = 'ASC' THEN -- Ensure batch interval given as parameter doesn't cause maximum to overflow the current partition maximum IF (v_start_control + p_batch_interval) >= (v_min_partition_timestamp + v_partition_interval) THEN v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; ELSE v_max_partition_timestamp := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; -- Ensure batch interval given as parameter doesn't cause minimum to underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_timestamp THEN v_min_partition_timestamp = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN IF v_epoch = false THEN v_sql := format('SELECT * FROM ONLY %I.%I WHERE %I >= %L AND %I < %L FOR UPDATE NOWAIT' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_timestamp , v_control , v_max_partition_timestamp); ELSE v_sql := format('SELECT * FROM ONLY %I.%I WHERE to_timestamp(%I) >= %L AND to_timestamp(%I) < %L FOR UPDATE NOWAIT' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_timestamp , v_control , v_max_partition_timestamp); END IF; EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; PERFORM @extschema@.create_partition_time(p_parent_table, v_partition_timestamp); -- This suffix generation code is in create_partition_time() as well v_partition_suffix := to_char(v_min_partition_timestamp, v_datetime_string); v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); IF v_epoch = false THEN v_sql := format('WITH partition_data AS ( DELETE FROM ONLY %I.%I WHERE %I >= %L AND %I < %L RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_timestamp , v_control , v_max_partition_timestamp , v_parent_schema , v_current_partition_name); ELSE v_sql := format('WITH partition_data AS ( DELETE FROM ONLY %I.%I WHERE to_timestamp(%I) >= %L AND to_timestamp(%I) < %L RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_timestamp , v_control , v_max_partition_timestamp , v_parent_schema , v_current_partition_name); END IF; EXECUTE v_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; PERFORM @extschema@.create_function_time(p_parent_table); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_total_rows; END $$; /* * Function to manage pre-creation of the next partitions in a set. * Also manages dropping old partitions if the retention option is set. * If p_parent_table is passed, will only run run_maintenance() on that one table (no matter what the configuration table may have set for it) * Otherwise, will run on all tables in the config table with p_run_maintenance() set to true. * For large partition sets, running analyze can cause maintenance to take longer than expected. Can set p_analyze to false to avoid a forced analyze run. * Be aware that constraint exclusion may not work properly until an analyze on the partition set is run. */ CREATE OR REPLACE FUNCTION run_maintenance(p_parent_table text DEFAULT NULL, p_analyze boolean DEFAULT true, p_jobmon boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_check_subpart int; v_create_count int := 0; v_current_partition text; v_current_partition_id bigint; v_current_partition_timestamp timestamptz; v_datetime_string text; v_drop_count int := 0; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_created boolean; v_last_partition_id bigint; v_last_partition_timestamp timestamptz; v_max_id_parent bigint; v_max_time_parent timestamptz; v_new_search_path text := '@extschema@,pg_temp'; v_next_partition_id bigint; v_next_partition_timestamp timestamptz; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_premade_count int; v_premake_id_max bigint; v_premake_id_min bigint; v_premake_timestamp_min timestamptz; v_premake_timestamp_max timestamptz; v_row record; v_row_max_id record; v_row_max_time record; v_row_sub record; v_skip_maint boolean; v_step_id bigint; v_step_overflow_id bigint; v_sub_id_max bigint; v_sub_id_max_suffix bigint; v_sub_id_min bigint; v_sub_parent text; v_sub_timestamp_max timestamptz; v_sub_timestamp_max_suffix timestamptz; v_sub_timestamp_min timestamptz; v_tablename text; v_tables_list_sql text; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman maintenance already running.'; RETURN; 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'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN RUN MAINTENANCE'); v_step_id := add_step(v_job_id, 'Running maintenance loop'); END IF; v_row := NULL; -- Ensure it's reset v_tables_list_sql := 'SELECT parent_table , partition_type , partition_interval , control , premake , datetime_string , undo_in_progress , sub_partition_set_full , epoch , infinite_time_partitions FROM @extschema@.part_config WHERE sub_partition_set_full = false'; IF p_parent_table IS NULL THEN v_tables_list_sql := v_tables_list_sql || ' AND use_run_maintenance = true'; ELSE v_tables_list_sql := v_tables_list_sql || format(' AND parent_table = %L', p_parent_table); END IF; FOR v_row IN EXECUTE v_tables_list_sql LOOP CONTINUE WHEN v_row.undo_in_progress; v_skip_maint := true; -- reset every loop -- Check for consistent data in part_config_sub table. Was unable to get this working properly as either a constraint or trigger. -- Would either delay raising an error until the next write (which I cannot predict) or disallow future edits to update a sub-partition set's configuration. -- This way at least provides a consistent way to check that I know will run. If anyone can get a working constraint/trigger, please help! -- Don't have to worry about this in the serial trigger maintenance since subpartitioning requires run_maintenance(). SELECT sub_parent INTO v_sub_parent FROM @extschema@.part_config_sub WHERE sub_parent = v_row.parent_table; IF v_sub_parent IS NOT NULL THEN SELECT count(*) INTO v_check_subpart FROM @extschema@.check_subpart_sameconfig(v_row.parent_table); IF v_check_subpart > 1 THEN RAISE EXCEPTION 'Inconsistent data in part_config_sub table. Sub-partition tables that are themselves sub-partitions cannot have differing configuration values among their siblings. Run this query: "SELECT * FROM @extschema@.check_subpart_sameconfig(''%'');" This should only return a single row or nothing. If multiple rows are returned, results are all children of the given parent. Update the differing values to be consistent for your desired values.', v_row.sub_parent; END IF; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(v_row.parent_table, '.', 1)::name AND tablename = split_part(v_row.parent_table, '.', 2)::name; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LIMIT 1; IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_last_partition: %', v_row.parent_table, v_last_partition; END IF; IF v_row.partition_type = 'time' OR v_row.partition_type = 'time-custom' THEN SELECT child_start_time INTO v_last_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_row.partition_interval, v_row.parent_table); -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_time IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP IF v_row.epoch = false THEN EXECUTE format('SELECT max(%I)::text FROM %I.%I' , v_row.control , v_row_max_time.partition_schemaname , v_row_max_time.partition_tablename ) INTO v_current_partition_timestamp; ELSE EXECUTE format('SELECT to_timestamp(max(%I))::text FROM %I.%I' , v_row.control , v_row_max_time.partition_schemaname , v_row_max_time.partition_tablename ) INTO v_current_partition_timestamp; END IF; IF v_current_partition_timestamp IS NOT NULL THEN SELECT suffix_timestamp INTO v_current_partition_timestamp FROM @extschema@.show_partition_name(v_row.parent_table, v_current_partition_timestamp::text); EXIT; END IF; END LOOP; -- Check for values in the parent table. If they are there and greater than all child values, use that instead -- This allows maintenance to continue working properly if there is a large gap in data insertion. Data will remain in parent, but new tables will be created IF v_row.epoch = false THEN EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_row.control, v_parent_schema, v_parent_tablename) INTO v_max_time_parent; ELSE EXECUTE format('SELECT to_timestamp(max(%I)) FROM ONLY %I.%I', v_row.control, v_parent_schema, v_parent_tablename) INTO v_max_time_parent; END IF; IF p_debug THEN RAISE NOTICE 'run_maint: v_current_partition_timestamp: %, v_max_time_parent: %', v_current_partition_timestamp, v_max_time_parent; END IF; IF v_max_time_parent > v_current_partition_timestamp THEN SELECT suffix_timestamp INTO v_current_partition_timestamp FROM @extschema@.show_partition_name(v_row.parent_table, v_max_time_parent::text); END IF; IF v_current_partition_timestamp IS NULL THEN -- Partition set is completely empty IF v_row.infinite_time_partitions IS TRUE THEN -- Set it to now so new partitions continue to be created v_current_partition_timestamp = CURRENT_TIMESTAMP; ELSE -- Nothing to do CONTINUE; END IF; END IF; -- If this is a subpartition, determine if the last child table has been made. If so, mark it as full so future maintenance runs can skip it SELECT sub_min::timestamptz, sub_max::timestamptz INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'time'); IF v_sub_timestamp_max IS NOT NULL THEN SELECT suffix_timestamp INTO v_sub_timestamp_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_timestamp_max::text); IF v_sub_timestamp_max_suffix = v_last_partition_timestamp THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; -- Check and see how many premade partitions there are. v_premade_count = round(EXTRACT('epoch' FROM age(v_last_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); v_next_partition_timestamp := v_last_partition_timestamp; IF p_debug THEN RAISE NOTICE 'run_maint before loop: current_partition_timestamp: %, v_premade_count: %, v_sub_timestamp_min: %, v_sub_timestamp_max: %' , v_current_partition_timestamp , v_premade_count , v_sub_timestamp_min , v_sub_timestamp_max; END IF; -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed WHILE (v_premade_count < v_row.premake) LOOP IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_premade_count: %, v_next_partition_timestamp: %', v_row.parent_table, v_premade_count, v_next_partition_timestamp; END IF; IF v_next_partition_timestamp < v_sub_timestamp_min OR v_next_partition_timestamp > v_sub_timestamp_max THEN -- With subpartitioning, no need to run if the timestamp is not in the parent table's range EXIT; END IF; BEGIN v_next_partition_timestamp := v_next_partition_timestamp + v_row.partition_interval::interval; EXCEPTION WHEN datetime_field_overflow THEN v_premade_count := v_row.premake; -- do this so it can exit the premake check loop and continue in the outer for loop IF v_jobmon_schema IS NOT NULL THEN 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', format('Child partition creation skipped for parent table: %s', v_partition_time)); END IF; RAISE WARNING 'Attempted partition time interval is outside PostgreSQL''s supported time range. Child partition creation skipped for parent table %', v_row.parent_table; CONTINUE; END; v_last_partition_created := @extschema@.create_partition_time(v_row.parent_table, ARRAY[v_next_partition_timestamp], p_analyze); IF v_last_partition_created THEN v_create_count := v_create_count + 1; PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); END IF; v_premade_count = round(EXTRACT('epoch' FROM age(v_next_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); END LOOP; ELSIF v_row.partition_type = 'id' THEN -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%I)::text FROM %I.%I' , v_row.control , v_row_max_id.partition_schemaname , v_row_max_id.partition_tablename) INTO v_current_partition_id; IF v_current_partition_id IS NOT NULL THEN SELECT suffix_id INTO v_current_partition_id FROM @extschema@.show_partition_name(v_row.parent_table, v_current_partition_id::text); EXIT; END IF; END LOOP; -- Check for values in the parent table. If they are there and greater than all child values, use that instead -- This allows maintenance to continue working properly if there is a large gap in data insertion. Data will remain in parent, but new tables will be created EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_row.control, v_parent_schema, v_parent_tablename) INTO v_max_id_parent; IF v_max_id_parent > v_current_partition_id THEN SELECT suffix_id INTO v_current_partition_id FROM @extschema@.show_partition_name(v_row.parent_table, v_max_id_parent::text); END IF; IF v_current_partition_id IS NULL THEN -- Partition set is completely empty. Nothing to do CONTINUE; END IF; SELECT child_start_id INTO v_last_partition_id FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_row.partition_interval, v_row.parent_table); -- Determine if this table is a child of a subpartition parent. If so, get limits to see if run_maintenance even needs to run for it. SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'id'); IF v_sub_id_max IS NOT NULL THEN SELECT suffix_id INTO v_sub_id_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_id_max::text); IF v_sub_id_max_suffix = v_last_partition_id THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; v_next_partition_id := v_last_partition_id; v_premade_count := ((v_last_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed. WHILE (v_premade_count < v_row.premake) LOOP IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_premade_count: %, v_next_partition_id: %', v_row.parent_table, v_premade_count, v_next_partition_id; END IF; IF v_next_partition_id < v_sub_id_min OR v_next_partition_id > v_sub_id_max THEN -- With subpartitioning, no need to run if the id is not in the parent table's range EXIT; END IF; v_next_partition_id := v_next_partition_id + v_row.partition_interval::bigint; v_last_partition_created := @extschema@.create_partition_id(v_row.parent_table, ARRAY[v_next_partition_id], p_analyze); IF v_last_partition_created THEN v_create_count := v_create_count + 1; PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); END IF; v_premade_count := ((v_next_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); END LOOP; END IF; -- end main IF check for time or id END LOOP; -- end of creation loop -- Manage dropping old partitions if retention option is set FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND (partition_type = 'time' OR partition_type = 'time-custom') LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); END IF; END IF; IF v_drop_count > 0 THEN PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); END IF; END LOOP; FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND partition_type = 'id' LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); END IF; END IF; IF v_drop_count > 0 THEN PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Partition maintenance finished. %s partitions made. %s partitions dropped.', v_create_count, v_drop_count)); 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'); 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 RUN MAINTENANCE'')', v_jobmon_schema) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Show the data boundries for a given child table as well as the suffix that will be used. * Passing the parent table argument improves performance by avoiding a catalog lookup. * Passing an interval lets you set one different than the default configured one if desired. */ CREATE FUNCTION show_partition_info(p_child_table text , p_partition_interval text DEFAULT NULL , p_parent_table text DEFAULT NULL , OUT child_start_time timestamptz , OUT child_end_time timestamptz , OUT child_start_id bigint , OUT child_end_id bigint , OUT suffix text) RETURNS record LANGUAGE plpgsql STABLE SECURITY DEFINER AS $$ DECLARE v_child_schema text; v_child_tablename text; v_datetime_string text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_table text; v_partition_interval text; v_partition_type text; v_quarter text; v_suffix text; v_suffix_position int; v_year text; BEGIN 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'); SELECT schemaname, tablename INTO v_child_schema, v_child_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_child_table, '.', 1)::name AND tablename = split_part(p_child_table, '.', 2)::name; IF v_child_tablename IS NULL THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RAISE EXCEPTION 'Child table given does not exist (%)', p_child_table; END IF; IF p_parent_table IS NULL THEN SELECT n.nspname||'.'|| c.relname INTO v_parent_table FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhparent JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhrelid::regclass = p_child_table::regclass; ELSE v_parent_table := p_parent_table; END IF; IF p_partition_interval IS NULL THEN SELECT partition_interval, partition_type, datetime_string INTO v_partition_interval, v_partition_type, v_datetime_string FROM @extschema@.part_config WHERE parent_table = v_parent_table; ELSE v_partition_interval := p_partition_interval; SELECT partition_type, datetime_string INTO v_partition_type, v_datetime_string FROM @extschema@.part_config WHERE parent_table = v_parent_table; END IF; IF v_partition_type IS NULL THEN RAISE EXCEPTION 'Parent table of given child not managed by pg_partman: %', v_parent_table; END IF; v_suffix_position := (length(v_child_tablename) - position('p_' in reverse(v_child_tablename))) + 2; v_suffix := substring(v_child_tablename from v_suffix_position); IF v_partition_type = 'time' OR v_partition_type = 'time-custom' THEN IF v_partition_interval::interval <> '3 months' OR (v_partition_interval::interval = '3 months' AND v_partition_type = 'time-custom') THEN child_start_time := to_timestamp(v_suffix, v_datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(v_suffix, 'q', 1); v_quarter := split_part(v_suffix, 'q', 2); CASE WHEN v_quarter = '1' THEN child_start_time := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN child_start_time := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN child_start_time := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN child_start_time := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; child_end_time := (child_start_time + v_partition_interval::interval) - '1 second'::interval; ELSIF v_partition_type = 'id' THEN child_start_id := v_suffix::bigint; child_end_id := (child_start_id + v_partition_interval::bigint) - 1; ELSE RAISE EXCEPTION 'Invalid partition type encountered in show_partition_info()'; END IF; suffix = v_suffix; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN; END $$; /* * Given a parent table and partition value, return the name of the child partition it would go in. * If using epoch time partitioning, give the text representation of the timestamp NOT the epoch integer value (use to_timestamp() to convert epoch values). * Also returns just the suffix value and true if the child table exists or false if it does not */ CREATE FUNCTION show_partition_name(p_parent_table text, p_value text, OUT partition_table text, OUT suffix_timestamp timestamptz, OUT suffix_id bigint, OUT table_exists boolean) RETURNS record LANGUAGE plpgsql STABLE AS $$ DECLARE v_child_exists text; v_datetime_string text; v_max_range timestamptz; v_min_range timestamptz; v_parent_schema text; v_parent_tablename text; v_partition_interval text; v_type text; BEGIN SELECT partition_type , partition_interval , datetime_string INTO v_type , v_partition_interval , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_type IS NULL THEN RAISE EXCEPTION 'Parent table given is not managed by pg_partman (%)', p_parent_table; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; IF v_parent_tablename IS NULL THEN RAISE EXCEPTION 'Parent table given does not exist (%)', p_parent_table; END IF; IF v_type = 'time' THEN CASE WHEN v_partition_interval::interval = '15 mins' THEN suffix_timestamp := date_trunc('hour', p_value::timestamptz) + '15min'::interval * floor(date_part('minute', p_value::timestamptz) / 15.0); WHEN v_partition_interval::interval = '30 mins' THEN suffix_timestamp := date_trunc('hour', p_value::timestamptz) + '30min'::interval * floor(date_part('minute', p_value::timestamptz) / 30.0); WHEN v_partition_interval::interval = '1 hour' THEN suffix_timestamp := date_trunc('hour', p_value::timestamptz); WHEN v_partition_interval::interval = '1 day' THEN suffix_timestamp := date_trunc('day', p_value::timestamptz); WHEN v_partition_interval::interval = '1 week' THEN suffix_timestamp := date_trunc('week', p_value::timestamptz); WHEN v_partition_interval::interval = '1 month' THEN suffix_timestamp := date_trunc('month', p_value::timestamptz); WHEN v_partition_interval::interval = '3 months' THEN suffix_timestamp := date_trunc('quarter', p_value::timestamptz); WHEN v_partition_interval::interval = '1 year' THEN suffix_timestamp := date_trunc('year', p_value::timestamptz); END CASE; partition_table := v_parent_schema||'.'||@extschema@.check_name_length(v_parent_tablename, to_char(suffix_timestamp, v_datetime_string), TRUE); ELSIF v_type = 'id' THEN suffix_id := (p_value::bigint - (p_value::bigint % v_partition_interval::bigint)); partition_table := v_parent_schema||'.'||@extschema@.check_name_length(v_parent_tablename, suffix_id::text, TRUE); ELSIF v_type = 'time-custom' THEN SELECT child_table, lower(partition_range) INTO partition_table, suffix_timestamp FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND partition_range @> p_value::timestamptz; IF partition_table IS NULL THEN SELECT max(upper(partition_range)) INTO v_max_range FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table; SELECT min(lower(partition_range)) INTO v_min_range FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table; IF p_value::timestamptz >= v_max_range THEN suffix_timestamp := v_max_range; LOOP -- Keep incrementing higher until given value is below the upper range suffix_timestamp := suffix_timestamp + v_partition_interval::interval; IF p_value::timestamptz < suffix_timestamp THEN -- Have to subtract one interval because the value would actually be in the partition previous -- to this partition timestamp since the partition names contain the lower boundary suffix_timestamp := suffix_timestamp - v_partition_interval::interval; EXIT; END IF; END LOOP; ELSIF p_value::timestamptz < v_min_range THEN suffix_timestamp := v_min_range; LOOP -- Keep decrementing lower until given value is below or equal to the lower range suffix_timestamp := suffix_timestamp - v_partition_interval::interval; IF p_value::timestamptz >= suffix_timestamp THEN EXIT; END IF; END LOOP; ELSE RAISE EXCEPTION 'Unable to determine a valid child table for the given parent table and value'; END IF; partition_table := v_parent_schema||'.'||@extschema@.check_name_length(v_parent_tablename, to_char(suffix_timestamp, v_datetime_string), TRUE); END IF; END IF; SELECT tablename INTO v_child_exists FROM pg_catalog.pg_tables WHERE schemaname = split_part(partition_table, '.', 1)::name AND tablename = split_part(partition_table, '.', 2)::name; IF v_child_exists IS NOT NULL THEN table_exists := true; ELSE table_exists := false; END IF; RETURN; END $$; -- Restore dropped object privileges DO $$ DECLARE v_row record; BEGIN FOR v_row IN SELECT statement FROM partman_preserve_privs_temp LOOP IF v_row.statement IS NOT NULL THEN EXECUTE v_row.statement; END IF; END LOOP; END $$; DROP TABLE IF EXISTS partman_preserve_privs_temp; pg_partman-4.6.0/updates/pg_partman--2.4.0--2.4.1.sql000066400000000000000000000144571412761706100214400ustar00rootroot00000000000000-- Fixed partition_data_id() function to allow intervals larger than a 32-bit integer (bigint) (Github Pull Request #125). -- Fixed vacuum_maintenance.py to work with timestamptz now that all extension time data types were fixed in v2.4.0 -- Fixed pgtap failure in postgres 9.5 for id subpartitioning test (Github Issue #82). CREATE TEMP TABLE partman_preserve_privs_temp (statement text); INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.partition_data_id(text, int, bigint, numeric, text) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'partition_data_id'; DROP FUNCTION partition_data_id(text, int, int, numeric, text); /* * Populate the child table(s) of an id-based partition set with old data from the original parent */ CREATE FUNCTION partition_data_id(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval bigint DEFAULT NULL, p_lock_wait numeric DEFAULT 0, p_order text DEFAULT 'ASC') RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_current_partition_name text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_id bigint; v_min_partition_id bigint; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_partition_id bigint[]; v_rowcount bigint; v_sql text; v_start_control bigint; v_total_rows bigint := 0; BEGIN SELECT partition_interval::bigint , control INTO v_partition_interval , v_control FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; 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'); SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; FOR i IN 1..p_batch_count LOOP IF p_order = 'ASC' THEN EXECUTE format('SELECT min(%I) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; IF v_start_control IS NULL THEN EXIT; END IF; v_min_partition_id = v_start_control - (v_start_control % v_partition_interval); v_partition_id := ARRAY[v_min_partition_id]; -- Check if custom batch interval overflows current partition maximum IF (v_start_control + p_batch_interval) >= (v_min_partition_id + v_partition_interval) THEN v_max_partition_id := v_min_partition_id + v_partition_interval; ELSE v_max_partition_id := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN EXECUTE 'SELECT max('||v_control||') FROM ONLY '||p_parent_table INTO v_start_control; IF v_start_control IS NULL THEN EXIT; END IF; v_min_partition_id = v_start_control - (v_start_control % v_partition_interval); -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_id := v_min_partition_id + v_partition_interval; v_partition_id := ARRAY[v_min_partition_id]; -- Make sure minimum doesn't underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_id THEN v_min_partition_id = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN v_sql := format('SELECT * FROM ONLY %I.%I WHERE %I >= %s AND %I < %s FOR UPDATE NOWAIT' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_id , v_control , v_max_partition_id); EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; PERFORM @extschema@.create_partition_id(p_parent_table, v_partition_id); v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_min_partition_id::text, TRUE); EXECUTE format('WITH partition_data AS ( DELETE FROM ONLY %I.%I WHERE %I >= %s AND %I < %s RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_id , v_control , v_max_partition_id , v_parent_schema , v_current_partition_name); GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; PERFORM @extschema@.create_function_id(p_parent_table); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_total_rows; END $$; -- Restore dropped object privileges DO $$ DECLARE v_row record; BEGIN FOR v_row IN SELECT statement FROM partman_preserve_privs_temp LOOP IF v_row.statement IS NOT NULL THEN EXECUTE v_row.statement; END IF; END LOOP; END $$; DROP TABLE IF EXISTS partman_preserve_privs_temp; pg_partman-4.6.0/updates/pg_partman--2.4.1--2.5.0.sql000066400000000000000000002006641412761706100214360ustar00rootroot00000000000000-- Added very limited support for INSERT ... ON CONFLICT (upsert) in the partitioning trigger. For situations where only new data is being inserted, this can provide significant performance improvements. However, major limitations are that the constraint violations that would trigger the ON CONFLICT clause only occur on a per child table basis. This is a known limitation for inheritance in general. Constraints DO NOT apply across all tables in an inheritance set (Ex. Primary keys are only enforced for each individual child table and not for all tables in the partition set. Data duplication is possible). The included python script "check_unique_constraint.py" can help mitigate duplication, but cannot prevent it. Of a larger concern is an ON CONFLICT DO UPDATE clause which may not fire and cause wildly inconsistent data if not accounted for. These limitations will likely never be overcome in this extension until global indexes or constraints for inheritance sets are supported in PostgreSQL. It is recommended you test this feature out extensively before implementing in production and monitor it carefully. Many thanks to MikaelUlvesjo for contributing work on this issue (Github Issue #105 & Pull Request #122). -- Added check to part_config_sub to ensure premake > 0. Makes it consistent with part_config table. -- Allow internal check_version() function to work with test releases of postgres. If it's an alpha, beta or rc release it ignores the current version, so you're on your own if things fail due to version feature mismatches. ALTER TABLE @extschema@.part_config ADD COLUMN upsert TEXT NOT NULL DEFAULT ''; ALTER TABLE @extschema@.part_config_sub ADD COLUMN sub_upsert TEXT NOT NULL DEFAULT ''; ALTER TABLE @extschema@.part_config_sub ADD CONSTRAINT positive_premake_check CHECK (sub_premake > 0); CREATE TEMP TABLE partman_preserve_privs_temp (statement text); INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_parent(text, text, text, text, text[], int, boolean, text, boolean, boolean, text, boolean, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_parent'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_sub_parent(text, text, text, text, text[], int, text, boolean, boolean, text, boolean, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_sub_parent'; DROP FUNCTION create_parent(text, text, text, text, text[], int, boolean, text, boolean, boolean, boolean, boolean); DROP FUNCTION create_sub_parent(text, text, text, text, text[], int, text, boolean, boolean, boolean, boolean); /* * Create the trigger function for the parent table of an id-based partition set */ CREATE OR REPLACE FUNCTION create_function_id(p_parent_table text, p_job_id bigint DEFAULT NULL) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_control text; v_count int; v_current_partition_name text; v_current_partition_id bigint; v_datetime_string text; v_final_partition_id bigint; v_function_name 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_position int; v_job_id bigint; v_jobmon text; v_jobmon_schema text; v_last_partition text; v_max bigint; v_next_partition_id bigint; v_next_partition_name text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_optimize_trigger int; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_premake int; v_prev_partition_id bigint; v_prev_partition_name text; v_row_max_id record; v_run_maint boolean; v_step_id bigint; v_top_parent text := p_parent_table; v_trig_func text; v_trigger_exception_handling boolean; v_upsert text; BEGIN SELECT partition_interval::bigint , control , premake , optimize_trigger , use_run_maintenance , jobmon , trigger_exception_handling , upsert INTO v_partition_interval , v_control , v_premake , v_optimize_trigger , v_run_maint , v_jobmon , v_trigger_exception_handling , v_upsert FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE FUNCTION: %s', p_parent_table)); ELSE v_job_id = p_job_id; END IF; v_step_id := add_step(v_job_id, format('Creating partition function for table %s', p_parent_table)); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); -- Get the highest level top parent if multi-level partitioned in order to get proper max() value below 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 INTO v_higher_parent_schema, v_higher_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 WHERE p.partition_type = 'id'; IF v_higher_parent_table IS NOT NULL THEN -- initially set in DECLARE v_top_parent := v_higher_parent_schema||'.'||v_higher_parent_table; END IF; END LOOP; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_top_parent, 'DESC') LOOP EXECUTE format('SELECT max(%I) FROM %I.%I', v_control, v_row_max_id.partition_schemaname, v_row_max_id.partition_tablename) INTO v_max; IF v_max IS NOT NULL THEN EXIT; END IF; END LOOP; IF v_max IS NULL THEN v_max := 0; END IF; v_current_partition_id = v_max - (v_max % v_partition_interval); v_next_partition_id := v_current_partition_id + v_partition_interval; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_current_partition_id::text, TRUE); v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_current_partition_id bigint; v_current_partition_name text; v_id_position int; v_last_partition text := %L; v_next_partition_id bigint; v_next_partition_name text; v_partition_created boolean; BEGIN IF TG_OP = ''INSERT'' THEN IF NEW.%I >= %s AND NEW.%I < %s THEN ' , v_parent_schema , v_function_name , v_last_partition , v_control , v_current_partition_id , v_control , v_next_partition_id ); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_current_partition_name::name; IF v_count > 0 THEN v_trig_func := v_trig_func || format(' INSERT INTO %I.%I VALUES (NEW.*) %s; ', v_parent_schema, v_current_partition_name, v_upsert); ELSE v_trig_func := v_trig_func || ' -- Child table for current values does not exist in this partition set, so write to parent RETURN NEW;'; END IF; FOR i IN 1..v_optimize_trigger LOOP v_prev_partition_id := v_current_partition_id - (v_partition_interval * i); v_next_partition_id := v_current_partition_id + (v_partition_interval * i); v_final_partition_id := v_next_partition_id + v_partition_interval; v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, v_prev_partition_id::text, TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, v_next_partition_id::text, TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles optimize_trigger being larger than premake (to go back in time further) and edge case of changing optimize_trigger immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_prev_partition_name::name; IF v_count > 0 THEN -- Only handle previous partitions if they're starting above zero IF v_prev_partition_id >= 0 THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %s AND NEW.%I < %s THEN INSERT INTO %I.%I VALUES (NEW.*) %s; ' , v_control , v_prev_partition_id , v_control , v_prev_partition_id + v_partition_interval , v_parent_schema , v_prev_partition_name , v_upsert ); END IF; END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_next_partition_name::name; IF v_count > 0 THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %s AND NEW.%I < %s THEN INSERT INTO %I.%I VALUES (NEW.*) %s;' , v_control , v_next_partition_id , v_control , v_final_partition_id , v_parent_schema , v_next_partition_name , v_upsert ); END IF; END LOOP; v_trig_func := v_trig_func ||format(' ELSE v_current_partition_id := NEW.%I - (NEW.%I %% %s); v_current_partition_name := @extschema@.check_name_length(%L, v_current_partition_id::text, TRUE); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = %L::name AND tablename = v_current_partition_name::name; IF v_count > 0 THEN EXECUTE format(''INSERT INTO %%I.%%I VALUES($1.*) %s'', %L, v_current_partition_name) USING NEW; ELSE RETURN NEW; END IF; END IF;' , v_control , v_control , v_partition_interval , v_parent_tablename , v_parent_schema , v_upsert , v_parent_schema ); IF v_run_maint IS FALSE THEN v_trig_func := v_trig_func ||format(' v_current_partition_id := NEW.%I - (NEW.%I %% %s); IF (NEW.%I %% %s) > (%s / 2) THEN v_id_position := (length(v_last_partition) - position(''p_'' in reverse(v_last_partition))) + 2; v_next_partition_id := (substring(v_last_partition from v_id_position)::bigint) + %s; WHILE ((v_next_partition_id - v_current_partition_id) / %s) <= %s LOOP v_partition_created := @extschema@.create_partition_id(%L, ARRAY[v_next_partition_id]); IF v_partition_created THEN PERFORM @extschema@.create_function_id(%L); PERFORM @extschema@.apply_constraints(%L); END IF; v_next_partition_id := v_next_partition_id + %s; END LOOP; END IF;' , v_control , v_control , v_partition_interval , v_control , v_partition_interval , v_partition_interval , v_partition_interval , v_partition_interval , v_premake , p_parent_table , p_parent_table , p_parent_table , v_partition_interval ); END IF; v_trig_func := v_trig_func ||' END IF; RETURN NULL;'; IF v_trigger_exception_handling THEN v_trig_func := v_trig_func ||' EXCEPTION WHEN OTHERS THEN RAISE WARNING ''pg_partman insert into child table failed, row inserted into parent (%.%). ERROR: %'', TG_TABLE_SCHEMA, TG_TABLE_NAME, COALESCE(SQLERRM, ''unknown''); RETURN NEW;'; END IF; v_trig_func := v_trig_func ||' END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for current id interval: %s to %s', v_current_partition_id, v_final_partition_id-1)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); 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 FUNCTION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''Partition function maintenance for table %s 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 $$; /* * Create the trigger function for the parent table of a time-based partition set */ CREATE OR REPLACE FUNCTION create_function_time(p_parent_table text, p_job_id bigint DEFAULT NULL) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_control text; v_count int; v_current_partition_name text; v_current_partition_timestamp timestamptz; v_datetime_string text; v_epoch boolean; v_final_partition_timestamp timestamptz; v_function_name text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_new_length int; v_next_partition_name text; v_next_partition_timestamp timestamptz; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_prev_partition_name text; v_prev_partition_timestamp timestamptz; v_step_id bigint; v_trig_func text; v_optimize_trigger int; v_trigger_exception_handling boolean; v_type text; v_upsert text; BEGIN SELECT partition_type , partition_interval::interval , epoch , control , optimize_trigger , datetime_string , jobmon , trigger_exception_handling , upsert INTO v_type , v_partition_interval , v_epoch , v_control , v_optimize_trigger , v_datetime_string , v_jobmon , v_trigger_exception_handling , v_upsert FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE FUNCTION: %s', p_parent_table)); ELSE v_job_id = p_job_id; END IF; v_step_id := add_step(v_job_id, format('Creating partition function for table %s', p_parent_table)); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); IF v_type = 'time' THEN v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_partition_name text; v_partition_timestamp timestamptz; BEGIN IF TG_OP = ''INSERT'' THEN ' , v_parent_schema , v_function_name); IF v_epoch = false THEN CASE WHEN v_partition_interval = '15 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', NEW.%I) + ''15min''::interval * floor(date_part(''minute'', NEW.%I) / 15.0);' , v_control , v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', NEW.%I) + ''30min''::interval * floor(date_part(''minute'', NEW.%I) / 30.0);' , v_control , v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 day' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''day'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 week' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''week'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 month' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''month'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_partition_interval = '3 months' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''quarter'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 year' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''year'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; ELSE -- epoch is true CASE WHEN v_partition_interval = '15 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', to_timestamp(NEW.%I)) + ''15min''::interval * floor(date_part(''minute'', NEW.%I) / 15.0);' , v_control , v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', to_timestamp(NEW.%I)) + ''30min''::interval * floor(date_part(''minute'', NEW.%I) / 30.0);' , v_control , v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', to_timestamp(NEW.%I));', v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 day' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''day'', to_timestamp(NEW.%I));', v_control); v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 week' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''week'', to_timestamp(NEW.%I));', v_control); v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 month' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''month'', to_timestamp(NEW.%I));', v_control); v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_partition_interval = '3 months' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''quarter'', to_timestamp(NEW.%I));', v_control); v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 year' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''year'', to_timestamp(NEW.%I));', v_control); v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; END IF; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, to_char(v_current_partition_timestamp, v_datetime_string), TRUE); v_next_partition_timestamp := v_current_partition_timestamp + v_partition_interval::interval; IF v_epoch = false THEN v_trig_func := v_trig_func ||format(' IF NEW.%I >= %L AND NEW.%I < %L THEN ' , v_control , v_current_partition_timestamp , v_control , v_next_partition_timestamp); ELSE v_trig_func := v_trig_func ||format(' IF to_timestamp(NEW.%I) >= %L AND to_timestamp(NEW.%I) < %L THEN ' , v_control , v_current_partition_timestamp , v_control , v_next_partition_timestamp); END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_current_partition_name::name; IF v_count > 0 THEN v_trig_func := v_trig_func || format(' INSERT INTO %I.%I VALUES (NEW.*) %s; ', v_parent_schema, v_current_partition_name, v_upsert); ELSE v_trig_func := v_trig_func || ' -- Child table for current values does not exist in this partition set, so write to parent RETURN NEW;'; END IF; FOR i IN 1..v_optimize_trigger LOOP v_prev_partition_timestamp := v_current_partition_timestamp - (v_partition_interval::interval * i); v_next_partition_timestamp := v_current_partition_timestamp + (v_partition_interval::interval * i); v_final_partition_timestamp := v_next_partition_timestamp + (v_partition_interval::interval); v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, to_char(v_prev_partition_timestamp, v_datetime_string), TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, to_char(v_next_partition_timestamp, v_datetime_string), TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles optimize_trigger being larger than premake (to go back in time further) and edge case of changing optimize_trigger immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_prev_partition_name::name; IF v_count > 0 THEN IF v_epoch = false THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %L AND NEW.%I < %L THEN INSERT INTO %I.%I VALUES (NEW.*) %s;' , v_control , v_prev_partition_timestamp , v_control , v_prev_partition_timestamp + v_partition_interval::interval , v_parent_schema , v_prev_partition_name , v_upsert); ELSE v_trig_func := v_trig_func ||format(' ELSIF to_timestamp(NEW.%I) >= %L AND to_timestamp(NEW.%I) < %L THEN INSERT INTO %I.%I VALUES (NEW.*) %s;' , v_control , v_prev_partition_timestamp , v_control , v_prev_partition_timestamp + v_partition_interval::interval , v_parent_schema , v_prev_partition_name , v_upsert); END IF; END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_next_partition_name::name; IF v_count > 0 THEN IF v_epoch = false THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %L AND NEW.%I < %L THEN INSERT INTO %I.%I VALUES (NEW.*) %s;' , v_control , v_next_partition_timestamp , v_control , v_final_partition_timestamp , v_parent_schema , v_next_partition_name , v_upsert); ELSE v_trig_func := v_trig_func ||format(' ELSIF to_timestamp(NEW.%I) >= %L AND to_timestamp(NEW.%I) < %L THEN INSERT INTO %I.%I VALUES (NEW.*) %s;' , v_control , v_next_partition_timestamp , v_control , v_final_partition_timestamp , v_parent_schema , v_next_partition_name , v_upsert); END IF; END IF; END LOOP; v_trig_func := v_trig_func||format(' ELSE v_partition_name := @extschema@.check_name_length(%L, to_char(v_partition_timestamp, %L), TRUE); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = %L::name AND tablename = v_partition_name::name; IF v_count > 0 THEN EXECUTE format(''INSERT INTO %%I.%%I VALUES($1.*) %s'', %L, v_partition_name) USING NEW; ELSE RETURN NEW; END IF; END IF;' , v_parent_tablename , v_datetime_string , v_parent_schema , v_upsert , v_parent_schema); v_trig_func := v_trig_func ||' END IF; RETURN NULL;'; IF v_trigger_exception_handling THEN v_trig_func := v_trig_func ||' EXCEPTION WHEN OTHERS THEN RAISE WARNING ''pg_partman insert into child table failed, row inserted into parent (%.%). ERROR: %'', TG_TABLE_SCHEMA, TG_TABLE_NAME, COALESCE(SQLERRM, ''unknown''); RETURN NEW;'; END IF; v_trig_func := v_trig_func ||' END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for current time interval: %s to %s' , v_current_partition_timestamp , v_final_partition_timestamp-'1sec'::interval)); END IF; ELSIF v_type = 'time-custom' THEN v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_child_schemaname text; v_child_table text; v_child_tablename text; v_upsert text; BEGIN ' , v_parent_schema , v_function_name); IF v_epoch = false THEN v_trig_func := v_trig_func || format(' SELECT c.child_table, p.upsert INTO v_child_table, v_upsert FROM @extschema@.custom_time_partitions c JOIN @extschema@.part_config p ON c.parent_table = p.parent_table WHERE c.partition_range @> NEW.%I AND c.parent_table = %L;' , v_control , v_parent_schema||'.'||v_parent_tablename); ELSE -- epoch true v_trig_func := v_trig_func || format(' SELECT c.child_table, p.upsert INTO v_child_table, v_upsert FROM @extschema@.custom_time_partitions c JOIN @extschema@.part_config p ON c.parent_table = p.parent_table WHERE c.partition_range @> to_timestamp(NEW.%I) AND c.parent_table = %L;' , v_control , v_parent_schema||'.'||v_parent_tablename); END IF; v_trig_func := v_trig_func || ' SELECT schemaname, tablename INTO v_child_schemaname, v_child_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(v_child_table, ''.'', 1)::name AND tablename = split_part(v_child_table, ''.'', 2)::name; IF v_child_schemaname IS NOT NULL AND v_child_tablename IS NOT NULL THEN EXECUTE format(''INSERT INTO %I.%I VALUES ($1.*) %s'', v_child_schemaname, v_child_tablename, v_upsert) USING NEW; ELSE RETURN NEW; END IF; RETURN NULL;'; IF v_trigger_exception_handling THEN v_trig_func := v_trig_func ||' EXCEPTION WHEN OTHERS THEN RAISE WARNING ''pg_partman insert into child table failed, row inserted into parent (%.%). ERROR: %'', TG_TABLE_SCHEMA, TG_TABLE_NAME, COALESCE(SQLERRM, ''unknown''); RETURN NEW;'; END IF; v_trig_func := v_trig_func ||' END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for custom time table: %s', p_parent_table)); END IF; ELSE RAISE EXCEPTION 'ERROR: Invalid time partitioning type given: %', v_type; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); 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 FUNCTION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''Partition function maintenance for table %s 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 $$; /* * Function to turn a table into the parent of a partition set */ CREATE FUNCTION 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_use_run_maintenance boolean DEFAULT NULL , p_start_partition text DEFAULT NULL , p_inherit_fk boolean DEFAULT true , p_epoch boolean DEFAULT false , p_upsert text DEFAULT '' , p_jobmon boolean DEFAULT true , p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_base_timestamp timestamptz; v_count int := 1; v_datetime_string 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_job_id bigint; v_jobmon_schema text; v_last_partition_created boolean; v_max bigint; v_notnull boolean; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_partition_id bigint; v_parent_partition_timestamp timestamptz; v_parent_schema text; v_parent_tablename text; v_partition_time timestamptz; v_partition_time_array timestamptz[]; v_partition_id_array bigint[]; v_row record; v_run_maint boolean; 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_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); BEGIN IF position('.' in p_parent_table) = 0 THEN RAISE EXCEPTION 'Parent table must be schema qualified'; END IF; IF p_upsert IS NOT NULL AND @extschema@.check_version('9.5.0') = 'false' THEN RAISE EXCEPTION 'INSERT ... ON CONFLICT (UPSERT) feature is only supported in PostgreSQL 9.5 and later'; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = 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 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; IF p_type = 'id' AND p_epoch = true THEN RAISE EXCEPTION 'p_epoch can only be used with time-based partitioning'; END IF; IF NOT @extschema@.check_partition_type(p_type) THEN RAISE EXCEPTION '% is not a valid partitioning type', p_type; 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'); IF p_use_run_maintenance IS NOT NULL THEN IF p_use_run_maintenance IS FALSE AND (p_type = 'time' OR p_type = 'time-custom') THEN RAISE EXCEPTION 'p_run_maintenance cannot be set to false for time based partitioning'; END IF; v_run_maint := p_use_run_maintenance; ELSIF p_type = 'time' OR p_type = 'time-custom' THEN v_run_maint := TRUE; ELSIF p_type = 'id' THEN v_run_maint := FALSE; ELSE RAISE EXCEPTION 'use_run_maintenance value cannot be set NULL'; END IF; 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 ) 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_use_run_maintenance , sub_epoch , sub_optimize_trigger , sub_optimize_constraint , sub_infinite_time_partitions , sub_jobmon , sub_trigger_exception_handling , sub_upsert 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_use_run_maintenance , sub_epoch , sub_optimize_trigger , sub_optimize_constraint , sub_infinite_time_partitions , sub_jobmon , sub_trigger_exception_handling , sub_upsert) 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_use_run_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); END LOOP; IF p_type = 'time' OR p_type = 'time-custom' 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 <> 'time-custom' THEN RAISE EXCEPTION 'Must use a predefined time interval if not using type "time-custom". See documentation.'; 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 , use_run_maintenance , inherit_fk , jobmon , upsert) VALUES ( p_parent_table , p_type , v_time_interval , p_epoch , p_control , p_premake , p_constraint_cols , v_datetime_string , v_run_maint , p_inherit_fk , p_jobmon , p_upsert); 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 -- Currently unknown edge case if code gets here RAISE EXCEPTION 'No child tables created. Unexpected edge case encountered. Please report this error to author with conditions that led to it.'; END IF; END 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 p_type = 'id' THEN v_id_interval := p_interval::bigint; IF v_id_interval < 10 THEN RAISE EXCEPTION 'Interval for serial 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 INTO v_higher_parent_schema, v_higher_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 WHERE p.partition_type = 'id'; IF v_higher_parent_table IS NOT NULL THEN -- 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 , use_run_maintenance , inherit_fk , jobmon , upsert) VALUES ( p_parent_table , p_type , v_id_interval , p_control , p_premake , p_constraint_cols , v_run_maint , p_inherit_fk , p_jobmon , p_upsert); 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 WHERE p.partition_type = 'id'; 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 EXCEPTION 'No child tables created. Unexpected edge case encountered. Please report this error to author with conditions that led to it.'; END IF; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating partition function'); END IF; IF p_type = 'time' OR p_type = 'time-custom' 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 p_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); 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 $$; /* * 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. * To avoid logical complications and contention issues, ALL subpartitions must be maintained using run_maintenance(). * This means the automatic, trigger based partition creation for serial partitioning will not work if it is a subpartition. */ CREATE FUNCTION create_sub_parent( p_top_parent text , p_control text , p_type text , p_interval text , 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 boolean DEFAULT false , p_upsert text DEFAULT '' , p_jobmon boolean DEFAULT true , p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_last_partition text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_interval text; v_parent_type text; v_row record; v_row_last_part record; v_run_maint boolean; v_sql text; v_success boolean := false; v_top_type text; BEGIN SELECT use_run_maintenance INTO v_run_maint FROM @extschema@.part_config WHERE parent_table = p_top_parent; IF v_run_maint 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; ELSIF v_run_maint = false THEN RAISE EXCEPTION 'Any parent table that will be part of a sub-partitioned set (on any level) must have use_run_maintenance set to true in part_config table, even for serial partitioning. See documentation for more info.'; END IF; IF p_upsert IS NOT NULL AND @extschema@.check_version('9.5.0') = 'false' THEN RAISE EXCEPTION 'INSERT ... ON CONFLICT (UPSERT) feature is only supported in PostgreSQL 9.5 and later'; END IF; 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'); FOR v_row IN -- Loop through all current children to turn them into partitioned tables SELECT partition_schemaname||'.'||partition_tablename AS child_table FROM @extschema@.show_partitions(p_top_parent) LOOP SELECT partition_type, partition_interval INTO v_parent_type, v_parent_interval FROM @extschema@.part_config WHERE parent_table = v_row.child_table; IF v_parent_interval = p_interval THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RAISE EXCEPTION 'Sub-partition interval cannot be equal to parent interval'; END IF; IF (v_parent_type = 'time' OR v_parent_type = 'time-custom') AND (p_type = 'time' OR p_type = 'time-custom') THEN IF p_interval::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 the given parent interval'; END IF; IF p_interval = 'weekly' 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_parent_type = 'id' 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 the given parent interval'; END IF; END IF; -- Just call existing create_parent() function but add the given parameters to the part_config_sub table as well 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_use_run_maintenance := %L , p_start_partition := %L , p_inherit_fk := %L , p_epoch := %L , p_upsert := %L , p_jobmon := %L , p_debug := %L )' , v_row.child_table , p_control , p_type , p_interval , p_constraint_cols , p_premake , true , p_start_partition , p_inherit_fk , p_epoch , p_upsert , p_jobmon , p_debug); EXECUTE v_sql; END LOOP; 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_use_run_maintenance , sub_epoch , sub_upsert , sub_jobmon) VALUES ( p_top_parent , p_control , p_type , p_interval , p_constraint_cols , p_premake , p_inherit_fk , true , p_epoch , p_upsert , p_jobmon); v_success := true; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_success; END $$; /* * Check PostgreSQL version number. Parameter must be full 3 point version. * Returns true if current version is greater than or equal to the parameter given. */ CREATE OR REPLACE FUNCTION check_version(p_check_version text) RETURNS boolean LANGUAGE plpgsql STABLE AS $$ DECLARE v_check_version text[]; v_current_version text[] := string_to_array(current_setting('server_version'), '.'); BEGIN v_check_version := string_to_array(p_check_version, '.'); IF v_current_version[1]::int > v_check_version[1]::int THEN RETURN true; END IF; IF v_current_version[1]::int = v_check_version[1]::int THEN IF substring(v_current_version[2] from 'beta') IS NOT NULL OR substring(v_current_version[2] from 'alpha') IS NOT NULL OR substring(v_current_version[2] from 'rc') IS NOT NULL THEN -- You're running a test version. You're on your own if things fail. RETURN true; END IF; IF v_current_version[2]::int > v_check_version[2]::int THEN RETURN true; END IF; IF v_current_version[2]::int = v_check_version[2]::int THEN IF v_current_version[3]::int >= v_check_version[3]::int THEN RETURN true; END IF; -- 0.0.x END IF; -- 0.x.0 END IF; -- x.0.0 RETURN false; END $$; -- Restore dropped object privileges DO $$ DECLARE v_row record; BEGIN FOR v_row IN SELECT statement FROM partman_preserve_privs_temp LOOP IF v_row.statement IS NOT NULL THEN EXECUTE v_row.statement; END IF; END LOOP; END $$; DROP TABLE IF EXISTS partman_preserve_privs_temp; pg_partman-4.6.0/updates/pg_partman--2.5.0--2.5.1.sql000066400000000000000000000720521412761706100214350ustar00rootroot00000000000000-- Fix check for ensuring upsert feature can only be used in 9.5 in create_parent() & create_sub_parent(). Was causing erroneous failures with versions prior to 9.5. Thanks to Death_Syn & ilmari on #postgresql freenode for reporting the issue. /* * Function to turn a table into the parent of a partition set */ CREATE OR REPLACE FUNCTION 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_use_run_maintenance boolean DEFAULT NULL , p_start_partition text DEFAULT NULL , p_inherit_fk boolean DEFAULT true , p_epoch boolean DEFAULT false , p_upsert text DEFAULT '' , p_jobmon boolean DEFAULT true , p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_base_timestamp timestamptz; v_count int := 1; v_datetime_string 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_job_id bigint; v_jobmon_schema text; v_last_partition_created boolean; v_max bigint; v_notnull boolean; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_partition_id bigint; v_parent_partition_timestamp timestamptz; v_parent_schema text; v_parent_tablename text; v_partition_time timestamptz; v_partition_time_array timestamptz[]; v_partition_id_array bigint[]; v_row record; v_run_maint boolean; 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_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); BEGIN IF position('.' in p_parent_table) = 0 THEN RAISE EXCEPTION 'Parent table must be schema qualified'; END IF; IF p_upsert <> '' AND @extschema@.check_version('9.5.0') = 'false' THEN RAISE EXCEPTION 'INSERT ... ON CONFLICT (UPSERT) feature is only supported in PostgreSQL 9.5 and later'; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = 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 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; IF p_type = 'id' AND p_epoch = true THEN RAISE EXCEPTION 'p_epoch can only be used with time-based partitioning'; END IF; IF NOT @extschema@.check_partition_type(p_type) THEN RAISE EXCEPTION '% is not a valid partitioning type', p_type; 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'); IF p_use_run_maintenance IS NOT NULL THEN IF p_use_run_maintenance IS FALSE AND (p_type = 'time' OR p_type = 'time-custom') THEN RAISE EXCEPTION 'p_run_maintenance cannot be set to false for time based partitioning'; END IF; v_run_maint := p_use_run_maintenance; ELSIF p_type = 'time' OR p_type = 'time-custom' THEN v_run_maint := TRUE; ELSIF p_type = 'id' THEN v_run_maint := FALSE; ELSE RAISE EXCEPTION 'use_run_maintenance value cannot be set NULL'; END IF; 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 ) 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_use_run_maintenance , sub_epoch , sub_optimize_trigger , sub_optimize_constraint , sub_infinite_time_partitions , sub_jobmon , sub_trigger_exception_handling , sub_upsert 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_use_run_maintenance , sub_epoch , sub_optimize_trigger , sub_optimize_constraint , sub_infinite_time_partitions , sub_jobmon , sub_trigger_exception_handling , sub_upsert) 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_use_run_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); END LOOP; IF p_type = 'time' OR p_type = 'time-custom' 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 <> 'time-custom' THEN RAISE EXCEPTION 'Must use a predefined time interval if not using type "time-custom". See documentation.'; 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 , use_run_maintenance , inherit_fk , jobmon , upsert) VALUES ( p_parent_table , p_type , v_time_interval , p_epoch , p_control , p_premake , p_constraint_cols , v_datetime_string , v_run_maint , p_inherit_fk , p_jobmon , p_upsert); 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 -- Currently unknown edge case if code gets here RAISE EXCEPTION 'No child tables created. Unexpected edge case encountered. Please report this error to author with conditions that led to it.'; END IF; END 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 p_type = 'id' THEN v_id_interval := p_interval::bigint; IF v_id_interval < 10 THEN RAISE EXCEPTION 'Interval for serial 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 INTO v_higher_parent_schema, v_higher_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 WHERE p.partition_type = 'id'; IF v_higher_parent_table IS NOT NULL THEN -- 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 , use_run_maintenance , inherit_fk , jobmon , upsert) VALUES ( p_parent_table , p_type , v_id_interval , p_control , p_premake , p_constraint_cols , v_run_maint , p_inherit_fk , p_jobmon , p_upsert); 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 WHERE p.partition_type = 'id'; 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 EXCEPTION 'No child tables created. Unexpected edge case encountered. Please report this error to author with conditions that led to it.'; END IF; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating partition function'); END IF; IF p_type = 'time' OR p_type = 'time-custom' 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 p_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); 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 $$; /* * 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. * To avoid logical complications and contention issues, ALL subpartitions must be maintained using run_maintenance(). * This means the automatic, trigger based partition creation for serial partitioning will not work if it is a subpartition. */ CREATE OR REPLACE FUNCTION create_sub_parent( p_top_parent text , p_control text , p_type text , p_interval text , 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 boolean DEFAULT false , p_upsert text DEFAULT '' , p_jobmon boolean DEFAULT true , p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_last_partition text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_interval text; v_parent_type text; v_row record; v_row_last_part record; v_run_maint boolean; v_sql text; v_success boolean := false; v_top_type text; BEGIN SELECT use_run_maintenance INTO v_run_maint FROM @extschema@.part_config WHERE parent_table = p_top_parent; IF v_run_maint 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; ELSIF v_run_maint = false THEN RAISE EXCEPTION 'Any parent table that will be part of a sub-partitioned set (on any level) must have use_run_maintenance set to true in part_config table, even for serial partitioning. See documentation for more info.'; END IF; IF p_upsert <> '' AND @extschema@.check_version('9.5.0') = 'false' THEN RAISE EXCEPTION 'INSERT ... ON CONFLICT (UPSERT) feature is only supported in PostgreSQL 9.5 and later'; END IF; 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'); FOR v_row IN -- Loop through all current children to turn them into partitioned tables SELECT partition_schemaname||'.'||partition_tablename AS child_table FROM @extschema@.show_partitions(p_top_parent) LOOP SELECT partition_type, partition_interval INTO v_parent_type, v_parent_interval FROM @extschema@.part_config WHERE parent_table = v_row.child_table; IF v_parent_interval = p_interval THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RAISE EXCEPTION 'Sub-partition interval cannot be equal to parent interval'; END IF; IF (v_parent_type = 'time' OR v_parent_type = 'time-custom') AND (p_type = 'time' OR p_type = 'time-custom') THEN IF p_interval::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 the given parent interval'; END IF; IF p_interval = 'weekly' 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_parent_type = 'id' 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 the given parent interval'; END IF; END IF; -- Just call existing create_parent() function but add the given parameters to the part_config_sub table as well 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_use_run_maintenance := %L , p_start_partition := %L , p_inherit_fk := %L , p_epoch := %L , p_upsert := %L , p_jobmon := %L , p_debug := %L )' , v_row.child_table , p_control , p_type , p_interval , p_constraint_cols , p_premake , true , p_start_partition , p_inherit_fk , p_epoch , p_upsert , p_jobmon , p_debug); EXECUTE v_sql; END LOOP; 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_use_run_maintenance , sub_epoch , sub_upsert , sub_jobmon) VALUES ( p_top_parent , p_control , p_type , p_interval , p_constraint_cols , p_premake , p_inherit_fk , true , p_epoch , p_upsert , p_jobmon); v_success := true; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_success; END $$; pg_partman-4.6.0/updates/pg_partman--2.5.1--2.6.0.sql000066400000000000000000002605371412761706100214450ustar00rootroot00000000000000-- New function to aid in column removal: drop_partition_column(). -- Depending on when a column was added (before or after setting up inheritance for partitioning), dropping it on the parent may or may not drop the column from all children. -- Using this function will ensure the column is always dropped. -- Uses IF EXISTS clause to cleanly work even if column is missing from children already. Will give off warnings if column doesn't exist, but not return errors. -- This function can work for any inheritance set to drop columns, not just those maintained by pg_partman. -- Issue mentioned in blog post by Andrew Dunstan and also borrowed code from there for this function - http://adpgtech.blogspot.com/2015/07/dropping-columns-on-partitioned-tables.html -- Added new configuration option to control if the partitioning trigger returns NULL or NEW. Default is to return NULL. See documentation for further use of this option (Github Issue #128 & Pull Request #127). -- New parameter to create_parent() & create_sub_parent() so be sure to account for that in existing function calls -- Account for all new config columns when checking subpartition configuration consistency. If you suddenly start getting errors about subpartition configuration mismatches, ensure all sibling subpartition parents are configured exactly the same. See error returned by this check for more details on how to fix this. -- Added --version option to vacuum_maintenance.py ALTER TABLE @extschema@.part_config ADD COLUMN trigger_return_null boolean NOT NULL DEFAULT true; ALTER TABLE @extschema@.part_config_sub ADD COLUMN sub_trigger_return_null boolean NOT NULL DEFAULT true; CREATE TEMP TABLE partman_preserve_privs_temp (statement text); INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.check_subpart_sameconfig(text) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'check_subpart_sameconfig'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_parent(text, text, text, text, text[], int, boolean, text, boolean, boolean, text, boolean, boolean, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_parent'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_sub_parent(text, text, text, text, text[], int, text, boolean, boolean, text, boolean, boolean, p_debug) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = ''; DROP FUNCTION @extschema@.check_subpart_sameconfig(text); DROP FUNCTION @extschema@.create_parent(text, text, text, text, text[], int, boolean, text, boolean, boolean, text, boolean, boolean); DROP FUNCTION @extschema@.create_sub_parent(text, text, text, text, text[], int, text, boolean, boolean, text, boolean, boolean); /* * Function to ensure a column is dropped in all child tables, no matter when it was created */ CREATE FUNCTION drop_partition_column(p_parent_table text, p_column text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_child_oid oid; v_parent_oid oid; v_parent_schema text; v_parent_tablename text; v_row record; BEGIN SELECT c.oid, n.nspname, c.relname INTO v_parent_oid, v_parent_schema, v_parent_tablename 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 'Given parent table does not exist: %', p_parent_table; END IF; EXECUTE format('ALTER TABLE %I.%I DROP COLUMN IF EXISTS %I', v_parent_schema, v_parent_tablename, p_column); FOR v_row IN SELECT n.nspname AS child_schema, c.relname AS child_table 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 inhparent = v_parent_oid LOOP EXECUTE format('ALTER TABLE %I.%I DROP COLUMN IF EXISTS %I', v_row.child_schema, v_row.child_table, p_column); END LOOP; END $$; /* * Create the trigger function for the parent table of an id-based partition set */ CREATE OR REPLACE FUNCTION create_function_id(p_parent_table text, p_job_id bigint DEFAULT NULL) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_control text; v_count int; v_current_partition_name text; v_current_partition_id bigint; v_datetime_string text; v_final_partition_id bigint; v_function_name 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_position int; v_job_id bigint; v_jobmon text; v_jobmon_schema text; v_last_partition text; v_max bigint; v_next_partition_id bigint; v_next_partition_name text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_optimize_trigger int; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_premake int; v_prev_partition_id bigint; v_prev_partition_name text; v_row_max_id record; v_run_maint boolean; v_step_id bigint; v_top_parent text := p_parent_table; v_trig_func text; v_trigger_exception_handling boolean; v_trigger_return_null boolean; v_upsert text; BEGIN SELECT partition_interval::bigint , control , premake , optimize_trigger , use_run_maintenance , jobmon , trigger_exception_handling , upsert , trigger_return_null INTO v_partition_interval , v_control , v_premake , v_optimize_trigger , v_run_maint , v_jobmon , v_trigger_exception_handling , v_upsert , v_trigger_return_null FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE FUNCTION: %s', p_parent_table)); ELSE v_job_id = p_job_id; END IF; v_step_id := add_step(v_job_id, format('Creating partition function for table %s', p_parent_table)); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); -- Get the highest level top parent if multi-level partitioned in order to get proper max() value below 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 INTO v_higher_parent_schema, v_higher_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 WHERE p.partition_type = 'id'; IF v_higher_parent_table IS NOT NULL THEN -- initially set in DECLARE v_top_parent := v_higher_parent_schema||'.'||v_higher_parent_table; END IF; END LOOP; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_top_parent, 'DESC') LOOP EXECUTE format('SELECT max(%I) FROM %I.%I', v_control, v_row_max_id.partition_schemaname, v_row_max_id.partition_tablename) INTO v_max; IF v_max IS NOT NULL THEN EXIT; END IF; END LOOP; IF v_max IS NULL THEN v_max := 0; END IF; v_current_partition_id = v_max - (v_max % v_partition_interval); v_next_partition_id := v_current_partition_id + v_partition_interval; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_current_partition_id::text, TRUE); v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_current_partition_id bigint; v_current_partition_name text; v_id_position int; v_last_partition text := %L; v_next_partition_id bigint; v_next_partition_name text; v_partition_created boolean; BEGIN IF TG_OP = ''INSERT'' THEN IF NEW.%I >= %s AND NEW.%I < %s THEN ' , v_parent_schema , v_function_name , v_last_partition , v_control , v_current_partition_id , v_control , v_next_partition_id ); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_current_partition_name::name; IF v_count > 0 THEN v_trig_func := v_trig_func || format(' INSERT INTO %I.%I VALUES (NEW.*) %s; ', v_parent_schema, v_current_partition_name, v_upsert); ELSE v_trig_func := v_trig_func || ' -- Child table for current values does not exist in this partition set, so write to parent RETURN NEW;'; END IF; FOR i IN 1..v_optimize_trigger LOOP v_prev_partition_id := v_current_partition_id - (v_partition_interval * i); v_next_partition_id := v_current_partition_id + (v_partition_interval * i); v_final_partition_id := v_next_partition_id + v_partition_interval; v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, v_prev_partition_id::text, TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, v_next_partition_id::text, TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles optimize_trigger being larger than premake (to go back in time further) and edge case of changing optimize_trigger immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_prev_partition_name::name; IF v_count > 0 THEN -- Only handle previous partitions if they're starting above zero IF v_prev_partition_id >= 0 THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %s AND NEW.%I < %s THEN INSERT INTO %I.%I VALUES (NEW.*) %s; ' , v_control , v_prev_partition_id , v_control , v_prev_partition_id + v_partition_interval , v_parent_schema , v_prev_partition_name , v_upsert ); END IF; END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_next_partition_name::name; IF v_count > 0 THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %s AND NEW.%I < %s THEN INSERT INTO %I.%I VALUES (NEW.*) %s;' , v_control , v_next_partition_id , v_control , v_final_partition_id , v_parent_schema , v_next_partition_name , v_upsert ); END IF; END LOOP; v_trig_func := v_trig_func ||format(' ELSE v_current_partition_id := NEW.%I - (NEW.%I %% %s); v_current_partition_name := @extschema@.check_name_length(%L, v_current_partition_id::text, TRUE); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = %L::name AND tablename = v_current_partition_name::name; IF v_count > 0 THEN EXECUTE format(''INSERT INTO %%I.%%I VALUES($1.*) %s'', %L, v_current_partition_name) USING NEW; ELSE RETURN NEW; END IF; END IF;' , v_control , v_control , v_partition_interval , v_parent_tablename , v_parent_schema , v_upsert , v_parent_schema ); IF v_run_maint IS FALSE THEN v_trig_func := v_trig_func ||format(' v_current_partition_id := NEW.%I - (NEW.%I %% %s); IF (NEW.%I %% %s) > (%s / 2) THEN v_id_position := (length(v_last_partition) - position(''p_'' in reverse(v_last_partition))) + 2; v_next_partition_id := (substring(v_last_partition from v_id_position)::bigint) + %s; WHILE ((v_next_partition_id - v_current_partition_id) / %s) <= %s LOOP v_partition_created := @extschema@.create_partition_id(%L, ARRAY[v_next_partition_id]); IF v_partition_created THEN PERFORM @extschema@.create_function_id(%L); PERFORM @extschema@.apply_constraints(%L); END IF; v_next_partition_id := v_next_partition_id + %s; END LOOP; END IF;' , v_control , v_control , v_partition_interval , v_control , v_partition_interval , v_partition_interval , v_partition_interval , v_partition_interval , v_premake , p_parent_table , p_parent_table , p_parent_table , v_partition_interval ); END IF; v_trig_func := v_trig_func ||' END IF;'; IF v_trigger_return_null IS TRUE THEN v_trig_func := v_trig_func ||' RETURN NULL;'; ELSE v_trig_func := v_trig_func ||' RETURN NEW;'; END IF; IF v_trigger_exception_handling THEN v_trig_func := v_trig_func ||' EXCEPTION WHEN OTHERS THEN RAISE WARNING ''pg_partman insert into child table failed, row inserted into parent (%.%). ERROR: %'', TG_TABLE_SCHEMA, TG_TABLE_NAME, COALESCE(SQLERRM, ''unknown''); RETURN NEW;'; END IF; v_trig_func := v_trig_func ||' END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for current id interval: %s to %s', v_current_partition_id, v_final_partition_id-1)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); 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 FUNCTION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''Partition function maintenance for table %s 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 $$; /* * Create the trigger function for the parent table of a time-based partition set */ CREATE OR REPLACE FUNCTION create_function_time(p_parent_table text, p_job_id bigint DEFAULT NULL) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_control text; v_count int; v_current_partition_name text; v_current_partition_timestamp timestamptz; v_datetime_string text; v_epoch boolean; v_final_partition_timestamp timestamptz; v_function_name text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_new_length int; v_next_partition_name text; v_next_partition_timestamp timestamptz; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_prev_partition_name text; v_prev_partition_timestamp timestamptz; v_step_id bigint; v_trig_func text; v_optimize_trigger int; v_trigger_exception_handling boolean; v_trigger_return_null boolean; v_type text; v_upsert text; BEGIN SELECT partition_type , partition_interval::interval , epoch , control , optimize_trigger , datetime_string , jobmon , trigger_exception_handling , upsert , trigger_return_null INTO v_type , v_partition_interval , v_epoch , v_control , v_optimize_trigger , v_datetime_string , v_jobmon , v_trigger_exception_handling , v_upsert , v_trigger_return_null FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE FUNCTION: %s', p_parent_table)); ELSE v_job_id = p_job_id; END IF; v_step_id := add_step(v_job_id, format('Creating partition function for table %s', p_parent_table)); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); IF v_type = 'time' THEN v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_partition_name text; v_partition_timestamp timestamptz; BEGIN IF TG_OP = ''INSERT'' THEN ' , v_parent_schema , v_function_name); IF v_epoch = false THEN CASE WHEN v_partition_interval = '15 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', NEW.%I) + ''15min''::interval * floor(date_part(''minute'', NEW.%I) / 15.0);' , v_control , v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', NEW.%I) + ''30min''::interval * floor(date_part(''minute'', NEW.%I) / 30.0);' , v_control , v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 day' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''day'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 week' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''week'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 month' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''month'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_partition_interval = '3 months' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''quarter'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 year' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''year'', NEW.%I);', v_control); v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; ELSE -- epoch is true CASE WHEN v_partition_interval = '15 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', to_timestamp(NEW.%I)) + ''15min''::interval * floor(date_part(''minute'', NEW.%I) / 15.0);' , v_control , v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', to_timestamp(NEW.%I)) + ''30min''::interval * floor(date_part(''minute'', NEW.%I) / 30.0);' , v_control , v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', to_timestamp(NEW.%I));', v_control); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 day' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''day'', to_timestamp(NEW.%I));', v_control); v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 week' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''week'', to_timestamp(NEW.%I));', v_control); v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 month' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''month'', to_timestamp(NEW.%I));', v_control); v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_partition_interval = '3 months' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''quarter'', to_timestamp(NEW.%I));', v_control); v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 year' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''year'', to_timestamp(NEW.%I));', v_control); v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; END IF; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, to_char(v_current_partition_timestamp, v_datetime_string), TRUE); v_next_partition_timestamp := v_current_partition_timestamp + v_partition_interval::interval; IF v_epoch = false THEN v_trig_func := v_trig_func ||format(' IF NEW.%I >= %L AND NEW.%I < %L THEN ' , v_control , v_current_partition_timestamp , v_control , v_next_partition_timestamp); ELSE v_trig_func := v_trig_func ||format(' IF to_timestamp(NEW.%I) >= %L AND to_timestamp(NEW.%I) < %L THEN ' , v_control , v_current_partition_timestamp , v_control , v_next_partition_timestamp); END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_current_partition_name::name; IF v_count > 0 THEN v_trig_func := v_trig_func || format(' INSERT INTO %I.%I VALUES (NEW.*) %s; ', v_parent_schema, v_current_partition_name, v_upsert); ELSE v_trig_func := v_trig_func || ' -- Child table for current values does not exist in this partition set, so write to parent RETURN NEW;'; END IF; FOR i IN 1..v_optimize_trigger LOOP v_prev_partition_timestamp := v_current_partition_timestamp - (v_partition_interval::interval * i); v_next_partition_timestamp := v_current_partition_timestamp + (v_partition_interval::interval * i); v_final_partition_timestamp := v_next_partition_timestamp + (v_partition_interval::interval); v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, to_char(v_prev_partition_timestamp, v_datetime_string), TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, to_char(v_next_partition_timestamp, v_datetime_string), TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles optimize_trigger being larger than premake (to go back in time further) and edge case of changing optimize_trigger immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_prev_partition_name::name; IF v_count > 0 THEN IF v_epoch = false THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %L AND NEW.%I < %L THEN INSERT INTO %I.%I VALUES (NEW.*) %s;' , v_control , v_prev_partition_timestamp , v_control , v_prev_partition_timestamp + v_partition_interval::interval , v_parent_schema , v_prev_partition_name , v_upsert); ELSE v_trig_func := v_trig_func ||format(' ELSIF to_timestamp(NEW.%I) >= %L AND to_timestamp(NEW.%I) < %L THEN INSERT INTO %I.%I VALUES (NEW.*) %s;' , v_control , v_prev_partition_timestamp , v_control , v_prev_partition_timestamp + v_partition_interval::interval , v_parent_schema , v_prev_partition_name , v_upsert); END IF; END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_next_partition_name::name; IF v_count > 0 THEN IF v_epoch = false THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %L AND NEW.%I < %L THEN INSERT INTO %I.%I VALUES (NEW.*) %s;' , v_control , v_next_partition_timestamp , v_control , v_final_partition_timestamp , v_parent_schema , v_next_partition_name , v_upsert); ELSE v_trig_func := v_trig_func ||format(' ELSIF to_timestamp(NEW.%I) >= %L AND to_timestamp(NEW.%I) < %L THEN INSERT INTO %I.%I VALUES (NEW.*) %s;' , v_control , v_next_partition_timestamp , v_control , v_final_partition_timestamp , v_parent_schema , v_next_partition_name , v_upsert); END IF; END IF; END LOOP; v_trig_func := v_trig_func||format(' ELSE v_partition_name := @extschema@.check_name_length(%L, to_char(v_partition_timestamp, %L), TRUE); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = %L::name AND tablename = v_partition_name::name; IF v_count > 0 THEN EXECUTE format(''INSERT INTO %%I.%%I VALUES($1.*) %s'', %L, v_partition_name) USING NEW; ELSE RETURN NEW; END IF; END IF;' , v_parent_tablename , v_datetime_string , v_parent_schema , v_upsert , v_parent_schema); v_trig_func := v_trig_func ||' END IF;'; IF v_trigger_return_null IS TRUE THEN v_trig_func := v_trig_func ||' RETURN NULL;'; ELSE v_trig_func := v_trig_func ||' RETURN NEW;'; END IF; IF v_trigger_exception_handling THEN v_trig_func := v_trig_func ||' EXCEPTION WHEN OTHERS THEN RAISE WARNING ''pg_partman insert into child table failed, row inserted into parent (%.%). ERROR: %'', TG_TABLE_SCHEMA, TG_TABLE_NAME, COALESCE(SQLERRM, ''unknown''); RETURN NEW;'; END IF; v_trig_func := v_trig_func ||' END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for current time interval: %s to %s' , v_current_partition_timestamp , v_final_partition_timestamp-'1sec'::interval)); END IF; ELSIF v_type = 'time-custom' THEN v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_child_schemaname text; v_child_table text; v_child_tablename text; v_upsert text; BEGIN ' , v_parent_schema , v_function_name); IF v_epoch = false THEN v_trig_func := v_trig_func || format(' SELECT c.child_table, p.upsert INTO v_child_table, v_upsert FROM @extschema@.custom_time_partitions c JOIN @extschema@.part_config p ON c.parent_table = p.parent_table WHERE c.partition_range @> NEW.%I AND c.parent_table = %L;' , v_control , v_parent_schema||'.'||v_parent_tablename); ELSE -- epoch true v_trig_func := v_trig_func || format(' SELECT c.child_table, p.upsert INTO v_child_table, v_upsert FROM @extschema@.custom_time_partitions c JOIN @extschema@.part_config p ON c.parent_table = p.parent_table WHERE c.partition_range @> to_timestamp(NEW.%I) AND c.parent_table = %L;' , v_control , v_parent_schema||'.'||v_parent_tablename); END IF; v_trig_func := v_trig_func || ' SELECT schemaname, tablename INTO v_child_schemaname, v_child_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(v_child_table, ''.'', 1)::name AND tablename = split_part(v_child_table, ''.'', 2)::name; IF v_child_schemaname IS NOT NULL AND v_child_tablename IS NOT NULL THEN EXECUTE format(''INSERT INTO %I.%I VALUES ($1.*) %s'', v_child_schemaname, v_child_tablename, v_upsert) USING NEW; ELSE RETURN NEW; END IF;'; IF v_trigger_return_null IS TRUE THEN v_trig_func := v_trig_func ||' RETURN NULL;'; ELSE v_trig_func := v_trig_func ||' RETURN NEW;'; END IF; IF v_trigger_exception_handling THEN v_trig_func := v_trig_func ||' EXCEPTION WHEN OTHERS THEN RAISE WARNING ''pg_partman insert into child table failed, row inserted into parent (%.%). ERROR: %'', TG_TABLE_SCHEMA, TG_TABLE_NAME, COALESCE(SQLERRM, ''unknown''); RETURN NEW;'; END IF; v_trig_func := v_trig_func ||' END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for custom time table: %s', p_parent_table)); END IF; ELSE RAISE EXCEPTION 'ERROR: Invalid time partitioning type given: %', v_type; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); 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 FUNCTION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''Partition function maintenance for table %s 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 $$; /* * Check for consistent data in part_config_sub table. Was unable to get this working properly as either a constraint or trigger. * Would either delay raising an error until the next write (which I cannot predict) or disallow future edits to update a sub-partition set's configuration. * This is called by run_maintainance() and at least provides a consistent way to check that I know will run. * If anyone can get a working constraint/trigger, please help! */ CREATE FUNCTION @extschema@.check_subpart_sameconfig(p_parent_table text) RETURNS TABLE (sub_partition_type text , sub_control text , sub_partition_interval text , sub_constraint_cols text[] , sub_premake int , sub_inherit_fk boolean , sub_retention text , sub_retention_schema text , sub_retention_keep_table boolean , sub_retention_keep_index boolean , sub_use_run_maintenance boolean , sub_epoch boolean , sub_optimize_trigger int , sub_optimize_constraint int , sub_infinite_time_partitions boolean , sub_jobmon boolean , sub_trigger_exception_handling boolean , sub_upsert text , sub_trigger_return_null boolean) LANGUAGE sql STABLE SECURITY DEFINER SET search_path = @extschema@,pg_temp AS $$ WITH parent_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE n1.nspname = split_part(p_parent_table, '.', 1)::name AND c1.relname = split_part(p_parent_table, '.', 2)::name ) , child_tables AS ( SELECT n.nspname||'.'||c.relname AS tablename FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN parent_info pi ON h.inhparent = pi.oid ) -- Column order here must match the RETURNS TABLE definition SELECT DISTINCT a.sub_partition_type , a.sub_control , a.sub_partition_interval , a.sub_constraint_cols , a.sub_premake , a.sub_inherit_fk , a.sub_retention , a.sub_retention_schema , a.sub_retention_keep_table , a.sub_retention_keep_index , a.sub_use_run_maintenance , a.sub_epoch , a.sub_optimize_trigger , a.sub_optimize_constraint , a.sub_infinite_time_partitions , a.sub_jobmon , a.sub_trigger_exception_handling , a.sub_upsert , a.sub_trigger_return_null FROM @extschema@.part_config_sub a JOIN child_tables b on a.sub_parent = b.tablename; $$; /* * Function to turn a table into the parent of a partition set */ CREATE FUNCTION 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_use_run_maintenance boolean DEFAULT NULL , p_start_partition text DEFAULT NULL , p_inherit_fk boolean DEFAULT true , p_epoch boolean DEFAULT false , p_upsert text DEFAULT '' , p_trigger_return_null boolean DEFAULT true , p_jobmon boolean DEFAULT true , p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_base_timestamp timestamptz; v_count int := 1; v_datetime_string 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_job_id bigint; v_jobmon_schema text; v_last_partition_created boolean; v_max bigint; v_notnull boolean; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_partition_id bigint; v_parent_partition_timestamp timestamptz; v_parent_schema text; v_parent_tablename text; v_partition_time timestamptz; v_partition_time_array timestamptz[]; v_partition_id_array bigint[]; v_row record; v_run_maint boolean; 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_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); BEGIN IF position('.' in p_parent_table) = 0 THEN RAISE EXCEPTION 'Parent table must be schema qualified'; END IF; IF p_upsert <> '' AND @extschema@.check_version('9.5.0') = 'false' THEN RAISE EXCEPTION 'INSERT ... ON CONFLICT (UPSERT) feature is only supported in PostgreSQL 9.5 and later'; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = 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 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; IF p_type = 'id' AND p_epoch = true THEN RAISE EXCEPTION 'p_epoch can only be used with time-based partitioning'; END IF; IF NOT @extschema@.check_partition_type(p_type) THEN RAISE EXCEPTION '% is not a valid partitioning type', p_type; 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'); IF p_use_run_maintenance IS NOT NULL THEN IF p_use_run_maintenance IS FALSE AND (p_type = 'time' OR p_type = 'time-custom') THEN RAISE EXCEPTION 'p_run_maintenance cannot be set to false for time based partitioning'; END IF; v_run_maint := p_use_run_maintenance; ELSIF p_type = 'time' OR p_type = 'time-custom' THEN v_run_maint := TRUE; ELSIF p_type = 'id' THEN v_run_maint := FALSE; ELSE RAISE EXCEPTION 'use_run_maintenance value cannot be set NULL'; END IF; 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 ) 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_use_run_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 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_use_run_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) 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_use_run_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); END LOOP; IF p_type = 'time' OR p_type = 'time-custom' 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 <> 'time-custom' THEN RAISE EXCEPTION 'Must use a predefined time interval if not using type "time-custom". See documentation.'; 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 , use_run_maintenance , inherit_fk , jobmon , upsert , trigger_return_null) VALUES ( p_parent_table , p_type , v_time_interval , p_epoch , p_control , p_premake , p_constraint_cols , v_datetime_string , v_run_maint , p_inherit_fk , p_jobmon , p_upsert , p_trigger_return_null); 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 -- Currently unknown edge case if code gets here RAISE EXCEPTION 'No child tables created. Unexpected edge case encountered. Please report this error to author with conditions that led to it.'; END IF; END 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 p_type = 'id' THEN v_id_interval := p_interval::bigint; IF v_id_interval < 10 THEN RAISE EXCEPTION 'Interval for serial 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 INTO v_higher_parent_schema, v_higher_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 WHERE p.partition_type = 'id'; IF v_higher_parent_table IS NOT NULL THEN -- 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 , use_run_maintenance , inherit_fk , jobmon , upsert , trigger_return_null) VALUES ( p_parent_table , p_type , v_id_interval , p_control , p_premake , p_constraint_cols , v_run_maint , p_inherit_fk , p_jobmon , p_upsert , p_trigger_return_null); 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 WHERE p.partition_type = 'id'; 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 EXCEPTION 'No child tables created. Unexpected edge case encountered. Please report this error to author with conditions that led to it.'; END IF; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating partition function'); END IF; IF p_type = 'time' OR p_type = 'time-custom' 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 p_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); 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 $$; /* * 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. * To avoid logical complications and contention issues, ALL subpartitions must be maintained using run_maintenance(). * This means the automatic, trigger based partition creation for serial partitioning will not work if it is a subpartition. */ CREATE FUNCTION create_sub_parent( p_top_parent text , p_control text , p_type text , p_interval text , 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 boolean DEFAULT false , p_upsert text DEFAULT '' , p_trigger_return_null boolean DEFAULT true , p_jobmon boolean DEFAULT true , p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_last_partition text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_interval text; v_parent_type text; v_row record; v_row_last_part record; v_run_maint boolean; v_sql text; v_success boolean := false; v_top_type text; BEGIN SELECT use_run_maintenance INTO v_run_maint FROM @extschema@.part_config WHERE parent_table = p_top_parent; IF v_run_maint 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; ELSIF v_run_maint = false THEN RAISE EXCEPTION 'Any parent table that will be part of a sub-partitioned set (on any level) must have use_run_maintenance set to true in part_config table, even for serial partitioning. See documentation for more info.'; END IF; IF p_upsert <> '' AND @extschema@.check_version('9.5.0') = 'false' THEN RAISE EXCEPTION 'INSERT ... ON CONFLICT (UPSERT) feature is only supported in PostgreSQL 9.5 and later'; END IF; 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'); FOR v_row IN -- Loop through all current children to turn them into partitioned tables SELECT partition_schemaname||'.'||partition_tablename AS child_table FROM @extschema@.show_partitions(p_top_parent) LOOP SELECT partition_type, partition_interval INTO v_parent_type, v_parent_interval FROM @extschema@.part_config WHERE parent_table = v_row.child_table; IF v_parent_interval = p_interval THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RAISE EXCEPTION 'Sub-partition interval cannot be equal to parent interval'; END IF; IF (v_parent_type = 'time' OR v_parent_type = 'time-custom') AND (p_type = 'time' OR p_type = 'time-custom') THEN IF p_interval::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 the given parent interval'; END IF; IF p_interval = 'weekly' 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_parent_type = 'id' 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 the given parent interval'; END IF; END IF; -- Just call existing create_parent() function but add the given parameters to the part_config_sub table as well 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_use_run_maintenance := %L , p_start_partition := %L , p_inherit_fk := %L , p_epoch := %L , p_upsert := %L , p_trigger_return_null := %L , p_jobmon := %L , p_debug := %L )' , v_row.child_table , p_control , p_type , p_interval , p_constraint_cols , p_premake , true , p_start_partition , p_inherit_fk , p_epoch , p_upsert , p_trigger_return_null , p_jobmon , p_debug); EXECUTE v_sql; END LOOP; 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_use_run_maintenance , sub_epoch , sub_upsert , sub_jobmon , sub_trigger_return_null) VALUES ( p_top_parent , p_control , p_type , p_interval , p_constraint_cols , p_premake , p_inherit_fk , true , p_epoch , p_upsert , p_jobmon , p_trigger_return_null); v_success := true; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_success; END $$; /* * Function to manage pre-creation of the next partitions in a set. * Also manages dropping old partitions if the retention option is set. * If p_parent_table is passed, will only run run_maintenance() on that one table (no matter what the configuration table may have set for it) * Otherwise, will run on all tables in the config table with p_run_maintenance() set to true. * For large partition sets, running analyze can cause maintenance to take longer than expected. Can set p_analyze to false to avoid a forced analyze run. * Be aware that constraint exclusion may not work properly until an analyze on the partition set is run. */ CREATE OR REPLACE FUNCTION run_maintenance(p_parent_table text DEFAULT NULL, p_analyze boolean DEFAULT true, p_jobmon boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_check_subpart int; v_create_count int := 0; v_current_partition text; v_current_partition_id bigint; v_current_partition_timestamp timestamptz; v_datetime_string text; v_drop_count int := 0; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_created boolean; v_last_partition_id bigint; v_last_partition_timestamp timestamptz; v_max_id_parent bigint; v_max_time_parent timestamptz; v_new_search_path text := '@extschema@,pg_temp'; v_next_partition_id bigint; v_next_partition_timestamp timestamptz; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_premade_count int; v_premake_id_max bigint; v_premake_id_min bigint; v_premake_timestamp_min timestamptz; v_premake_timestamp_max timestamptz; v_row record; v_row_max_id record; v_row_max_time record; v_row_sub record; v_step_id bigint; v_step_overflow_id bigint; v_sub_id_max bigint; v_sub_id_max_suffix bigint; v_sub_id_min bigint; v_sub_parent text; v_sub_timestamp_max timestamptz; v_sub_timestamp_max_suffix timestamptz; v_sub_timestamp_min timestamptz; v_tablename text; v_tables_list_sql text; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman maintenance already running.'; RETURN; 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'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN RUN MAINTENANCE'); v_step_id := add_step(v_job_id, 'Running maintenance loop'); END IF; v_row := NULL; -- Ensure it's reset v_tables_list_sql := 'SELECT parent_table , partition_type , partition_interval , control , premake , datetime_string , undo_in_progress , sub_partition_set_full , epoch , infinite_time_partitions FROM @extschema@.part_config WHERE sub_partition_set_full = false'; IF p_parent_table IS NULL THEN v_tables_list_sql := v_tables_list_sql || ' AND use_run_maintenance = true'; ELSE v_tables_list_sql := v_tables_list_sql || format(' AND parent_table = %L', p_parent_table); END IF; FOR v_row IN EXECUTE v_tables_list_sql LOOP CONTINUE WHEN v_row.undo_in_progress; -- Check for consistent data in part_config_sub table. Was unable to get this working properly as either a constraint or trigger. -- Would either delay raising an error until the next write (which I cannot predict) or disallow future edits to update a sub-partition set's configuration. -- This way at least provides a consistent way to check that I know will run. If anyone can get a working constraint/trigger, please help! -- Don't have to worry about this in the serial trigger maintenance since subpartitioning requires run_maintenance(). SELECT sub_parent INTO v_sub_parent FROM @extschema@.part_config_sub WHERE sub_parent = v_row.parent_table; IF v_sub_parent IS NOT NULL THEN SELECT count(*) INTO v_check_subpart FROM @extschema@.check_subpart_sameconfig(v_row.parent_table); IF v_check_subpart > 1 THEN RAISE EXCEPTION 'Inconsistent data in part_config_sub table. Sub-partition tables that are themselves sub-partitions cannot have differing configuration values among their siblings. Run this query: "SELECT * FROM @extschema@.check_subpart_sameconfig(''%'');" This should only return a single row or nothing. If multiple rows are returned, results are all children of the given parent. Update the differing values to be consistent for your desired values.', v_row.sub_parent; END IF; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(v_row.parent_table, '.', 1)::name AND tablename = split_part(v_row.parent_table, '.', 2)::name; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LIMIT 1; IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_last_partition: %', v_row.parent_table, v_last_partition; END IF; IF v_row.partition_type = 'time' OR v_row.partition_type = 'time-custom' THEN SELECT child_start_time INTO v_last_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_row.partition_interval, v_row.parent_table); -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_time IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP IF v_row.epoch = false THEN EXECUTE format('SELECT max(%I)::text FROM %I.%I' , v_row.control , v_row_max_time.partition_schemaname , v_row_max_time.partition_tablename ) INTO v_current_partition_timestamp; ELSE EXECUTE format('SELECT to_timestamp(max(%I))::text FROM %I.%I' , v_row.control , v_row_max_time.partition_schemaname , v_row_max_time.partition_tablename ) INTO v_current_partition_timestamp; END IF; IF v_current_partition_timestamp IS NOT NULL THEN SELECT suffix_timestamp INTO v_current_partition_timestamp FROM @extschema@.show_partition_name(v_row.parent_table, v_current_partition_timestamp::text); EXIT; END IF; END LOOP; -- Check for values in the parent table. If they are there and greater than all child values, use that instead -- This allows maintenance to continue working properly if there is a large gap in data insertion. Data will remain in parent, but new tables will be created IF v_row.epoch = false THEN EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_row.control, v_parent_schema, v_parent_tablename) INTO v_max_time_parent; ELSE EXECUTE format('SELECT to_timestamp(max(%I)) FROM ONLY %I.%I', v_row.control, v_parent_schema, v_parent_tablename) INTO v_max_time_parent; END IF; IF p_debug THEN RAISE NOTICE 'run_maint: v_current_partition_timestamp: %, v_max_time_parent: %', v_current_partition_timestamp, v_max_time_parent; END IF; IF v_max_time_parent > v_current_partition_timestamp THEN SELECT suffix_timestamp INTO v_current_partition_timestamp FROM @extschema@.show_partition_name(v_row.parent_table, v_max_time_parent::text); END IF; IF v_current_partition_timestamp IS NULL THEN -- Partition set is completely empty IF v_row.infinite_time_partitions IS TRUE THEN -- Set it to now so new partitions continue to be created v_current_partition_timestamp = CURRENT_TIMESTAMP; ELSE -- Nothing to do CONTINUE; END IF; END IF; -- If this is a subpartition, determine if the last child table has been made. If so, mark it as full so future maintenance runs can skip it SELECT sub_min::timestamptz, sub_max::timestamptz INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'time'); IF v_sub_timestamp_max IS NOT NULL THEN SELECT suffix_timestamp INTO v_sub_timestamp_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_timestamp_max::text); IF v_sub_timestamp_max_suffix = v_last_partition_timestamp THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; -- Check and see how many premade partitions there are. v_premade_count = round(EXTRACT('epoch' FROM age(v_last_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); v_next_partition_timestamp := v_last_partition_timestamp; IF p_debug THEN RAISE NOTICE 'run_maint before loop: current_partition_timestamp: %, v_premade_count: %, v_sub_timestamp_min: %, v_sub_timestamp_max: %' , v_current_partition_timestamp , v_premade_count , v_sub_timestamp_min , v_sub_timestamp_max; END IF; -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed WHILE (v_premade_count < v_row.premake) LOOP IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_premade_count: %, v_next_partition_timestamp: %', v_row.parent_table, v_premade_count, v_next_partition_timestamp; END IF; IF v_next_partition_timestamp < v_sub_timestamp_min OR v_next_partition_timestamp > v_sub_timestamp_max THEN -- With subpartitioning, no need to run if the timestamp is not in the parent table's range EXIT; END IF; BEGIN v_next_partition_timestamp := v_next_partition_timestamp + v_row.partition_interval::interval; EXCEPTION WHEN datetime_field_overflow THEN v_premade_count := v_row.premake; -- do this so it can exit the premake check loop and continue in the outer for loop IF v_jobmon_schema IS NOT NULL THEN 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', format('Child partition creation skipped for parent table: %s', v_partition_time)); END IF; RAISE WARNING 'Attempted partition time interval is outside PostgreSQL''s supported time range. Child partition creation skipped for parent table %', v_row.parent_table; CONTINUE; END; v_last_partition_created := @extschema@.create_partition_time(v_row.parent_table, ARRAY[v_next_partition_timestamp], p_analyze); IF v_last_partition_created THEN v_create_count := v_create_count + 1; PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); END IF; v_premade_count = round(EXTRACT('epoch' FROM age(v_next_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); END LOOP; ELSIF v_row.partition_type = 'id' THEN -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%I)::text FROM %I.%I' , v_row.control , v_row_max_id.partition_schemaname , v_row_max_id.partition_tablename) INTO v_current_partition_id; IF v_current_partition_id IS NOT NULL THEN SELECT suffix_id INTO v_current_partition_id FROM @extschema@.show_partition_name(v_row.parent_table, v_current_partition_id::text); EXIT; END IF; END LOOP; -- Check for values in the parent table. If they are there and greater than all child values, use that instead -- This allows maintenance to continue working properly if there is a large gap in data insertion. Data will remain in parent, but new tables will be created EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_row.control, v_parent_schema, v_parent_tablename) INTO v_max_id_parent; IF v_max_id_parent > v_current_partition_id THEN SELECT suffix_id INTO v_current_partition_id FROM @extschema@.show_partition_name(v_row.parent_table, v_max_id_parent::text); END IF; IF v_current_partition_id IS NULL THEN -- Partition set is completely empty. Nothing to do CONTINUE; END IF; SELECT child_start_id INTO v_last_partition_id FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_row.partition_interval, v_row.parent_table); -- Determine if this table is a child of a subpartition parent. If so, get limits to see if run_maintenance even needs to run for it. SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'id'); IF v_sub_id_max IS NOT NULL THEN SELECT suffix_id INTO v_sub_id_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_id_max::text); IF v_sub_id_max_suffix = v_last_partition_id THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; v_next_partition_id := v_last_partition_id; v_premade_count := ((v_last_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed. WHILE (v_premade_count < v_row.premake) LOOP IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_premade_count: %, v_next_partition_id: %', v_row.parent_table, v_premade_count, v_next_partition_id; END IF; IF v_next_partition_id < v_sub_id_min OR v_next_partition_id > v_sub_id_max THEN -- With subpartitioning, no need to run if the id is not in the parent table's range EXIT; END IF; v_next_partition_id := v_next_partition_id + v_row.partition_interval::bigint; v_last_partition_created := @extschema@.create_partition_id(v_row.parent_table, ARRAY[v_next_partition_id], p_analyze); IF v_last_partition_created THEN v_create_count := v_create_count + 1; PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); END IF; v_premade_count := ((v_next_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); END LOOP; END IF; -- end main IF check for time or id END LOOP; -- end of creation loop -- Manage dropping old partitions if retention option is set FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND (partition_type = 'time' OR partition_type = 'time-custom') LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); END IF; END IF; IF v_drop_count > 0 THEN PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); END IF; END LOOP; FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND partition_type = 'id' LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); END IF; END IF; IF v_drop_count > 0 THEN PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Partition maintenance finished. %s partitions made. %s partitions dropped.', v_create_count, v_drop_count)); 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'); 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 RUN MAINTENANCE'')', v_jobmon_schema) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; -- Restore dropped object privileges DO $$ DECLARE v_row record; BEGIN FOR v_row IN SELECT statement FROM partman_preserve_privs_temp LOOP IF v_row.statement IS NOT NULL THEN EXECUTE v_row.statement; END IF; END LOOP; END $$; DROP TABLE IF EXISTS partman_preserve_privs_temp; pg_partman-4.6.0/updates/pg_partman--2.6.0--2.6.1.sql000066400000000000000000002511241412761706100214360ustar00rootroot00000000000000-- WARNING: The pg_partman background worker is currently incompatible with PostgreSQL 9.6. It runs fine in 9.4 and 9.5. I'm still working on trying to figure out what the issue is. If you must upgrade to 9.6 before this issue is fixed, you can disable the background worker by removing the entry for it in shared_preload_libraries and restarting postgres. Instead schedule the run_maintenance() plpgsql function to run via another scheduler method (cron). -- NOTE: A restart of the database is required to put the background worker code changes in place once the new library file is installed. -- Add p_analyze parameter to partition_data_id() and partition_data_time() functions to allow moving data out of the parent to be faster if an analyze is not required after each child table is created. If set to false, highly recommended that an analyze on the entire partitions set is run after moving data. (Github Pull Request #137) -- Also allows better control of automatic analyzing of new partitions for serial partition sets by allowing the p_analyze parameter to run_maintenance() to be passed further down the function call hierarchy. -- Updated partition_data.py to pass this parameter as FALSE when calling these functions. The script does a VACUUM ANALYZE when it completes all batches anyway, so an analyze per child table is not necessary. -- Improve performance during privilege inheritance when creating new partitions or when running reapply_privileges() function. Most noticable on databases with many objects (Github Issue #139). -- Fixed buffer overflow in background worker code if database name was too long (Github Pull Request #140). -- Fixed bug in background worker code that could cause improper initialization of dynamic bgw processes (Github Pull Request #140). -- Account for non-lowercase schemename for pg_partman's schema in background worker (Github PullRequest #140). CREATE TEMP TABLE partman_preserve_privs_temp (statement text); INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.partition_data_id(text, int, bigint, numeric, text, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'partition_data_id'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_function_id(text, bigint, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_function_id'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.partition_data_time(text, int, interval, numeric, text, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'partition_data_time'; DROP FUNCTION partition_data_id(text, int, bigint, numeric, text); DROP FUNCTION partition_data_time(text, int, interval, numeric, text); DROP FUNCTION create_function_id(text, bigint); /* * Custom view to help improve privilege lookups for pg_partman. * information_schema is a performance bottleneck since indexes aren't being used properly. */ CREATE VIEW @extschema@.table_privs AS SELECT u_grantor.rolname AS grantor, grantee.rolname AS grantee, nc.nspname AS table_schema, c.relname AS table_name, c.prtype AS privilege_type FROM ( SELECT oid, relname, relnamespace, relkind, relowner, (aclexplode(coalesce(relacl, acldefault('r', relowner)))).* FROM pg_class ) AS c (oid, relname, relnamespace, relkind, relowner, grantor, grantee, prtype, grantable), pg_namespace nc, pg_authid u_grantor, ( SELECT oid, rolname FROM pg_authid UNION ALL SELECT 0::oid, 'PUBLIC' ) AS grantee (oid, rolname) WHERE c.relnamespace = nc.oid AND c.relkind IN ('r', 'v') AND c.grantee = grantee.oid AND c.grantor = u_grantor.oid AND c.prtype IN ('INSERT', 'SELECT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER') AND (pg_has_role(u_grantor.oid, 'USAGE') OR pg_has_role(grantee.oid, 'USAGE') OR grantee.rolname = 'PUBLIC' ); /* * Apply privileges that exist on a given parent to the given child table */ CREATE OR REPLACE FUNCTION apply_privileges(p_parent_schema text, p_parent_tablename text, p_child_schema text, p_child_tablename text, p_job_id bigint DEFAULT NULL) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_child_grant record; v_child_owner text; v_grantees text[]; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_match boolean; v_parent_grant record; v_parent_owner text; v_revoke text; v_row_revoke record; v_sql text; v_step_id bigint; BEGIN SELECT jobmon INTO v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_schema ||'.'|| p_parent_tablename; IF v_jobmon IS NULL THEN RAISE EXCEPTION 'Given table is not managed by this extention: %.%', p_parent_schema, p_parent_tablename; END IF; SELECT tableowner INTO v_parent_owner FROM pg_catalog.pg_tables WHERE schemaname = p_parent_schema::name AND tablename = p_parent_tablename::name; SELECT tableowner INTO v_child_owner FROM pg_catalog.pg_tables WHERE schemaname = p_child_schema::name AND tablename = p_child_tablename::name; IF v_parent_owner IS NULL THEN RAISE EXCEPTION 'Given parent table does not exist: %.%', v_parent_schema, v_parent_tablename; END IF; IF v_child_owner IS NULL THEN RAISE EXCEPTION 'Given child table does not exist: %.%', v_child_schema, v_child_tablename; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; END IF; IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN EXECUTE format('SELECT %I.add_job(%L)', v_jobmon_schema, format('PARTMAN APPLYING PRIVILEGES TO CHILD TABLE: %s.%s', p_child_schema, p_child_tablename)) INTO v_job_id; ELSE v_job_id := p_job_id; END IF; EXECUTE format('SELECT %I.add_step(%L, %L)', v_jobmon_schema, v_job_id, format('Setting new child table privileges for %s.%s', p_child_schema, p_child_tablename)) INTO v_step_id; END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.update_step(%L, %L, %L)' , v_jobmon_schema , v_step_id , 'PENDING' , format('Applying privileges on child partition: %s.%s' , p_child_schema , p_child_tablename) ); END IF; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types , grantee FROM @extschema@.table_privs WHERE table_schema = p_parent_schema::name AND table_name = p_parent_tablename::name GROUP BY grantee LOOP -- Compare parent & child grants. Don't re-apply if it already exists v_match := false; v_sql := NULL; FOR v_child_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types , grantee FROM @extschema@.table_privs WHERE table_schema = p_child_schema::name AND table_name = p_child_tablename::name GROUP BY grantee LOOP IF v_parent_grant.types = v_child_grant.types AND v_parent_grant.grantee = v_child_grant.grantee THEN v_match := true; END IF; END LOOP; IF v_match = false THEN IF v_parent_grant.grantee = 'PUBLIC' THEN v_sql := 'GRANT %s ON %I.%I TO %s'; ELSE v_sql := 'GRANT %s ON %I.%I TO %I'; END IF; EXECUTE format(v_sql , array_to_string(v_parent_grant.types, ',') , p_child_schema , p_child_tablename , v_parent_grant.grantee); v_sql := NULL; SELECT string_agg(r, ',') INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN IF v_parent_grant.grantee = 'PUBLIC' THEN v_sql := 'REVOKE %s ON %I.%I FROM %s CASCADE'; ELSE v_sql := 'REVOKE %s ON %I.%I FROM %I CASCADE'; END IF; EXECUTE format(v_sql , v_revoke , p_child_schema , p_child_tablename , v_parent_grant.grantee); v_sql := NULL; END IF; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN FOR v_row_revoke IN SELECT role FROM ( SELECT DISTINCT grantee::text AS role FROM @extschema@.table_privs WHERE table_schema = p_child_schema::name AND table_name = p_child_tablename::name EXCEPT SELECT unnest(v_grantees)) x LOOP IF v_row_revoke.role IS NOT NULL THEN IF v_row_revoke.role = 'PUBLIC' THEN v_sql := 'REVOKE ALL ON %I.%I FROM %s'; ELSE v_sql := 'REVOKE ALL ON %I.%I FROM %I'; END IF; EXECUTE format(v_sql , p_child_schema , p_child_tablename , v_row_revoke.role); END IF; END LOOP; END IF; IF v_parent_owner <> v_child_owner THEN EXECUTE format('ALTER TABLE %I.%I OWNER TO %I' , p_child_schema , p_child_tablename , v_parent_owner); END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.update_step(%L, %L, %L)', v_jobmon_schema, v_step_id, 'OK', 'Done'); IF p_job_id IS NULL THEN EXECUTE format('SELECT %I.close_job(%L)', v_jobmon_schema, v_job_id); END IF; END IF; 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 RE-APPLYING PRIVILEGES TO ALL CHILD TABLES OF: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Create the trigger function for the parent table of an id-based partition set */ CREATE FUNCTION create_function_id(p_parent_table text, p_job_id bigint DEFAULT NULL, p_analyze boolean DEFAULT true) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_control text; v_count int; v_current_partition_name text; v_current_partition_id bigint; v_datetime_string text; v_final_partition_id bigint; v_function_name 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_position int; v_job_id bigint; v_jobmon text; v_jobmon_schema text; v_last_partition text; v_max bigint; v_next_partition_id bigint; v_next_partition_name text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_optimize_trigger int; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_premake int; v_prev_partition_id bigint; v_prev_partition_name text; v_row_max_id record; v_run_maint boolean; v_step_id bigint; v_top_parent text := p_parent_table; v_trig_func text; v_trigger_exception_handling boolean; v_trigger_return_null boolean; v_upsert text; BEGIN SELECT partition_interval::bigint , control , premake , optimize_trigger , use_run_maintenance , jobmon , trigger_exception_handling , upsert , trigger_return_null INTO v_partition_interval , v_control , v_premake , v_optimize_trigger , v_run_maint , v_jobmon , v_trigger_exception_handling , v_upsert , v_trigger_return_null FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE FUNCTION: %s', p_parent_table)); ELSE v_job_id = p_job_id; END IF; v_step_id := add_step(v_job_id, format('Creating partition function for table %s', p_parent_table)); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); -- Get the highest level top parent if multi-level partitioned in order to get proper max() value below 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 INTO v_higher_parent_schema, v_higher_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 WHERE p.partition_type = 'id'; IF v_higher_parent_table IS NOT NULL THEN -- initially set in DECLARE v_top_parent := v_higher_parent_schema||'.'||v_higher_parent_table; END IF; END LOOP; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_top_parent, 'DESC') LOOP EXECUTE format('SELECT max(%I) FROM %I.%I', v_control, v_row_max_id.partition_schemaname, v_row_max_id.partition_tablename) INTO v_max; IF v_max IS NOT NULL THEN EXIT; END IF; END LOOP; IF v_max IS NULL THEN v_max := 0; END IF; v_current_partition_id = v_max - (v_max % v_partition_interval); v_next_partition_id := v_current_partition_id + v_partition_interval; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_current_partition_id::text, TRUE); v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_current_partition_id bigint; v_current_partition_name text; v_id_position int; v_last_partition text := %L; v_next_partition_id bigint; v_next_partition_name text; v_partition_created boolean; BEGIN IF TG_OP = ''INSERT'' THEN IF NEW.%I >= %s AND NEW.%I < %s THEN ' , v_parent_schema , v_function_name , v_last_partition , v_control , v_current_partition_id , v_control , v_next_partition_id ); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_current_partition_name::name; IF v_count > 0 THEN v_trig_func := v_trig_func || format(' INSERT INTO %I.%I VALUES (NEW.*) %s; ', v_parent_schema, v_current_partition_name, v_upsert); ELSE v_trig_func := v_trig_func || ' -- Child table for current values does not exist in this partition set, so write to parent RETURN NEW;'; END IF; FOR i IN 1..v_optimize_trigger LOOP v_prev_partition_id := v_current_partition_id - (v_partition_interval * i); v_next_partition_id := v_current_partition_id + (v_partition_interval * i); v_final_partition_id := v_next_partition_id + v_partition_interval; v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, v_prev_partition_id::text, TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, v_next_partition_id::text, TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles optimize_trigger being larger than premake (to go back in time further) and edge case of changing optimize_trigger immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_prev_partition_name::name; IF v_count > 0 THEN -- Only handle previous partitions if they're starting above zero IF v_prev_partition_id >= 0 THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %s AND NEW.%I < %s THEN INSERT INTO %I.%I VALUES (NEW.*) %s; ' , v_control , v_prev_partition_id , v_control , v_prev_partition_id + v_partition_interval , v_parent_schema , v_prev_partition_name , v_upsert ); END IF; END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_next_partition_name::name; IF v_count > 0 THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %s AND NEW.%I < %s THEN INSERT INTO %I.%I VALUES (NEW.*) %s;' , v_control , v_next_partition_id , v_control , v_final_partition_id , v_parent_schema , v_next_partition_name , v_upsert ); END IF; END LOOP; v_trig_func := v_trig_func ||format(' ELSE v_current_partition_id := NEW.%I - (NEW.%I %% %s); v_current_partition_name := @extschema@.check_name_length(%L, v_current_partition_id::text, TRUE); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = %L::name AND tablename = v_current_partition_name::name; IF v_count > 0 THEN EXECUTE format(''INSERT INTO %%I.%%I VALUES($1.*) %s'', %L, v_current_partition_name) USING NEW; ELSE RETURN NEW; END IF; END IF;' , v_control , v_control , v_partition_interval , v_parent_tablename , v_parent_schema , v_upsert , v_parent_schema ); IF v_run_maint IS FALSE THEN v_trig_func := v_trig_func ||format(' v_current_partition_id := NEW.%I - (NEW.%I %% %s); IF (NEW.%I %% %s) > (%s / 2) THEN v_id_position := (length(v_last_partition) - position(''p_'' in reverse(v_last_partition))) + 2; v_next_partition_id := (substring(v_last_partition from v_id_position)::bigint) + %s; WHILE ((v_next_partition_id - v_current_partition_id) / %s) <= %s LOOP v_partition_created := @extschema@.create_partition_id(%L, ARRAY[v_next_partition_id], p_analyze := %L); IF v_partition_created THEN PERFORM @extschema@.create_function_id(%L, p_analyze := %L); PERFORM @extschema@.apply_constraints(%L); END IF; v_next_partition_id := v_next_partition_id + %s; END LOOP; END IF;' , v_control , v_control , v_partition_interval , v_control , v_partition_interval , v_partition_interval , v_partition_interval , v_partition_interval , v_premake , p_parent_table , p_analyze , p_parent_table , p_analyze , p_parent_table , v_partition_interval ); END IF; v_trig_func := v_trig_func ||' END IF;'; IF v_trigger_return_null IS TRUE THEN v_trig_func := v_trig_func ||' RETURN NULL;'; ELSE v_trig_func := v_trig_func ||' RETURN NEW;'; END IF; IF v_trigger_exception_handling THEN v_trig_func := v_trig_func ||' EXCEPTION WHEN OTHERS THEN RAISE WARNING ''pg_partman insert into child table failed, row inserted into parent (%.%). ERROR: %'', TG_TABLE_SCHEMA, TG_TABLE_NAME, COALESCE(SQLERRM, ''unknown''); RETURN NEW;'; END IF; v_trig_func := v_trig_func ||' END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for current id interval: %s to %s', v_current_partition_id, v_final_partition_id-1)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); 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 FUNCTION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''Partition function maintenance for table %s 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 $$; /* * Populate the child table(s) of an id-based partition set with old data from the original parent */ CREATE FUNCTION partition_data_id(p_parent_table text , p_batch_count int DEFAULT 1 , p_batch_interval bigint DEFAULT NULL , p_lock_wait numeric DEFAULT 0 , p_order text DEFAULT 'ASC' , p_analyze boolean DEFAULT true) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_current_partition_name text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_id bigint; v_min_partition_id bigint; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_partition_id bigint[]; v_rowcount bigint; v_sql text; v_start_control bigint; v_total_rows bigint := 0; BEGIN SELECT partition_interval::bigint , control INTO v_partition_interval , v_control FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'id'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; 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'); SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; FOR i IN 1..p_batch_count LOOP IF p_order = 'ASC' THEN EXECUTE format('SELECT min(%I) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; IF v_start_control IS NULL THEN EXIT; END IF; v_min_partition_id = v_start_control - (v_start_control % v_partition_interval); v_partition_id := ARRAY[v_min_partition_id]; -- Check if custom batch interval overflows current partition maximum IF (v_start_control + p_batch_interval) >= (v_min_partition_id + v_partition_interval) THEN v_max_partition_id := v_min_partition_id + v_partition_interval; ELSE v_max_partition_id := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN EXECUTE 'SELECT max('||v_control||') FROM ONLY '||p_parent_table INTO v_start_control; IF v_start_control IS NULL THEN EXIT; END IF; v_min_partition_id = v_start_control - (v_start_control % v_partition_interval); -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_id := v_min_partition_id + v_partition_interval; v_partition_id := ARRAY[v_min_partition_id]; -- Make sure minimum doesn't underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_id THEN v_min_partition_id = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN v_sql := format('SELECT * FROM ONLY %I.%I WHERE %I >= %s AND %I < %s FOR UPDATE NOWAIT' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_id , v_control , v_max_partition_id); EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; PERFORM @extschema@.create_partition_id(p_parent_table, v_partition_id, p_analyze); v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_min_partition_id::text, TRUE); EXECUTE format('WITH partition_data AS ( DELETE FROM ONLY %I.%I WHERE %I >= %s AND %I < %s RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_id , v_control , v_max_partition_id , v_parent_schema , v_current_partition_name); GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; PERFORM @extschema@.create_function_id(p_parent_table, NULL, p_analyze); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_total_rows; END $$; /* * Function to turn a table into the parent of a partition set */ CREATE OR REPLACE FUNCTION 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_use_run_maintenance boolean DEFAULT NULL , p_start_partition text DEFAULT NULL , p_inherit_fk boolean DEFAULT true , p_epoch boolean DEFAULT false , p_upsert text DEFAULT '' , p_trigger_return_null boolean DEFAULT true , p_jobmon boolean DEFAULT true , p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_base_timestamp timestamptz; v_count int := 1; v_datetime_string 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_job_id bigint; v_jobmon_schema text; v_last_partition_created boolean; v_max bigint; v_notnull boolean; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_partition_id bigint; v_parent_partition_timestamp timestamptz; v_parent_schema text; v_parent_tablename text; v_partition_time timestamptz; v_partition_time_array timestamptz[]; v_partition_id_array bigint[]; v_row record; v_run_maint boolean; 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_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); BEGIN IF position('.' in p_parent_table) = 0 THEN RAISE EXCEPTION 'Parent table must be schema qualified'; END IF; IF p_upsert <> '' AND @extschema@.check_version('9.5.0') = 'false' THEN RAISE EXCEPTION 'INSERT ... ON CONFLICT (UPSERT) feature is only supported in PostgreSQL 9.5 and later'; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = 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 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; IF p_type = 'id' AND p_epoch = true THEN RAISE EXCEPTION 'p_epoch can only be used with time-based partitioning'; END IF; IF NOT @extschema@.check_partition_type(p_type) THEN RAISE EXCEPTION '% is not a valid partitioning type', p_type; 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'); IF p_use_run_maintenance IS NOT NULL THEN IF p_use_run_maintenance IS FALSE AND (p_type = 'time' OR p_type = 'time-custom') THEN RAISE EXCEPTION 'p_run_maintenance cannot be set to false for time based partitioning'; END IF; v_run_maint := p_use_run_maintenance; ELSIF p_type = 'time' OR p_type = 'time-custom' THEN v_run_maint := TRUE; ELSIF p_type = 'id' THEN v_run_maint := FALSE; ELSE RAISE EXCEPTION 'use_run_maintenance value cannot be set NULL'; END IF; 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 ) 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_use_run_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 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_use_run_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) 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_use_run_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); END LOOP; IF p_type = 'time' OR p_type = 'time-custom' 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 <> 'time-custom' THEN RAISE EXCEPTION 'Must use a predefined time interval if not using type "time-custom". See documentation.'; 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 , use_run_maintenance , inherit_fk , jobmon , upsert , trigger_return_null) VALUES ( p_parent_table , p_type , v_time_interval , p_epoch , p_control , p_premake , p_constraint_cols , v_datetime_string , v_run_maint , p_inherit_fk , p_jobmon , p_upsert , p_trigger_return_null); 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 -- Currently unknown edge case if code gets here RAISE EXCEPTION 'No child tables created. Unexpected edge case encountered. Please report this error to author with conditions that led to it.'; END IF; END 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 p_type = 'id' THEN v_id_interval := p_interval::bigint; IF v_id_interval < 10 THEN RAISE EXCEPTION 'Interval for serial 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 INTO v_higher_parent_schema, v_higher_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 WHERE p.partition_type = 'id'; IF v_higher_parent_table IS NOT NULL THEN -- 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 , use_run_maintenance , inherit_fk , jobmon , upsert , trigger_return_null) VALUES ( p_parent_table , p_type , v_id_interval , p_control , p_premake , p_constraint_cols , v_run_maint , p_inherit_fk , p_jobmon , p_upsert , p_trigger_return_null); 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 WHERE p.partition_type = 'id'; 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 EXCEPTION 'No child tables created. Unexpected edge case encountered. Please report this error to author with conditions that led to it.'; END IF; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating partition function'); END IF; IF p_type = 'time' OR p_type = 'time-custom' 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 p_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); 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 $$; -- Removed unneeded variable /* * Function to manage pre-creation of the next partitions in a set. * Also manages dropping old partitions if the retention option is set. * If p_parent_table is passed, will only run run_maintenance() on that one table (no matter what the configuration table may have set for it) * Otherwise, will run on all tables in the config table with p_run_maintenance() set to true. * For large partition sets, running analyze can cause maintenance to take longer than expected. Can set p_analyze to false to avoid a forced analyze run. * Be aware that constraint exclusion may not work properly until an analyze on the partition set is run. */ CREATE OR REPLACE FUNCTION run_maintenance(p_parent_table text DEFAULT NULL, p_analyze boolean DEFAULT true, p_jobmon boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_check_subpart int; v_create_count int := 0; v_current_partition text; v_current_partition_id bigint; v_current_partition_timestamp timestamptz; v_drop_count int := 0; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_created boolean; v_last_partition_id bigint; v_last_partition_timestamp timestamptz; v_max_id_parent bigint; v_max_time_parent timestamptz; v_new_search_path text := '@extschema@,pg_temp'; v_next_partition_id bigint; v_next_partition_timestamp timestamptz; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_premade_count int; v_premake_id_max bigint; v_premake_id_min bigint; v_premake_timestamp_min timestamptz; v_premake_timestamp_max timestamptz; v_row record; v_row_max_id record; v_row_max_time record; v_row_sub record; v_step_id bigint; v_step_overflow_id bigint; v_sub_id_max bigint; v_sub_id_max_suffix bigint; v_sub_id_min bigint; v_sub_parent text; v_sub_timestamp_max timestamptz; v_sub_timestamp_max_suffix timestamptz; v_sub_timestamp_min timestamptz; v_tablename text; v_tables_list_sql text; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman maintenance already running.'; RETURN; 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'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN RUN MAINTENANCE'); v_step_id := add_step(v_job_id, 'Running maintenance loop'); END IF; v_row := NULL; -- Ensure it's reset v_tables_list_sql := 'SELECT parent_table , partition_type , partition_interval , control , premake , undo_in_progress , sub_partition_set_full , epoch , infinite_time_partitions FROM @extschema@.part_config WHERE sub_partition_set_full = false'; IF p_parent_table IS NULL THEN v_tables_list_sql := v_tables_list_sql || ' AND use_run_maintenance = true'; ELSE v_tables_list_sql := v_tables_list_sql || format(' AND parent_table = %L', p_parent_table); END IF; FOR v_row IN EXECUTE v_tables_list_sql LOOP CONTINUE WHEN v_row.undo_in_progress; -- Check for consistent data in part_config_sub table. Was unable to get this working properly as either a constraint or trigger. -- Would either delay raising an error until the next write (which I cannot predict) or disallow future edits to update a sub-partition set's configuration. -- This way at least provides a consistent way to check that I know will run. If anyone can get a working constraint/trigger, please help! -- Don't have to worry about this in the serial trigger maintenance since subpartitioning requires run_maintenance(). SELECT sub_parent INTO v_sub_parent FROM @extschema@.part_config_sub WHERE sub_parent = v_row.parent_table; IF v_sub_parent IS NOT NULL THEN SELECT count(*) INTO v_check_subpart FROM @extschema@.check_subpart_sameconfig(v_row.parent_table); IF v_check_subpart > 1 THEN RAISE EXCEPTION 'Inconsistent data in part_config_sub table. Sub-partition tables that are themselves sub-partitions cannot have differing configuration values among their siblings. Run this query: "SELECT * FROM @extschema@.check_subpart_sameconfig(''%'');" This should only return a single row or nothing. If multiple rows are returned, results are all children of the given parent. Update the differing values to be consistent for your desired values.', v_row.sub_parent; END IF; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(v_row.parent_table, '.', 1)::name AND tablename = split_part(v_row.parent_table, '.', 2)::name; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LIMIT 1; IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_last_partition: %', v_row.parent_table, v_last_partition; END IF; IF v_row.partition_type = 'time' OR v_row.partition_type = 'time-custom' THEN SELECT child_start_time INTO v_last_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_row.partition_interval, v_row.parent_table); -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_time IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP IF v_row.epoch = false THEN EXECUTE format('SELECT max(%I)::text FROM %I.%I' , v_row.control , v_row_max_time.partition_schemaname , v_row_max_time.partition_tablename ) INTO v_current_partition_timestamp; ELSE EXECUTE format('SELECT to_timestamp(max(%I))::text FROM %I.%I' , v_row.control , v_row_max_time.partition_schemaname , v_row_max_time.partition_tablename ) INTO v_current_partition_timestamp; END IF; IF v_current_partition_timestamp IS NOT NULL THEN SELECT suffix_timestamp INTO v_current_partition_timestamp FROM @extschema@.show_partition_name(v_row.parent_table, v_current_partition_timestamp::text); EXIT; END IF; END LOOP; -- Check for values in the parent table. If they are there and greater than all child values, use that instead -- This allows maintenance to continue working properly if there is a large gap in data insertion. Data will remain in parent, but new tables will be created IF v_row.epoch = false THEN EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_row.control, v_parent_schema, v_parent_tablename) INTO v_max_time_parent; ELSE EXECUTE format('SELECT to_timestamp(max(%I)) FROM ONLY %I.%I', v_row.control, v_parent_schema, v_parent_tablename) INTO v_max_time_parent; END IF; IF p_debug THEN RAISE NOTICE 'run_maint: v_current_partition_timestamp: %, v_max_time_parent: %', v_current_partition_timestamp, v_max_time_parent; END IF; IF v_max_time_parent > v_current_partition_timestamp THEN SELECT suffix_timestamp INTO v_current_partition_timestamp FROM @extschema@.show_partition_name(v_row.parent_table, v_max_time_parent::text); END IF; IF v_current_partition_timestamp IS NULL THEN -- Partition set is completely empty IF v_row.infinite_time_partitions IS TRUE THEN -- Set it to now so new partitions continue to be created v_current_partition_timestamp = CURRENT_TIMESTAMP; ELSE -- Nothing to do CONTINUE; END IF; END IF; -- If this is a subpartition, determine if the last child table has been made. If so, mark it as full so future maintenance runs can skip it SELECT sub_min::timestamptz, sub_max::timestamptz INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'time'); IF v_sub_timestamp_max IS NOT NULL THEN SELECT suffix_timestamp INTO v_sub_timestamp_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_timestamp_max::text); IF v_sub_timestamp_max_suffix = v_last_partition_timestamp THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; -- Check and see how many premade partitions there are. v_premade_count = round(EXTRACT('epoch' FROM age(v_last_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); v_next_partition_timestamp := v_last_partition_timestamp; IF p_debug THEN RAISE NOTICE 'run_maint before loop: current_partition_timestamp: %, v_premade_count: %, v_sub_timestamp_min: %, v_sub_timestamp_max: %' , v_current_partition_timestamp , v_premade_count , v_sub_timestamp_min , v_sub_timestamp_max; END IF; -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed WHILE (v_premade_count < v_row.premake) LOOP IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_premade_count: %, v_next_partition_timestamp: %', v_row.parent_table, v_premade_count, v_next_partition_timestamp; END IF; IF v_next_partition_timestamp < v_sub_timestamp_min OR v_next_partition_timestamp > v_sub_timestamp_max THEN -- With subpartitioning, no need to run if the timestamp is not in the parent table's range EXIT; END IF; BEGIN v_next_partition_timestamp := v_next_partition_timestamp + v_row.partition_interval::interval; EXCEPTION WHEN datetime_field_overflow THEN v_premade_count := v_row.premake; -- do this so it can exit the premake check loop and continue in the outer for loop IF v_jobmon_schema IS NOT NULL THEN 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', format('Child partition creation skipped for parent table: %s', v_partition_time)); END IF; RAISE WARNING 'Attempted partition time interval is outside PostgreSQL''s supported time range. Child partition creation skipped for parent table %', v_row.parent_table; CONTINUE; END; v_last_partition_created := @extschema@.create_partition_time(v_row.parent_table, ARRAY[v_next_partition_timestamp], p_analyze); IF v_last_partition_created THEN v_create_count := v_create_count + 1; PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); END IF; v_premade_count = round(EXTRACT('epoch' FROM age(v_next_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); END LOOP; ELSIF v_row.partition_type = 'id' THEN -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%I)::text FROM %I.%I' , v_row.control , v_row_max_id.partition_schemaname , v_row_max_id.partition_tablename) INTO v_current_partition_id; IF v_current_partition_id IS NOT NULL THEN SELECT suffix_id INTO v_current_partition_id FROM @extschema@.show_partition_name(v_row.parent_table, v_current_partition_id::text); EXIT; END IF; END LOOP; -- Check for values in the parent table. If they are there and greater than all child values, use that instead -- This allows maintenance to continue working properly if there is a large gap in data insertion. Data will remain in parent, but new tables will be created EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_row.control, v_parent_schema, v_parent_tablename) INTO v_max_id_parent; IF v_max_id_parent > v_current_partition_id THEN SELECT suffix_id INTO v_current_partition_id FROM @extschema@.show_partition_name(v_row.parent_table, v_max_id_parent::text); END IF; IF v_current_partition_id IS NULL THEN -- Partition set is completely empty. Nothing to do CONTINUE; END IF; SELECT child_start_id INTO v_last_partition_id FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_row.partition_interval, v_row.parent_table); -- Determine if this table is a child of a subpartition parent. If so, get limits to see if run_maintenance even needs to run for it. SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'id'); IF v_sub_id_max IS NOT NULL THEN SELECT suffix_id INTO v_sub_id_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_id_max::text); IF v_sub_id_max_suffix = v_last_partition_id THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; v_next_partition_id := v_last_partition_id; v_premade_count := ((v_last_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed. WHILE (v_premade_count < v_row.premake) LOOP IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_premade_count: %, v_next_partition_id: %', v_row.parent_table, v_premade_count, v_next_partition_id; END IF; IF v_next_partition_id < v_sub_id_min OR v_next_partition_id > v_sub_id_max THEN -- With subpartitioning, no need to run if the id is not in the parent table's range EXIT; END IF; v_next_partition_id := v_next_partition_id + v_row.partition_interval::bigint; v_last_partition_created := @extschema@.create_partition_id(v_row.parent_table, ARRAY[v_next_partition_id], p_analyze); IF v_last_partition_created THEN v_create_count := v_create_count + 1; PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); END IF; v_premade_count := ((v_next_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); END LOOP; END IF; -- end main IF check for time or id END LOOP; -- end of creation loop -- Manage dropping old partitions if retention option is set FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND (partition_type = 'time' OR partition_type = 'time-custom') LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); END IF; END IF; IF v_drop_count > 0 THEN PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); END IF; END LOOP; FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND partition_type = 'id' LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); END IF; END IF; IF v_drop_count > 0 THEN PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Partition maintenance finished. %s partitions made. %s partitions dropped.', v_create_count, v_drop_count)); 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'); 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 RUN MAINTENANCE'')', v_jobmon_schema) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Populate the child table(s) of a time-based partition set with old data from the original parent */ CREATE FUNCTION partition_data_time( p_parent_table text , p_batch_count int DEFAULT 1 , p_batch_interval interval DEFAULT NULL , p_lock_wait numeric DEFAULT 0 , p_order text DEFAULT 'ASC' , p_analyze boolean DEFAULT true) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_datetime_string text; v_current_partition_name text; v_epoch boolean; v_last_partition text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_timestamp timestamptz; v_min_partition_timestamp timestamptz; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_partition_suffix text; v_partition_timestamp timestamptz[]; v_quarter text; v_rowcount bigint; v_sql text; v_start_control timestamptz; v_time_position int; v_total_rows bigint := 0; v_type text; v_year text; BEGIN 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'); SELECT partition_type , partition_interval::interval , control , datetime_string , epoch INTO v_type , v_partition_interval , v_control , v_datetime_string , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); IF NOT FOUND THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; FOR i IN 1..p_batch_count LOOP IF v_epoch = false THEN IF p_order = 'ASC' THEN EXECUTE format('SELECT min(%I) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSIF p_order = 'DESC' THEN EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; ELSE IF p_order = 'ASC' THEN EXECUTE format('SELECT to_timestamp(min(%I)) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSIF p_order = 'DESC' THEN EXECUTE format('SELECT to_timestamp(max(%I)) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; END IF; IF v_start_control IS NULL THEN EXIT; END IF; IF v_type = 'time' THEN CASE WHEN v_partition_interval = '15 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '15min'::interval * floor(date_part('minute', v_start_control) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '30min'::interval * floor(date_part('minute', v_start_control) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control); WHEN v_partition_interval = '1 day' THEN v_min_partition_timestamp := date_trunc('day', v_start_control); WHEN v_partition_interval = '1 week' THEN v_min_partition_timestamp := date_trunc('week', v_start_control); WHEN v_partition_interval = '1 month' THEN v_min_partition_timestamp := date_trunc('month', v_start_control); WHEN v_partition_interval = '3 months' THEN v_min_partition_timestamp := date_trunc('quarter', v_start_control); WHEN v_partition_interval = '1 year' THEN v_min_partition_timestamp := date_trunc('year', v_start_control); END CASE; ELSIF v_type = 'time-custom' THEN SELECT child_start_time INTO v_min_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition , v_partition_interval::text , p_parent_table); v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; LOOP IF v_start_control >= v_min_partition_timestamp AND v_start_control < v_max_partition_timestamp THEN EXIT; ELSE BEGIN IF v_start_control > v_max_partition_timestamp THEN -- Keep going forward in time, checking if child partition time interval encompasses the current v_start_control value v_min_partition_timestamp := v_max_partition_timestamp; v_max_partition_timestamp := v_max_partition_timestamp + v_partition_interval; ELSE -- Keep going backwards in time, checking if child partition time interval encompasses the current v_start_control value v_max_partition_timestamp := v_min_partition_timestamp; v_min_partition_timestamp := v_min_partition_timestamp - v_partition_interval; END IF; EXCEPTION WHEN datetime_field_overflow THEN RAISE EXCEPTION 'Attempted partition time interval is outside PostgreSQL''s supported time range. Unable to create partition with interval before timestamp % ', v_min_partition_interval; END; END IF; END LOOP; END IF; v_partition_timestamp := ARRAY[v_min_partition_timestamp]; IF p_order = 'ASC' THEN -- Ensure batch interval given as parameter doesn't cause maximum to overflow the current partition maximum IF (v_start_control + p_batch_interval) >= (v_min_partition_timestamp + v_partition_interval) THEN v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; ELSE v_max_partition_timestamp := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; -- Ensure batch interval given as parameter doesn't cause minimum to underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_timestamp THEN v_min_partition_timestamp = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN IF v_epoch = false THEN v_sql := format('SELECT * FROM ONLY %I.%I WHERE %I >= %L AND %I < %L FOR UPDATE NOWAIT' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_timestamp , v_control , v_max_partition_timestamp); ELSE v_sql := format('SELECT * FROM ONLY %I.%I WHERE to_timestamp(%I) >= %L AND to_timestamp(%I) < %L FOR UPDATE NOWAIT' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_timestamp , v_control , v_max_partition_timestamp); END IF; EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; PERFORM @extschema@.create_partition_time(p_parent_table, v_partition_timestamp, p_analyze); -- This suffix generation code is in create_partition_time() as well v_partition_suffix := to_char(v_min_partition_timestamp, v_datetime_string); v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); IF v_epoch = false THEN v_sql := format('WITH partition_data AS ( DELETE FROM ONLY %I.%I WHERE %I >= %L AND %I < %L RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_timestamp , v_control , v_max_partition_timestamp , v_parent_schema , v_current_partition_name); ELSE v_sql := format('WITH partition_data AS ( DELETE FROM ONLY %I.%I WHERE to_timestamp(%I) >= %L AND to_timestamp(%I) < %L RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_timestamp , v_control , v_max_partition_timestamp , v_parent_schema , v_current_partition_name); END IF; EXECUTE v_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; PERFORM @extschema@.create_function_time(p_parent_table); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_total_rows; END $$; -- Restore dropped object privileges DO $$ DECLARE v_row record; BEGIN FOR v_row IN SELECT statement FROM partman_preserve_privs_temp LOOP IF v_row.statement IS NOT NULL THEN EXECUTE v_row.statement; END IF; END LOOP; END $$; DROP TABLE IF EXISTS partman_preserve_privs_temp; pg_partman-4.6.0/updates/pg_partman--2.6.1--2.6.2.sql000066400000000000000000002162461412761706100214460ustar00rootroot00000000000000-- Refactor of time functions in relation to epoch partitioning to improve maintainability of the code. Much thanks to Michael Rasmussen (Trekoid)! (Github Pull Request #143) -- Some code cleanup for python scripts. Much thanks to Rodolphe QuiƩdeville (rodo)! (Github Pull Request #148) /* * Create the trigger function for the parent table of a time-based partition set */ CREATE OR REPLACE FUNCTION create_function_time(p_parent_table text, p_job_id bigint DEFAULT NULL) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_control text; v_count int; v_current_partition_name text; v_current_partition_timestamp timestamptz; v_datetime_string text; v_epoch boolean; v_final_partition_timestamp timestamptz; v_function_name text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_new_length int; v_next_partition_name text; v_next_partition_timestamp timestamptz; v_parent_schema text; v_parent_tablename text; v_partition_expression text; v_partition_interval interval; v_prev_partition_name text; v_prev_partition_timestamp timestamptz; v_step_id bigint; v_trig_func text; v_optimize_trigger int; v_trigger_exception_handling boolean; v_trigger_return_null boolean; v_type text; v_upsert text; BEGIN SELECT partition_type , partition_interval::interval , epoch , control , optimize_trigger , datetime_string , jobmon , trigger_exception_handling , upsert , trigger_return_null INTO v_type , v_partition_interval , v_epoch , v_control , v_optimize_trigger , v_datetime_string , v_jobmon , v_trigger_exception_handling , v_upsert , v_trigger_return_null FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE FUNCTION: %s', p_parent_table)); ELSE v_job_id = p_job_id; END IF; v_step_id := add_step(v_job_id, format('Creating partition function for table %s', p_parent_table)); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); v_partition_expression := case when v_epoch = true then format('to_timestamp(NEW.%I)', v_control) else format('NEW.%I', v_control) end; IF v_type = 'time' THEN v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_partition_name text; v_partition_timestamp timestamptz; BEGIN IF TG_OP = ''INSERT'' THEN ' , v_parent_schema , v_function_name); CASE WHEN v_partition_interval = '15 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', %s) + ''15min''::interval * floor(date_part(''minute'', %1$s) / 15.0);' , v_partition_expression); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', %s) + ''30min''::interval * floor(date_part(''minute'', %1$s) / 30.0);' , v_partition_expression); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', %s);', v_partition_expression); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 day' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''day'', %s);', v_partition_expression); v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 week' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''week'', %s);', v_partition_expression); v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 month' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''month'', %s);', v_partition_expression); v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_partition_interval = '3 months' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''quarter'', %s);', v_partition_expression); v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 year' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''year'', %s);', v_partition_expression); v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, to_char(v_current_partition_timestamp, v_datetime_string), TRUE); v_next_partition_timestamp := v_current_partition_timestamp + v_partition_interval::interval; v_trig_func := v_trig_func ||format(' IF %s >= %L AND %1$s < %3$L THEN ' , v_partition_expression , v_current_partition_timestamp , v_next_partition_timestamp); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_current_partition_name::name; IF v_count > 0 THEN v_trig_func := v_trig_func || format(' INSERT INTO %I.%I VALUES (NEW.*) %s; ', v_parent_schema, v_current_partition_name, v_upsert); ELSE v_trig_func := v_trig_func || ' -- Child table for current values does not exist in this partition set, so write to parent RETURN NEW;'; END IF; FOR i IN 1..v_optimize_trigger LOOP v_prev_partition_timestamp := v_current_partition_timestamp - (v_partition_interval::interval * i); v_next_partition_timestamp := v_current_partition_timestamp + (v_partition_interval::interval * i); v_final_partition_timestamp := v_next_partition_timestamp + (v_partition_interval::interval); v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, to_char(v_prev_partition_timestamp, v_datetime_string), TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, to_char(v_next_partition_timestamp, v_datetime_string), TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles optimize_trigger being larger than premake (to go back in time further) and edge case of changing optimize_trigger immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_prev_partition_name::name; IF v_count > 0 THEN v_trig_func := v_trig_func ||format(' ELSIF %s >= %L AND %1$s < %3$L THEN INSERT INTO %I.%I VALUES (NEW.*) %s;' , v_partition_expression , v_prev_partition_timestamp , v_prev_partition_timestamp + v_partition_interval::interval , v_parent_schema , v_prev_partition_name , v_upsert); END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_next_partition_name::name; IF v_count > 0 THEN v_trig_func := v_trig_func ||format(' ELSIF %s >= %L AND %1$s < %3$L THEN INSERT INTO %I.%I VALUES (NEW.*) %s;' , v_partition_expression , v_next_partition_timestamp , v_final_partition_timestamp , v_parent_schema , v_next_partition_name , v_upsert); END IF; END LOOP; v_trig_func := v_trig_func||format(' ELSE v_partition_name := @extschema@.check_name_length(%L, to_char(v_partition_timestamp, %L), TRUE); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = %L::name AND tablename = v_partition_name::name; IF v_count > 0 THEN EXECUTE format(''INSERT INTO %%I.%%I VALUES($1.*) %s'', %L, v_partition_name) USING NEW; ELSE RETURN NEW; END IF; END IF;' , v_parent_tablename , v_datetime_string , v_parent_schema , v_upsert , v_parent_schema); v_trig_func := v_trig_func ||' END IF;'; IF v_trigger_return_null IS TRUE THEN v_trig_func := v_trig_func ||' RETURN NULL;'; ELSE v_trig_func := v_trig_func ||' RETURN NEW;'; END IF; IF v_trigger_exception_handling THEN v_trig_func := v_trig_func ||' EXCEPTION WHEN OTHERS THEN RAISE WARNING ''pg_partman insert into child table failed, row inserted into parent (%.%). ERROR: %'', TG_TABLE_SCHEMA, TG_TABLE_NAME, COALESCE(SQLERRM, ''unknown''); RETURN NEW;'; END IF; v_trig_func := v_trig_func ||' END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for current time interval: %s to %s' , v_current_partition_timestamp , v_final_partition_timestamp-'1sec'::interval)); END IF; ELSIF v_type = 'time-custom' THEN v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_child_schemaname text; v_child_table text; v_child_tablename text; v_upsert text; BEGIN ' , v_parent_schema , v_function_name); v_trig_func := v_trig_func || format(' SELECT c.child_table, p.upsert INTO v_child_table, v_upsert FROM @extschema@.custom_time_partitions c JOIN @extschema@.part_config p ON c.parent_table = p.parent_table WHERE c.partition_range @> %s AND c.parent_table = %L;' , v_partition_expression , v_parent_schema||'.'||v_parent_tablename); v_trig_func := v_trig_func || ' SELECT schemaname, tablename INTO v_child_schemaname, v_child_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(v_child_table, ''.'', 1)::name AND tablename = split_part(v_child_table, ''.'', 2)::name; IF v_child_schemaname IS NOT NULL AND v_child_tablename IS NOT NULL THEN EXECUTE format(''INSERT INTO %I.%I VALUES ($1.*) %s'', v_child_schemaname, v_child_tablename, v_upsert) USING NEW; ELSE RETURN NEW; END IF;'; IF v_trigger_return_null IS TRUE THEN v_trig_func := v_trig_func ||' RETURN NULL;'; ELSE v_trig_func := v_trig_func ||' RETURN NEW;'; END IF; IF v_trigger_exception_handling THEN v_trig_func := v_trig_func ||' EXCEPTION WHEN OTHERS THEN RAISE WARNING ''pg_partman insert into child table failed, row inserted into parent (%.%). ERROR: %'', TG_TABLE_SCHEMA, TG_TABLE_NAME, COALESCE(SQLERRM, ''unknown''); RETURN NEW;'; END IF; v_trig_func := v_trig_func ||' END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for custom time table: %s', p_parent_table)); END IF; ELSE RAISE EXCEPTION 'ERROR: Invalid time partitioning type given: %', v_type; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); 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 FUNCTION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''Partition function maintenance for table %s 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 $$; /* * Function to create a child table in a time-based partition set */ CREATE OR REPLACE FUNCTION create_partition_time(p_parent_table text, p_partition_times timestamptz[], p_analyze boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_datetime_string text; v_exists text; v_epoch boolean; v_grantees text[]; v_hasoids boolean; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_partition_created boolean := false; v_partition_name text; v_partition_suffix text; v_parent_tablespace text; v_partition_expression text; v_partition_interval interval; v_partition_timestamp_end timestamptz; v_partition_timestamp_start timestamptz; v_quarter text; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_sub_timestamp_max timestamptz; v_sub_timestamp_min timestamptz; v_trunc_value text; v_time timestamptz; v_type text; v_unlogged char; v_year text; BEGIN SELECT partition_type , control , partition_interval , epoch , inherit_fk , jobmon , datetime_string INTO v_type , v_control , v_partition_interval , v_epoch , v_inherit_fk , v_jobmon , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::timestamptz, sub_max::timestamptz INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'time'); SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; v_partition_expression := case when v_epoch = true then format('to_timestamp(%I)', v_control) else format('%I', v_control) end; IF p_debug THEN RAISE NOTICE 'create_partition_time: v_partition_expression: %', v_partition_expression; END IF; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_timestamp_start := v_time; BEGIN v_partition_timestamp_end := v_time + v_partition_interval; 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_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_time||' skipped'); CONTINUE; END; -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_timestamp_min IS NOT NULL THEN IF v_time < v_sub_timestamp_min OR v_time > v_sub_timestamp_max THEN CONTINUE; END IF; END IF; -- This suffix generation code is in partition_data_time() as well v_partition_suffix := to_char(v_time, v_datetime_string); v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); SELECT tablename INTO v_exists FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_partition_name::name; IF v_exists IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Creating new partition %s.%s with interval from %s to %s' , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end-'1sec'::interval)); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c 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; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c 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; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%s >= %L AND %4$s < %6$L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_partition_expression , v_partition_timestamp_start , v_partition_timestamp_end); IF v_epoch = true THEN EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_int_check' , v_control , EXTRACT('epoch' from v_partition_timestamp_start) , v_control , EXTRACT('epoch' from v_partition_timestamp_end) ); END IF; EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); -- If custom time, set extra config options. IF v_type = 'time-custom' THEN INSERT INTO @extschema@.custom_time_partitions (parent_table, child_table, partition_range) VALUES ( p_parent_table, v_parent_schema||'.'||v_partition_name, tstzrange(v_partition_timestamp_start, v_partition_timestamp_end, '[)') ); END IF; PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_use_run_maintenance , sub_infinite_time_partitions , sub_jobmon , sub_trigger_exception_handling FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Subpartitioning %s.%s', v_parent_schema, v_partition_name)); END IF; 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_use_run_maintenance := %L , p_inherit_fk := %L , p_epoch := %L , p_jobmon := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_use_run_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions , trigger_exception_handling = v_row.sub_trigger_exception_handling WHERE parent_table = v_parent_schema||'.'||v_partition_name; END LOOP; -- end sub partitioning LOOP -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table, p_job_id := v_job_id, p_debug := p_debug); v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s. Attempted intervals: %s', p_parent_table, p_partition_times)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; 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_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Populate the child table(s) of a time-based partition set with old data from the original parent */ CREATE OR REPLACE FUNCTION partition_data_time( p_parent_table text , p_batch_count int DEFAULT 1 , p_batch_interval interval DEFAULT NULL , p_lock_wait numeric DEFAULT 0 , p_order text DEFAULT 'ASC' , p_analyze boolean DEFAULT true) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_datetime_string text; v_current_partition_name text; v_epoch boolean; v_last_partition text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_timestamp timestamptz; v_min_partition_timestamp timestamptz; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_expression text; v_partition_interval interval; v_partition_suffix text; v_partition_timestamp timestamptz[]; v_quarter text; v_rowcount bigint; v_start_control timestamptz; v_time_position int; v_total_rows bigint := 0; v_type text; v_year text; BEGIN 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'); SELECT partition_type , partition_interval::interval , control , datetime_string , epoch INTO v_type , v_partition_interval , v_control , v_datetime_string , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); IF NOT FOUND THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; v_partition_expression := case when v_epoch = true then format('to_timestamp(%I)', v_control) else format('%I', v_control) end; FOR i IN 1..p_batch_count LOOP IF p_order = 'ASC' THEN EXECUTE format('SELECT min(%s) FROM ONLY %I.%I', v_partition_expression, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSIF p_order = 'DESC' THEN EXECUTE format('SELECT max(%s) FROM ONLY %I.%I', v_partition_expression, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; IF v_start_control IS NULL THEN EXIT; END IF; IF v_type = 'time' THEN CASE WHEN v_partition_interval = '15 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '15min'::interval * floor(date_part('minute', v_start_control) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '30min'::interval * floor(date_part('minute', v_start_control) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control); WHEN v_partition_interval = '1 day' THEN v_min_partition_timestamp := date_trunc('day', v_start_control); WHEN v_partition_interval = '1 week' THEN v_min_partition_timestamp := date_trunc('week', v_start_control); WHEN v_partition_interval = '1 month' THEN v_min_partition_timestamp := date_trunc('month', v_start_control); WHEN v_partition_interval = '3 months' THEN v_min_partition_timestamp := date_trunc('quarter', v_start_control); WHEN v_partition_interval = '1 year' THEN v_min_partition_timestamp := date_trunc('year', v_start_control); END CASE; ELSIF v_type = 'time-custom' THEN SELECT child_start_time INTO v_min_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition , v_partition_interval::text , p_parent_table); v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; LOOP IF v_start_control >= v_min_partition_timestamp AND v_start_control < v_max_partition_timestamp THEN EXIT; ELSE BEGIN IF v_start_control > v_max_partition_timestamp THEN -- Keep going forward in time, checking if child partition time interval encompasses the current v_start_control value v_min_partition_timestamp := v_max_partition_timestamp; v_max_partition_timestamp := v_max_partition_timestamp + v_partition_interval; ELSE -- Keep going backwards in time, checking if child partition time interval encompasses the current v_start_control value v_max_partition_timestamp := v_min_partition_timestamp; v_min_partition_timestamp := v_min_partition_timestamp - v_partition_interval; END IF; EXCEPTION WHEN datetime_field_overflow THEN RAISE EXCEPTION 'Attempted partition time interval is outside PostgreSQL''s supported time range. Unable to create partition with interval before timestamp % ', v_min_partition_interval; END; END IF; END LOOP; END IF; v_partition_timestamp := ARRAY[v_min_partition_timestamp]; IF p_order = 'ASC' THEN -- Ensure batch interval given as parameter doesn't cause maximum to overflow the current partition maximum IF (v_start_control + p_batch_interval) >= (v_min_partition_timestamp + v_partition_interval) THEN v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; ELSE v_max_partition_timestamp := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; -- Ensure batch interval given as parameter doesn't cause minimum to underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_timestamp THEN v_min_partition_timestamp = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM ONLY %I.%I WHERE %s >= %L AND %3$s < %5$L FOR UPDATE NOWAIT' , v_parent_schema , v_parent_tablename , v_partition_expression , v_min_partition_timestamp , v_max_partition_timestamp); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; PERFORM @extschema@.create_partition_time(p_parent_table, v_partition_timestamp, p_analyze); -- This suffix generation code is in create_partition_time() as well v_partition_suffix := to_char(v_min_partition_timestamp, v_datetime_string); v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); EXECUTE format('WITH partition_data AS ( DELETE FROM ONLY %I.%I WHERE %s >= %L AND %3$s < %5$L RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_parent_schema , v_parent_tablename , v_partition_expression , v_min_partition_timestamp , v_max_partition_timestamp , v_parent_schema , v_current_partition_name); GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; PERFORM @extschema@.create_function_time(p_parent_table); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_total_rows; END $$; /* * Function to manage pre-creation of the next partitions in a set. * Also manages dropping old partitions if the retention option is set. * If p_parent_table is passed, will only run run_maintenance() on that one table (no matter what the configuration table may have set for it) * Otherwise, will run on all tables in the config table with p_run_maintenance() set to true. * For large partition sets, running analyze can cause maintenance to take longer than expected. Can set p_analyze to false to avoid a forced analyze run. * Be aware that constraint exclusion may not work properly until an analyze on the partition set is run. */ CREATE OR REPLACE FUNCTION run_maintenance(p_parent_table text DEFAULT NULL, p_analyze boolean DEFAULT true, p_jobmon boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_check_subpart int; v_create_count int := 0; v_current_partition text; v_current_partition_id bigint; v_current_partition_timestamp timestamptz; v_drop_count int := 0; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_created boolean; v_last_partition_id bigint; v_last_partition_timestamp timestamptz; v_max_id_parent bigint; v_max_time_parent timestamptz; v_new_search_path text := '@extschema@,pg_temp'; v_next_partition_id bigint; v_next_partition_timestamp timestamptz; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_expression text; v_premade_count int; v_premake_id_max bigint; v_premake_id_min bigint; v_premake_timestamp_min timestamptz; v_premake_timestamp_max timestamptz; v_row record; v_row_max_id record; v_row_max_time record; v_row_sub record; v_step_id bigint; v_step_overflow_id bigint; v_sub_id_max bigint; v_sub_id_max_suffix bigint; v_sub_id_min bigint; v_sub_parent text; v_sub_timestamp_max timestamptz; v_sub_timestamp_max_suffix timestamptz; v_sub_timestamp_min timestamptz; v_tablename text; v_tables_list_sql text; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman maintenance already running.'; RETURN; 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'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN RUN MAINTENANCE'); v_step_id := add_step(v_job_id, 'Running maintenance loop'); END IF; v_row := NULL; -- Ensure it's reset v_tables_list_sql := 'SELECT parent_table , partition_type , partition_interval , control , premake , undo_in_progress , sub_partition_set_full , epoch , infinite_time_partitions FROM @extschema@.part_config WHERE sub_partition_set_full = false'; IF p_parent_table IS NULL THEN v_tables_list_sql := v_tables_list_sql || ' AND use_run_maintenance = true'; ELSE v_tables_list_sql := v_tables_list_sql || format(' AND parent_table = %L', p_parent_table); END IF; FOR v_row IN EXECUTE v_tables_list_sql LOOP CONTINUE WHEN v_row.undo_in_progress; -- Check for consistent data in part_config_sub table. Was unable to get this working properly as either a constraint or trigger. -- Would either delay raising an error until the next write (which I cannot predict) or disallow future edits to update a sub-partition set's configuration. -- This way at least provides a consistent way to check that I know will run. If anyone can get a working constraint/trigger, please help! -- Don't have to worry about this in the serial trigger maintenance since subpartitioning requires run_maintenance(). SELECT sub_parent INTO v_sub_parent FROM @extschema@.part_config_sub WHERE sub_parent = v_row.parent_table; IF v_sub_parent IS NOT NULL THEN SELECT count(*) INTO v_check_subpart FROM @extschema@.check_subpart_sameconfig(v_row.parent_table); IF v_check_subpart > 1 THEN RAISE EXCEPTION 'Inconsistent data in part_config_sub table. Sub-partition tables that are themselves sub-partitions cannot have differing configuration values among their siblings. Run this query: "SELECT * FROM @extschema@.check_subpart_sameconfig(''%'');" This should only return a single row or nothing. If multiple rows are returned, results are all children of the given parent. Update the differing values to be consistent for your desired values.', v_row.sub_parent; END IF; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(v_row.parent_table, '.', 1)::name AND tablename = split_part(v_row.parent_table, '.', 2)::name; v_partition_expression := case when v_row.epoch = true then format('to_timestamp(%I)', v_row.control) else format('%I', v_row.control) end; IF p_debug THEN RAISE NOTICE 'run_maint: v_partition_expression: %', v_partition_expression; END IF; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LIMIT 1; IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_last_partition: %', v_row.parent_table, v_last_partition; END IF; IF v_row.partition_type = 'time' OR v_row.partition_type = 'time-custom' THEN SELECT child_start_time INTO v_last_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_row.partition_interval, v_row.parent_table); -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_time IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%s)::text FROM %I.%I' , v_partition_expression , v_row_max_time.partition_schemaname , v_row_max_time.partition_tablename ) INTO v_current_partition_timestamp; IF v_current_partition_timestamp IS NOT NULL THEN SELECT suffix_timestamp INTO v_current_partition_timestamp FROM @extschema@.show_partition_name(v_row.parent_table, v_current_partition_timestamp::text); EXIT; END IF; END LOOP; -- Check for values in the parent table. If they are there and greater than all child values, use that instead -- This allows maintenance to continue working properly if there is a large gap in data insertion. Data will remain in parent, but new tables will be created EXECUTE format('SELECT max(%s) FROM ONLY %I.%I', v_partition_expression, v_parent_schema, v_parent_tablename) INTO v_max_time_parent; IF p_debug THEN RAISE NOTICE 'run_maint: v_current_partition_timestamp: %, v_max_time_parent: %', v_current_partition_timestamp, v_max_time_parent; END IF; IF v_max_time_parent > v_current_partition_timestamp THEN SELECT suffix_timestamp INTO v_current_partition_timestamp FROM @extschema@.show_partition_name(v_row.parent_table, v_max_time_parent::text); END IF; IF v_current_partition_timestamp IS NULL THEN -- Partition set is completely empty IF v_row.infinite_time_partitions IS TRUE THEN -- Set it to now so new partitions continue to be created v_current_partition_timestamp = CURRENT_TIMESTAMP; ELSE -- Nothing to do CONTINUE; END IF; END IF; -- If this is a subpartition, determine if the last child table has been made. If so, mark it as full so future maintenance runs can skip it SELECT sub_min::timestamptz, sub_max::timestamptz INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'time'); IF v_sub_timestamp_max IS NOT NULL THEN SELECT suffix_timestamp INTO v_sub_timestamp_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_timestamp_max::text); IF v_sub_timestamp_max_suffix = v_last_partition_timestamp THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; -- Check and see how many premade partitions there are. v_premade_count = round(EXTRACT('epoch' FROM age(v_last_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); v_next_partition_timestamp := v_last_partition_timestamp; IF p_debug THEN RAISE NOTICE 'run_maint before loop: current_partition_timestamp: %, v_premade_count: %, v_sub_timestamp_min: %, v_sub_timestamp_max: %' , v_current_partition_timestamp , v_premade_count , v_sub_timestamp_min , v_sub_timestamp_max; END IF; -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed WHILE (v_premade_count < v_row.premake) LOOP IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_premade_count: %, v_next_partition_timestamp: %', v_row.parent_table, v_premade_count, v_next_partition_timestamp; END IF; IF v_next_partition_timestamp < v_sub_timestamp_min OR v_next_partition_timestamp > v_sub_timestamp_max THEN -- With subpartitioning, no need to run if the timestamp is not in the parent table's range EXIT; END IF; BEGIN v_next_partition_timestamp := v_next_partition_timestamp + v_row.partition_interval::interval; EXCEPTION WHEN datetime_field_overflow THEN v_premade_count := v_row.premake; -- do this so it can exit the premake check loop and continue in the outer for loop IF v_jobmon_schema IS NOT NULL THEN 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', format('Child partition creation skipped for parent table: %s', v_partition_time)); END IF; RAISE WARNING 'Attempted partition time interval is outside PostgreSQL''s supported time range. Child partition creation skipped for parent table %', v_row.parent_table; CONTINUE; END; v_last_partition_created := @extschema@.create_partition_time(v_row.parent_table, ARRAY[v_next_partition_timestamp], p_analyze); IF v_last_partition_created THEN v_create_count := v_create_count + 1; PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); END IF; v_premade_count = round(EXTRACT('epoch' FROM age(v_next_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); END LOOP; ELSIF v_row.partition_type = 'id' THEN -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%I)::text FROM %I.%I' , v_row.control , v_row_max_id.partition_schemaname , v_row_max_id.partition_tablename) INTO v_current_partition_id; IF v_current_partition_id IS NOT NULL THEN SELECT suffix_id INTO v_current_partition_id FROM @extschema@.show_partition_name(v_row.parent_table, v_current_partition_id::text); EXIT; END IF; END LOOP; -- Check for values in the parent table. If they are there and greater than all child values, use that instead -- This allows maintenance to continue working properly if there is a large gap in data insertion. Data will remain in parent, but new tables will be created EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_row.control, v_parent_schema, v_parent_tablename) INTO v_max_id_parent; IF v_max_id_parent > v_current_partition_id THEN SELECT suffix_id INTO v_current_partition_id FROM @extschema@.show_partition_name(v_row.parent_table, v_max_id_parent::text); END IF; IF v_current_partition_id IS NULL THEN -- Partition set is completely empty. Nothing to do CONTINUE; END IF; SELECT child_start_id INTO v_last_partition_id FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_row.partition_interval, v_row.parent_table); -- Determine if this table is a child of a subpartition parent. If so, get limits to see if run_maintenance even needs to run for it. SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'id'); IF v_sub_id_max IS NOT NULL THEN SELECT suffix_id INTO v_sub_id_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_id_max::text); IF v_sub_id_max_suffix = v_last_partition_id THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; v_next_partition_id := v_last_partition_id; v_premade_count := ((v_last_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed. WHILE (v_premade_count < v_row.premake) LOOP IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_premade_count: %, v_next_partition_id: %', v_row.parent_table, v_premade_count, v_next_partition_id; END IF; IF v_next_partition_id < v_sub_id_min OR v_next_partition_id > v_sub_id_max THEN -- With subpartitioning, no need to run if the id is not in the parent table's range EXIT; END IF; v_next_partition_id := v_next_partition_id + v_row.partition_interval::bigint; v_last_partition_created := @extschema@.create_partition_id(v_row.parent_table, ARRAY[v_next_partition_id], p_analyze); IF v_last_partition_created THEN v_create_count := v_create_count + 1; PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); END IF; v_premade_count := ((v_next_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); END LOOP; END IF; -- end main IF check for time or id END LOOP; -- end of creation loop -- Manage dropping old partitions if retention option is set FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND (partition_type = 'time' OR partition_type = 'time-custom') LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); END IF; END IF; IF v_drop_count > 0 THEN PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); END IF; END LOOP; FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND partition_type = 'id' LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); END IF; END IF; IF v_drop_count > 0 THEN PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Partition maintenance finished. %s partitions made. %s partitions dropped.', v_create_count, v_drop_count)); 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'); 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 RUN MAINTENANCE'')', v_jobmon_schema) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to undo time-based partitioning created by this extension */ CREATE OR REPLACE FUNCTION undo_partition_time(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval interval DEFAULT NULL, p_keep_table boolean DEFAULT true, p_lock_wait numeric DEFAULT 0) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_batch_loop_count int := 0; v_child_loop_total bigint := 0; v_child_min timestamptz; v_child_table text; v_control text; v_epoch boolean; v_function_name text; v_inner_loop_count int; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_expression text; v_partition_interval interval; v_row record; v_rowcount bigint; v_step_id bigint; v_sub_count int; v_total bigint := 0; v_trig_name text; v_type text; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition_time')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition_time already running.'; RETURN 0; END IF; SELECT partition_type , partition_interval::interval , control , jobmon , epoch INTO v_type , v_partition_interval , v_control , v_jobmon , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Check if any child tables are themselves partitioned or part of an inheritance tree. Prevent undo at this level if so. -- Need to either lock child tables at all levels or handle the proper removal of triggers on all child tables first -- before multi-level undo can be performed safely. FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table) LOOP SELECT count(*) INTO v_sub_count FROM pg_catalog.pg_inherits i JOIN pg_catalog.pg_class c ON i.inhparent = c.oid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_row.partition_tablename::name AND n.nspname = v_row.partition_schemaname::name; IF v_sub_count > 0 THEN RAISE EXCEPTION 'Child table for this parent has child table(s) itself (%). Run undo partitioning on this table or remove inheritance first to ensure all data is properly moved to parent', v_row.partition_schemaname||'.'||v_row.partition_tablename; END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN UNDO PARTITIONING: %s', p_parent_table)); v_step_id := add_step(v_job_id, format('Undoing partitioning for table %s', p_parent_table)); END IF; IF p_batch_interval IS NULL THEN p_batch_interval := v_partition_interval; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; v_partition_expression := case when v_epoch = true then format('to_timestamp(%I)', v_control) else format('%I', v_control) end; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables. v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t JOIN pg_catalog.pg_class c ON t.tgrelid = c.oid WHERE tgname = v_trig_name::name AND c.relname = v_parent_tablename::name; SELECT proname INTO v_function_name FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid WHERE n.nspname = v_parent_schema::name AND proname = v_function_name::name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_parent_tablename); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; RETURN -1; END IF; END IF; -- END p_lock_wait IF EXECUTE format('DROP TRIGGER IF EXISTS %I ON %I.%I', v_trig_name, v_parent_schema, v_parent_tablename); END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later IF v_function_name IS NOT NULL THEN EXECUTE format('DROP FUNCTION IF EXISTS %I.%I()', v_parent_schema, v_function_name); END IF; IF v_jobmon_schema IS NOT NULL THEN IF (v_trig_name IS NOT NULL OR v_function_name IS NOT NULL) THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); ELSE PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process.'); END IF; END IF; <> LOOP -- Get ordered list of child table in set. Store in variable one at a time per loop until none are left or batch count is reached. -- This easily allows it to loop over same child table until empty or move onto next child table after it's dropped SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC') LIMIT 1; EXIT outer_child_loop WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Removing child partition: %s.%s', v_parent_schema, v_child_table)); END IF; EXECUTE format('SELECT min(%s) FROM %I.%I', v_partition_expression, v_parent_schema, v_child_table) INTO v_child_min; IF v_child_min IS NULL THEN -- No rows left in this child table. Remove from partition set. -- lockwait timeout for table drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_child_table); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; RETURN -1; END IF; END IF; -- END p_lock_wait IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_parent_schema , v_child_table , v_parent_schema , v_parent_tablename); IF p_keep_table = false THEN EXECUTE format('DROP TABLE %I.%I', v_parent_schema, v_child_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table DROPPED. Moved %s rows to parent', v_child_loop_total)); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table UNINHERITED, not DROPPED. Moved %s rows to parent', v_child_loop_total)); END IF; END IF; IF v_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_parent_schema||'.'||v_child_table; END IF; v_undo_count := v_undo_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached CONTINUE outer_child_loop; -- skip data moving steps below END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM %I.%I WHERE %I <= %L FOR UPDATE NOWAIT' , v_parent_schema , v_child_table , v_control , v_child_min + (p_batch_interval * v_inner_loop_count)); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; RETURN -1; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval EXECUTE format('WITH move_data AS ( DELETE FROM %I.%I WHERE %s <= %L RETURNING *) INSERT INTO %I.%I SELECT * FROM move_data' , v_parent_schema , v_child_table , v_partition_expression , v_child_min + (p_batch_interval * v_inner_loop_count) , v_parent_schema , v_parent_tablename); GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Moved %s rows to parent.', v_child_loop_total)); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; -- Check again if table is empty and go to outer loop again to drop it if so EXECUTE format('SELECT min(%s) FROM %I.%I', v_partition_expression, v_parent_schema, v_child_table) INTO v_child_min; CONTINUE outer_child_loop WHEN v_child_min IS NULL; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC') LIMIT 1; IF v_child_table IS NULL THEN DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) to the parent. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', format('Copied %s row(s) to the parent. Removed %s partitions.', v_total, v_undo_count)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_total; 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 UNDO PARTITIONING: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; pg_partman-4.6.0/updates/pg_partman--2.6.2--2.6.3.sql000066400000000000000000003300161412761706100214400ustar00rootroot00000000000000-- IMPORTANT NOTE: Installing the new C library requires a restart of the database cluster. Even if you are not running 9.6, it is recommended to restart after this update to ensure the latest library file is loaded into the database if you have compiled the C library. -- Fixed pg_partman background worker to work with PostgreSQL 9.6. If configured for multiple databases in a single cluster, the background worker processes will no longer start up and run in parallel. Unfortunately, changes in background worker code have made that harder to do at this time. Note it never ran in parallel on a single database before, it would only do that for multiple databases. (Github Issue #141). -- Allow option for millisecond epoch partitioning. (Github Issue #150, Also requested back in #75) -- The "epoch" parameter to any function and the column in the part_config table has changed from boolean to text and only accepts the values: "seconds", "milliseconds", "none". -- The default is "none". -- Any current epoch partition sets will automatically have this value set to "seconds". -- Update for 2.6.4 -- This update has been modified since the release of 2.6.4 to allow upgrades in a specific edge case. See the CHANGELOG or the notes at the top of the 2.6.4 update file for why this happened and how to deal with possible update issues. CREATE TEMP TABLE partman_preserve_privs_temp (statement text); INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.check_subpart_sameconfig(p_parent_table text) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'check_subpart_sameconfig'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_parent(text, text, text, text, text[], int, boolean, text, boolean, text, text, boolean, boolean, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_parent'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_sub_parent(text, text, text, text, text[], int, text, boolean, text, text, boolean, boolean, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_sub_parent'; DROP FUNCTION @extschema@.check_subpart_sameconfig(p_parent_table text); DROP FUNCTION @extschema@.create_parent(text, text, text, text, text[], int, boolean, text, boolean, boolean, text, boolean, boolean, boolean); DROP FUNCTION @extschema@.create_sub_parent(text, text, text, text, text[], int, text, boolean, boolean, text, boolean, boolean, boolean); ALTER TABLE @extschema@.part_config ALTER COLUMN epoch TYPE text; UPDATE @extschema@.part_config SET epoch = 'seconds' WHERE epoch = 'true'; UPDATE @extschema@.part_config SET epoch = 'none' WHERE epoch = 'false'; --ALTER TABLE @extschema@.part_config ALTER COLUMN epoch SET DEFAULT 'none'; ALTER TABLE @extschema@.part_config_sub ALTER COLUMN sub_epoch TYPE text; UPDATE @extschema@.part_config_sub SET sub_epoch = 'seconds' WHERE sub_epoch = 'true'; UPDATE @extschema@.part_config_sub SET sub_epoch = 'none' WHERE sub_epoch = 'false'; --ALTER TABLE @extschema@.part_config_sub ALTER COLUMN sub_epoch SET DEFAULT 'none'; /* * Check function for config table epoch types */ CREATE FUNCTION @extschema@.check_epoch_type (p_type text) RETURNS boolean LANGUAGE plpgsql IMMUTABLE SECURITY DEFINER AS $$ DECLARE v_result boolean; BEGIN SELECT p_type IN ('none', 'seconds', 'milliseconds') INTO v_result; RETURN v_result; END $$; --ALTER TABLE @extschema@.part_config --ADD CONSTRAINT part_config_epoch_check --CHECK (@extschema@.check_epoch_type(epoch)); --ALTER TABLE @extschema@.part_config_sub --ADD CONSTRAINT part_config_sub_epoch_check --CHECK (@extschema@.check_epoch_type(sub_epoch)); /* * Check for consistent data in part_config_sub table. Was unable to get this working properly as either a constraint or trigger. * Would either delay raising an error until the next write (which I cannot predict) or disallow future edits to update a sub-partition set's configuration. * This is called by run_maintainance() and at least provides a consistent way to check that I know will run. * If anyone can get a working constraint/trigger, please help! */ CREATE FUNCTION @extschema@.check_subpart_sameconfig(p_parent_table text) RETURNS TABLE (sub_partition_type text , sub_control text , sub_partition_interval text , sub_constraint_cols text[] , sub_premake int , sub_inherit_fk boolean , sub_retention text , sub_retention_schema text , sub_retention_keep_table boolean , sub_retention_keep_index boolean , sub_use_run_maintenance boolean , sub_epoch text , sub_optimize_trigger int , sub_optimize_constraint int , sub_infinite_time_partitions boolean , sub_jobmon boolean , sub_trigger_exception_handling boolean , sub_upsert text , sub_trigger_return_null boolean) LANGUAGE sql STABLE SECURITY DEFINER SET search_path = @extschema@,pg_temp AS $$ WITH parent_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE n1.nspname = split_part(p_parent_table, '.', 1)::name AND c1.relname = split_part(p_parent_table, '.', 2)::name ) , child_tables AS ( SELECT n.nspname||'.'||c.relname AS tablename FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN parent_info pi ON h.inhparent = pi.oid ) -- Column order here must match the RETURNS TABLE definition SELECT DISTINCT a.sub_partition_type , a.sub_control , a.sub_partition_interval , a.sub_constraint_cols , a.sub_premake , a.sub_inherit_fk , a.sub_retention , a.sub_retention_schema , a.sub_retention_keep_table , a.sub_retention_keep_index , a.sub_use_run_maintenance , a.sub_epoch , a.sub_optimize_trigger , a.sub_optimize_constraint , a.sub_infinite_time_partitions , a.sub_jobmon , a.sub_trigger_exception_handling , a.sub_upsert , a.sub_trigger_return_null FROM @extschema@.part_config_sub a JOIN child_tables b on a.sub_parent = b.tablename; $$; /* * Create the trigger function for the parent table of a time-based partition set */ CREATE OR REPLACE FUNCTION create_function_time(p_parent_table text, p_job_id bigint DEFAULT NULL) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_control text; v_count int; v_current_partition_name text; v_current_partition_timestamp timestamptz; v_datetime_string text; v_epoch text; v_final_partition_timestamp timestamptz; v_function_name text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_new_length int; v_next_partition_name text; v_next_partition_timestamp timestamptz; v_parent_schema text; v_parent_tablename text; v_partition_expression text; v_partition_interval interval; v_prev_partition_name text; v_prev_partition_timestamp timestamptz; v_step_id bigint; v_trig_func text; v_optimize_trigger int; v_trigger_exception_handling boolean; v_trigger_return_null boolean; v_type text; v_upsert text; BEGIN SELECT partition_type , partition_interval::interval , epoch , control , optimize_trigger , datetime_string , jobmon , trigger_exception_handling , upsert , trigger_return_null INTO v_type , v_partition_interval , v_epoch , v_control , v_optimize_trigger , v_datetime_string , v_jobmon , v_trigger_exception_handling , v_upsert , v_trigger_return_null FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE FUNCTION: %s', p_parent_table)); ELSE v_job_id = p_job_id; END IF; v_step_id := add_step(v_job_id, format('Creating partition function for table %s', p_parent_table)); END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(NEW.%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((NEW.%I/1000)::float)', v_control) ELSE format('NEW.%I', v_control) END; IF v_type = 'time' THEN v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_partition_name text; v_partition_timestamp timestamptz; BEGIN IF TG_OP = ''INSERT'' THEN ' , v_parent_schema , v_function_name); CASE WHEN v_partition_interval = '15 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', %s) + ''15min''::interval * floor(date_part(''minute'', %1$s) / 15.0);' , v_partition_expression); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', %s) + ''30min''::interval * floor(date_part(''minute'', %1$s) / 30.0);' , v_partition_expression); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', %s);', v_partition_expression); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 day' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''day'', %s);', v_partition_expression); v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 week' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''week'', %s);', v_partition_expression); v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 month' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''month'', %s);', v_partition_expression); v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_partition_interval = '3 months' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''quarter'', %s);', v_partition_expression); v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 year' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''year'', %s);', v_partition_expression); v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, to_char(v_current_partition_timestamp, v_datetime_string), TRUE); v_next_partition_timestamp := v_current_partition_timestamp + v_partition_interval::interval; v_trig_func := v_trig_func ||format(' IF %s >= %L AND %1$s < %3$L THEN ' , v_partition_expression , v_current_partition_timestamp , v_next_partition_timestamp); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_current_partition_name::name; IF v_count > 0 THEN v_trig_func := v_trig_func || format(' INSERT INTO %I.%I VALUES (NEW.*) %s; ', v_parent_schema, v_current_partition_name, v_upsert); ELSE v_trig_func := v_trig_func || ' -- Child table for current values does not exist in this partition set, so write to parent RETURN NEW;'; END IF; FOR i IN 1..v_optimize_trigger LOOP v_prev_partition_timestamp := v_current_partition_timestamp - (v_partition_interval::interval * i); v_next_partition_timestamp := v_current_partition_timestamp + (v_partition_interval::interval * i); v_final_partition_timestamp := v_next_partition_timestamp + (v_partition_interval::interval); v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, to_char(v_prev_partition_timestamp, v_datetime_string), TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, to_char(v_next_partition_timestamp, v_datetime_string), TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles optimize_trigger being larger than premake (to go back in time further) and edge case of changing optimize_trigger immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_prev_partition_name::name; IF v_count > 0 THEN v_trig_func := v_trig_func ||format(' ELSIF %s >= %L AND %1$s < %3$L THEN INSERT INTO %I.%I VALUES (NEW.*) %s;' , v_partition_expression , v_prev_partition_timestamp , v_prev_partition_timestamp + v_partition_interval::interval , v_parent_schema , v_prev_partition_name , v_upsert); END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_next_partition_name::name; IF v_count > 0 THEN v_trig_func := v_trig_func ||format(' ELSIF %s >= %L AND %1$s < %3$L THEN INSERT INTO %I.%I VALUES (NEW.*) %s;' , v_partition_expression , v_next_partition_timestamp , v_final_partition_timestamp , v_parent_schema , v_next_partition_name , v_upsert); END IF; END LOOP; v_trig_func := v_trig_func||format(' ELSE v_partition_name := @extschema@.check_name_length(%L, to_char(v_partition_timestamp, %L), TRUE); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = %L::name AND tablename = v_partition_name::name; IF v_count > 0 THEN EXECUTE format(''INSERT INTO %%I.%%I VALUES($1.*) %s'', %L, v_partition_name) USING NEW; ELSE RETURN NEW; END IF; END IF;' , v_parent_tablename , v_datetime_string , v_parent_schema , v_upsert , v_parent_schema); v_trig_func := v_trig_func ||' END IF;'; IF v_trigger_return_null IS TRUE THEN v_trig_func := v_trig_func ||' RETURN NULL;'; ELSE v_trig_func := v_trig_func ||' RETURN NEW;'; END IF; IF v_trigger_exception_handling THEN v_trig_func := v_trig_func ||' EXCEPTION WHEN OTHERS THEN RAISE WARNING ''pg_partman insert into child table failed, row inserted into parent (%.%). ERROR: %'', TG_TABLE_SCHEMA, TG_TABLE_NAME, COALESCE(SQLERRM, ''unknown''); RETURN NEW;'; END IF; v_trig_func := v_trig_func ||' END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for current time interval: %s to %s' , v_current_partition_timestamp , v_final_partition_timestamp-'1sec'::interval)); END IF; ELSIF v_type = 'time-custom' THEN v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_child_schemaname text; v_child_table text; v_child_tablename text; v_upsert text; BEGIN ' , v_parent_schema , v_function_name); v_trig_func := v_trig_func || format(' SELECT c.child_table, p.upsert INTO v_child_table, v_upsert FROM @extschema@.custom_time_partitions c JOIN @extschema@.part_config p ON c.parent_table = p.parent_table WHERE c.partition_range @> %s AND c.parent_table = %L;' , v_partition_expression , v_parent_schema||'.'||v_parent_tablename); v_trig_func := v_trig_func || ' SELECT schemaname, tablename INTO v_child_schemaname, v_child_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(v_child_table, ''.'', 1)::name AND tablename = split_part(v_child_table, ''.'', 2)::name; IF v_child_schemaname IS NOT NULL AND v_child_tablename IS NOT NULL THEN EXECUTE format(''INSERT INTO %I.%I VALUES ($1.*) %s'', v_child_schemaname, v_child_tablename, v_upsert) USING NEW; ELSE RETURN NEW; END IF;'; IF v_trigger_return_null IS TRUE THEN v_trig_func := v_trig_func ||' RETURN NULL;'; ELSE v_trig_func := v_trig_func ||' RETURN NEW;'; END IF; IF v_trigger_exception_handling THEN v_trig_func := v_trig_func ||' EXCEPTION WHEN OTHERS THEN RAISE WARNING ''pg_partman insert into child table failed, row inserted into parent (%.%). ERROR: %'', TG_TABLE_SCHEMA, TG_TABLE_NAME, COALESCE(SQLERRM, ''unknown''); RETURN NEW;'; END IF; v_trig_func := v_trig_func ||' END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for custom time table: %s', p_parent_table)); END IF; ELSE RAISE EXCEPTION 'ERROR: Invalid time partitioning type given: %', v_type; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); 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 FUNCTION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''Partition function maintenance for table %s 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 $$; /* * Function to turn a table into the parent of a partition set */ CREATE FUNCTION 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_use_run_maintenance boolean DEFAULT NULL , 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 , p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_base_timestamp timestamptz; v_count int := 1; v_datetime_string 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_job_id bigint; v_jobmon_schema text; v_last_partition_created boolean; v_max bigint; v_notnull boolean; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_partition_id bigint; v_parent_partition_timestamp timestamptz; v_parent_schema text; v_parent_tablename text; v_partition_time timestamptz; v_partition_time_array timestamptz[]; v_partition_id_array bigint[]; v_row record; v_run_maint boolean; 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_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); BEGIN IF position('.' in p_parent_table) = 0 THEN RAISE EXCEPTION 'Parent table must be schema qualified'; END IF; IF p_upsert <> '' AND @extschema@.check_version('9.5.0') = 'false' THEN RAISE EXCEPTION 'INSERT ... ON CONFLICT (UPSERT) feature is only supported in PostgreSQL 9.5 and later'; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = 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 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; IF p_type = 'id' AND p_epoch <> 'none' THEN RAISE EXCEPTION 'p_epoch can only be used with time-based partitioning'; END IF; IF NOT @extschema@.check_partition_type(p_type) THEN RAISE EXCEPTION '% is not a valid partitioning type', p_type; 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'); IF p_use_run_maintenance IS NOT NULL THEN IF p_use_run_maintenance IS FALSE AND (p_type = 'time' OR p_type = 'time-custom') THEN RAISE EXCEPTION 'p_run_maintenance cannot be set to false for time based partitioning'; END IF; v_run_maint := p_use_run_maintenance; ELSIF p_type = 'time' OR p_type = 'time-custom' THEN v_run_maint := TRUE; ELSIF p_type = 'id' THEN v_run_maint := FALSE; ELSE RAISE EXCEPTION 'use_run_maintenance value cannot be set NULL'; END IF; 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 ) 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_use_run_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 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_use_run_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) 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_use_run_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); END LOOP; IF p_type = 'time' OR p_type = 'time-custom' 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 <> 'time-custom' THEN RAISE EXCEPTION 'Must use a predefined time interval if not using type "time-custom". See documentation.'; 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 , use_run_maintenance , inherit_fk , jobmon , upsert , trigger_return_null) VALUES ( p_parent_table , p_type , v_time_interval , p_epoch , p_control , p_premake , p_constraint_cols , v_datetime_string , v_run_maint , p_inherit_fk , p_jobmon , p_upsert , p_trigger_return_null); 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 -- Currently unknown edge case if code gets here RAISE EXCEPTION 'No child tables created. Unexpected edge case encountered. Please report this error to author with conditions that led to it.'; END IF; END 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 p_type = 'id' THEN v_id_interval := p_interval::bigint; IF v_id_interval < 10 THEN RAISE EXCEPTION 'Interval for serial 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 INTO v_higher_parent_schema, v_higher_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 WHERE p.partition_type = 'id'; IF v_higher_parent_table IS NOT NULL THEN -- 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 , use_run_maintenance , inherit_fk , jobmon , upsert , trigger_return_null) VALUES ( p_parent_table , p_type , v_id_interval , p_control , p_premake , p_constraint_cols , v_run_maint , p_inherit_fk , p_jobmon , p_upsert , p_trigger_return_null); 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 WHERE p.partition_type = 'id'; 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 EXCEPTION 'No child tables created. Unexpected edge case encountered. Please report this error to author with conditions that led to it.'; END IF; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating partition function'); END IF; IF p_type = 'time' OR p_type = 'time-custom' 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 p_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); 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 $$; /* * Function to create a child table in a time-based partition set */ CREATE OR REPLACE FUNCTION create_partition_time(p_parent_table text, p_partition_times timestamptz[], p_analyze boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_datetime_string text; v_exists text; v_epoch text; v_grantees text[]; v_hasoids boolean; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_grant record; v_parent_owner text; v_parent_schema text; v_parent_tablename text; v_partition_created boolean := false; v_partition_name text; v_partition_suffix text; v_parent_tablespace text; v_partition_expression text; v_partition_interval interval; v_partition_timestamp_end timestamptz; v_partition_timestamp_start timestamptz; v_quarter text; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_sub_timestamp_max timestamptz; v_sub_timestamp_min timestamptz; v_trunc_value text; v_time timestamptz; v_type text; v_unlogged char; v_year text; BEGIN SELECT partition_type , control , partition_interval , epoch , inherit_fk , jobmon , datetime_string INTO v_type , v_control , v_partition_interval , v_epoch , v_inherit_fk , v_jobmon , v_datetime_string FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::timestamptz, sub_max::timestamptz INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'time'); SELECT tableowner, schemaname, tablename, tablespace INTO v_parent_owner, v_parent_schema, v_parent_tablename, v_parent_tablespace FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) END; IF p_debug THEN RAISE NOTICE 'create_partition_time: v_partition_expression: %', v_partition_expression; END IF; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_timestamp_start := v_time; BEGIN v_partition_timestamp_end := v_time + v_partition_interval; 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_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_time||' skipped'); CONTINUE; END; -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_timestamp_min IS NOT NULL THEN IF v_time < v_sub_timestamp_min OR v_time > v_sub_timestamp_max THEN CONTINUE; END IF; END IF; -- This suffix generation code is in partition_data_time() as well v_partition_suffix := to_char(v_time, v_datetime_string); v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); SELECT tablename INTO v_exists FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_partition_name::name; IF v_exists IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Creating new partition %s.%s with interval from %s to %s' , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end-'1sec'::interval)); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c 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; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS)' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c 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; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%s >= %L AND %4$s < %6$L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_partition_expression , v_partition_timestamp_start , v_partition_timestamp_end); IF v_epoch = 'seconds' THEN EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_int_check' , v_control , EXTRACT('epoch' from v_partition_timestamp_start) , v_control , EXTRACT('epoch' from v_partition_timestamp_end) ); ELSIF v_epoch = 'milliseconds' THEN EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_int_check' , v_control , EXTRACT('epoch' from v_partition_timestamp_start) * 1000 , v_control , EXTRACT('epoch' from v_partition_timestamp_end) * 1000); END IF; EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); -- If custom time, set extra config options. IF v_type = 'time-custom' THEN INSERT INTO @extschema@.custom_time_partitions (parent_table, child_table, partition_range) VALUES ( p_parent_table, v_parent_schema||'.'||v_partition_name, tstzrange(v_partition_timestamp_start, v_partition_timestamp_end, '[)') ); END IF; PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_use_run_maintenance , sub_infinite_time_partitions , sub_jobmon , sub_trigger_exception_handling FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Subpartitioning %s.%s', v_parent_schema, v_partition_name)); END IF; 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_use_run_maintenance := %L , p_inherit_fk := %L , p_epoch := %L , p_jobmon := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_use_run_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions , trigger_exception_handling = v_row.sub_trigger_exception_handling WHERE parent_table = v_parent_schema||'.'||v_partition_name; END LOOP; -- end sub partitioning LOOP -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table, p_job_id := v_job_id, p_debug := p_debug); v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s. Attempted intervals: %s', p_parent_table, p_partition_times)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; 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_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * 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. * To avoid logical complications and contention issues, ALL subpartitions must be maintained using run_maintenance(). * This means the automatic, trigger based partition creation for serial partitioning will not work if it is a subpartition. */ CREATE FUNCTION create_sub_parent( p_top_parent text , p_control text , p_type text , p_interval text , 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 , p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_last_partition text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_interval text; v_parent_type text; v_row record; v_row_last_part record; v_run_maint boolean; v_sql text; v_success boolean := false; v_top_type text; BEGIN SELECT use_run_maintenance INTO v_run_maint FROM @extschema@.part_config WHERE parent_table = p_top_parent; IF v_run_maint 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; ELSIF v_run_maint = false THEN RAISE EXCEPTION 'Any parent table that will be part of a sub-partitioned set (on any level) must have use_run_maintenance set to true in part_config table, even for serial partitioning. See documentation for more info.'; END IF; IF p_upsert <> '' AND @extschema@.check_version('9.5.0') = 'false' THEN RAISE EXCEPTION 'INSERT ... ON CONFLICT (UPSERT) feature is only supported in PostgreSQL 9.5 and later'; END IF; 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'); FOR v_row IN -- Loop through all current children to turn them into partitioned tables SELECT partition_schemaname||'.'||partition_tablename AS child_table FROM @extschema@.show_partitions(p_top_parent) LOOP SELECT partition_type, partition_interval INTO v_parent_type, v_parent_interval FROM @extschema@.part_config WHERE parent_table = v_row.child_table; IF v_parent_interval = p_interval THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RAISE EXCEPTION 'Sub-partition interval cannot be equal to parent interval'; END IF; IF (v_parent_type = 'time' OR v_parent_type = 'time-custom') AND (p_type = 'time' OR p_type = 'time-custom') THEN IF p_interval::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 the given parent interval'; END IF; IF p_interval = 'weekly' 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_parent_type = 'id' 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 the given parent interval'; END IF; END IF; -- Just call existing create_parent() function but add the given parameters to the part_config_sub table as well 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_use_run_maintenance := %L , p_start_partition := %L , p_inherit_fk := %L , p_epoch := %L , p_upsert := %L , p_trigger_return_null := %L , p_jobmon := %L , p_debug := %L )' , v_row.child_table , p_control , p_type , p_interval , p_constraint_cols , p_premake , true , p_start_partition , p_inherit_fk , p_epoch , p_upsert , p_trigger_return_null , p_jobmon , p_debug); EXECUTE v_sql; END LOOP; 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_use_run_maintenance , sub_epoch , sub_upsert , sub_jobmon , sub_trigger_return_null) VALUES ( p_top_parent , p_control , p_type , p_interval , p_constraint_cols , p_premake , p_inherit_fk , true , p_epoch , p_upsert , p_jobmon , p_trigger_return_null); v_success := true; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_success; END $$; /* * Populate the child table(s) of a time-based partition set with old data from the original parent */ CREATE OR REPLACE FUNCTION partition_data_time( p_parent_table text , p_batch_count int DEFAULT 1 , p_batch_interval interval DEFAULT NULL , p_lock_wait numeric DEFAULT 0 , p_order text DEFAULT 'ASC' , p_analyze boolean DEFAULT true) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_datetime_string text; v_current_partition_name text; v_epoch text; v_last_partition text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_timestamp timestamptz; v_min_partition_timestamp timestamptz; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_expression text; v_partition_interval interval; v_partition_suffix text; v_partition_timestamp timestamptz[]; v_quarter text; v_rowcount bigint; v_start_control timestamptz; v_time_position int; v_total_rows bigint := 0; v_type text; v_year text; BEGIN 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'); SELECT partition_type , partition_interval::interval , control , datetime_string , epoch INTO v_type , v_partition_interval , v_control , v_datetime_string , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); IF NOT FOUND THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) END; FOR i IN 1..p_batch_count LOOP IF p_order = 'ASC' THEN EXECUTE format('SELECT min(%s) FROM ONLY %I.%I', v_partition_expression, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSIF p_order = 'DESC' THEN EXECUTE format('SELECT max(%s) FROM ONLY %I.%I', v_partition_expression, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; IF v_start_control IS NULL THEN EXIT; END IF; IF v_type = 'time' THEN CASE WHEN v_partition_interval = '15 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '15min'::interval * floor(date_part('minute', v_start_control) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '30min'::interval * floor(date_part('minute', v_start_control) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control); WHEN v_partition_interval = '1 day' THEN v_min_partition_timestamp := date_trunc('day', v_start_control); WHEN v_partition_interval = '1 week' THEN v_min_partition_timestamp := date_trunc('week', v_start_control); WHEN v_partition_interval = '1 month' THEN v_min_partition_timestamp := date_trunc('month', v_start_control); WHEN v_partition_interval = '3 months' THEN v_min_partition_timestamp := date_trunc('quarter', v_start_control); WHEN v_partition_interval = '1 year' THEN v_min_partition_timestamp := date_trunc('year', v_start_control); END CASE; ELSIF v_type = 'time-custom' THEN SELECT child_start_time INTO v_min_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition , v_partition_interval::text , p_parent_table); v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; LOOP IF v_start_control >= v_min_partition_timestamp AND v_start_control < v_max_partition_timestamp THEN EXIT; ELSE BEGIN IF v_start_control > v_max_partition_timestamp THEN -- Keep going forward in time, checking if child partition time interval encompasses the current v_start_control value v_min_partition_timestamp := v_max_partition_timestamp; v_max_partition_timestamp := v_max_partition_timestamp + v_partition_interval; ELSE -- Keep going backwards in time, checking if child partition time interval encompasses the current v_start_control value v_max_partition_timestamp := v_min_partition_timestamp; v_min_partition_timestamp := v_min_partition_timestamp - v_partition_interval; END IF; EXCEPTION WHEN datetime_field_overflow THEN RAISE EXCEPTION 'Attempted partition time interval is outside PostgreSQL''s supported time range. Unable to create partition with interval before timestamp % ', v_min_partition_interval; END; END IF; END LOOP; END IF; v_partition_timestamp := ARRAY[v_min_partition_timestamp]; IF p_order = 'ASC' THEN -- Ensure batch interval given as parameter doesn't cause maximum to overflow the current partition maximum IF (v_start_control + p_batch_interval) >= (v_min_partition_timestamp + v_partition_interval) THEN v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; ELSE v_max_partition_timestamp := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; -- Ensure batch interval given as parameter doesn't cause minimum to underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_timestamp THEN v_min_partition_timestamp = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM ONLY %I.%I WHERE %s >= %L AND %3$s < %5$L FOR UPDATE NOWAIT' , v_parent_schema , v_parent_tablename , v_partition_expression , v_min_partition_timestamp , v_max_partition_timestamp); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; PERFORM @extschema@.create_partition_time(p_parent_table, v_partition_timestamp, p_analyze); -- This suffix generation code is in create_partition_time() as well v_partition_suffix := to_char(v_min_partition_timestamp, v_datetime_string); v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); EXECUTE format('WITH partition_data AS ( DELETE FROM ONLY %I.%I WHERE %s >= %L AND %3$s < %5$L RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_parent_schema , v_parent_tablename , v_partition_expression , v_min_partition_timestamp , v_max_partition_timestamp , v_parent_schema , v_current_partition_name); GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; PERFORM @extschema@.create_function_time(p_parent_table); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_total_rows; END $$; /* * Function to manage pre-creation of the next partitions in a set. * Also manages dropping old partitions if the retention option is set. * If p_parent_table is passed, will only run run_maintenance() on that one table (no matter what the configuration table may have set for it) * Otherwise, will run on all tables in the config table with p_run_maintenance() set to true. * For large partition sets, running analyze can cause maintenance to take longer than expected. Can set p_analyze to false to avoid a forced analyze run. * Be aware that constraint exclusion may not work properly until an analyze on the partition set is run. */ CREATE OR REPLACE FUNCTION run_maintenance(p_parent_table text DEFAULT NULL, p_analyze boolean DEFAULT true, p_jobmon boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_check_subpart int; v_create_count int := 0; v_current_partition text; v_current_partition_id bigint; v_current_partition_timestamp timestamptz; v_drop_count int := 0; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_created boolean; v_last_partition_id bigint; v_last_partition_timestamp timestamptz; v_max_id_parent bigint; v_max_time_parent timestamptz; v_new_search_path text := '@extschema@,pg_temp'; v_next_partition_id bigint; v_next_partition_timestamp timestamptz; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_expression text; v_premade_count int; v_premake_id_max bigint; v_premake_id_min bigint; v_premake_timestamp_min timestamptz; v_premake_timestamp_max timestamptz; v_row record; v_row_max_id record; v_row_max_time record; v_row_sub record; v_step_id bigint; v_step_overflow_id bigint; v_sub_id_max bigint; v_sub_id_max_suffix bigint; v_sub_id_min bigint; v_sub_parent text; v_sub_timestamp_max timestamptz; v_sub_timestamp_max_suffix timestamptz; v_sub_timestamp_min timestamptz; v_tablename text; v_tables_list_sql text; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman maintenance already running.'; RETURN; 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'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN RUN MAINTENANCE'); v_step_id := add_step(v_job_id, 'Running maintenance loop'); END IF; v_row := NULL; -- Ensure it's reset v_tables_list_sql := 'SELECT parent_table , partition_type , partition_interval , control , premake , undo_in_progress , sub_partition_set_full , epoch , infinite_time_partitions FROM @extschema@.part_config WHERE sub_partition_set_full = false'; IF p_parent_table IS NULL THEN v_tables_list_sql := v_tables_list_sql || ' AND use_run_maintenance = true'; ELSE v_tables_list_sql := v_tables_list_sql || format(' AND parent_table = %L', p_parent_table); END IF; FOR v_row IN EXECUTE v_tables_list_sql LOOP CONTINUE WHEN v_row.undo_in_progress; -- Check for consistent data in part_config_sub table. Was unable to get this working properly as either a constraint or trigger. -- Would either delay raising an error until the next write (which I cannot predict) or disallow future edits to update a sub-partition set's configuration. -- This way at least provides a consistent way to check that I know will run. If anyone can get a working constraint/trigger, please help! -- Don't have to worry about this in the serial trigger maintenance since subpartitioning requires run_maintenance(). SELECT sub_parent INTO v_sub_parent FROM @extschema@.part_config_sub WHERE sub_parent = v_row.parent_table; IF v_sub_parent IS NOT NULL THEN SELECT count(*) INTO v_check_subpart FROM @extschema@.check_subpart_sameconfig(v_row.parent_table); IF v_check_subpart > 1 THEN RAISE EXCEPTION 'Inconsistent data in part_config_sub table. Sub-partition tables that are themselves sub-partitions cannot have differing configuration values among their siblings. Run this query: "SELECT * FROM @extschema@.check_subpart_sameconfig(''%'');" This should only return a single row or nothing. If multiple rows are returned, results are all children of the given parent. Update the differing values to be consistent for your desired values.', v_row.sub_parent; END IF; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(v_row.parent_table, '.', 1)::name AND tablename = split_part(v_row.parent_table, '.', 2)::name; v_partition_expression := CASE WHEN v_row.epoch = 'seconds' THEN format('to_timestamp(%I)', v_row.control) WHEN v_row.epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_row.control) ELSE format('%I', v_row.control) END; IF p_debug THEN RAISE NOTICE 'run_maint: v_partition_expression: %', v_partition_expression; END IF; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LIMIT 1; IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_last_partition: %', v_row.parent_table, v_last_partition; END IF; IF v_row.partition_type = 'time' OR v_row.partition_type = 'time-custom' THEN SELECT child_start_time INTO v_last_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_row.partition_interval, v_row.parent_table); -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_time IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%s)::text FROM %I.%I' , v_partition_expression , v_row_max_time.partition_schemaname , v_row_max_time.partition_tablename ) INTO v_current_partition_timestamp; IF v_current_partition_timestamp IS NOT NULL THEN SELECT suffix_timestamp INTO v_current_partition_timestamp FROM @extschema@.show_partition_name(v_row.parent_table, v_current_partition_timestamp::text); EXIT; END IF; END LOOP; -- Check for values in the parent table. If they are there and greater than all child values, use that instead -- This allows maintenance to continue working properly if there is a large gap in data insertion. Data will remain in parent, but new tables will be created EXECUTE format('SELECT max(%s) FROM ONLY %I.%I', v_partition_expression, v_parent_schema, v_parent_tablename) INTO v_max_time_parent; IF p_debug THEN RAISE NOTICE 'run_maint: v_current_partition_timestamp: %, v_max_time_parent: %', v_current_partition_timestamp, v_max_time_parent; END IF; IF v_max_time_parent > v_current_partition_timestamp THEN SELECT suffix_timestamp INTO v_current_partition_timestamp FROM @extschema@.show_partition_name(v_row.parent_table, v_max_time_parent::text); END IF; IF v_current_partition_timestamp IS NULL THEN -- Partition set is completely empty IF v_row.infinite_time_partitions IS TRUE THEN -- Set it to now so new partitions continue to be created v_current_partition_timestamp = CURRENT_TIMESTAMP; ELSE -- Nothing to do CONTINUE; END IF; END IF; -- If this is a subpartition, determine if the last child table has been made. If so, mark it as full so future maintenance runs can skip it SELECT sub_min::timestamptz, sub_max::timestamptz INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'time'); IF v_sub_timestamp_max IS NOT NULL THEN SELECT suffix_timestamp INTO v_sub_timestamp_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_timestamp_max::text); IF v_sub_timestamp_max_suffix = v_last_partition_timestamp THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; -- Check and see how many premade partitions there are. v_premade_count = round(EXTRACT('epoch' FROM age(v_last_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); v_next_partition_timestamp := v_last_partition_timestamp; IF p_debug THEN RAISE NOTICE 'run_maint before loop: current_partition_timestamp: %, v_premade_count: %, v_sub_timestamp_min: %, v_sub_timestamp_max: %' , v_current_partition_timestamp , v_premade_count , v_sub_timestamp_min , v_sub_timestamp_max; END IF; -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed WHILE (v_premade_count < v_row.premake) LOOP IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_premade_count: %, v_next_partition_timestamp: %', v_row.parent_table, v_premade_count, v_next_partition_timestamp; END IF; IF v_next_partition_timestamp < v_sub_timestamp_min OR v_next_partition_timestamp > v_sub_timestamp_max THEN -- With subpartitioning, no need to run if the timestamp is not in the parent table's range EXIT; END IF; BEGIN v_next_partition_timestamp := v_next_partition_timestamp + v_row.partition_interval::interval; EXCEPTION WHEN datetime_field_overflow THEN v_premade_count := v_row.premake; -- do this so it can exit the premake check loop and continue in the outer for loop IF v_jobmon_schema IS NOT NULL THEN 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', format('Child partition creation skipped for parent table: %s', v_partition_time)); END IF; RAISE WARNING 'Attempted partition time interval is outside PostgreSQL''s supported time range. Child partition creation skipped for parent table %', v_row.parent_table; CONTINUE; END; v_last_partition_created := @extschema@.create_partition_time(v_row.parent_table, ARRAY[v_next_partition_timestamp], p_analyze); IF v_last_partition_created THEN v_create_count := v_create_count + 1; PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); END IF; v_premade_count = round(EXTRACT('epoch' FROM age(v_next_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); END LOOP; ELSIF v_row.partition_type = 'id' THEN -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%I)::text FROM %I.%I' , v_row.control , v_row_max_id.partition_schemaname , v_row_max_id.partition_tablename) INTO v_current_partition_id; IF v_current_partition_id IS NOT NULL THEN SELECT suffix_id INTO v_current_partition_id FROM @extschema@.show_partition_name(v_row.parent_table, v_current_partition_id::text); EXIT; END IF; END LOOP; -- Check for values in the parent table. If they are there and greater than all child values, use that instead -- This allows maintenance to continue working properly if there is a large gap in data insertion. Data will remain in parent, but new tables will be created EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_row.control, v_parent_schema, v_parent_tablename) INTO v_max_id_parent; IF v_max_id_parent > v_current_partition_id THEN SELECT suffix_id INTO v_current_partition_id FROM @extschema@.show_partition_name(v_row.parent_table, v_max_id_parent::text); END IF; IF v_current_partition_id IS NULL THEN -- Partition set is completely empty. Nothing to do CONTINUE; END IF; SELECT child_start_id INTO v_last_partition_id FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_row.partition_interval, v_row.parent_table); -- Determine if this table is a child of a subpartition parent. If so, get limits to see if run_maintenance even needs to run for it. SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'id'); IF v_sub_id_max IS NOT NULL THEN SELECT suffix_id INTO v_sub_id_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_id_max::text); IF v_sub_id_max_suffix = v_last_partition_id THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; v_next_partition_id := v_last_partition_id; v_premade_count := ((v_last_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed. WHILE (v_premade_count < v_row.premake) LOOP IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_premade_count: %, v_next_partition_id: %', v_row.parent_table, v_premade_count, v_next_partition_id; END IF; IF v_next_partition_id < v_sub_id_min OR v_next_partition_id > v_sub_id_max THEN -- With subpartitioning, no need to run if the id is not in the parent table's range EXIT; END IF; v_next_partition_id := v_next_partition_id + v_row.partition_interval::bigint; v_last_partition_created := @extschema@.create_partition_id(v_row.parent_table, ARRAY[v_next_partition_id], p_analyze); IF v_last_partition_created THEN v_create_count := v_create_count + 1; PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); END IF; v_premade_count := ((v_next_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); END LOOP; END IF; -- end main IF check for time or id END LOOP; -- end of creation loop -- Manage dropping old partitions if retention option is set FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND (partition_type = 'time' OR partition_type = 'time-custom') LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); END IF; END IF; IF v_drop_count > 0 THEN PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); END IF; END LOOP; FOR v_row IN SELECT parent_table FROM @extschema@.part_config WHERE retention IS NOT NULL AND undo_in_progress = false AND partition_type = 'id' LOOP IF p_parent_table IS NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); ELSE -- Only run retention on table given in parameter IF p_parent_table <> v_row.parent_table THEN CONTINUE; ELSE v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); END IF; END IF; IF v_drop_count > 0 THEN PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Partition maintenance finished. %s partitions made. %s partitions dropped.', v_create_count, v_drop_count)); 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'); 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 RUN MAINTENANCE'')', v_jobmon_schema) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; /* * Function to undo time-based partitioning created by this extension */ CREATE OR REPLACE FUNCTION undo_partition_time(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval interval DEFAULT NULL, p_keep_table boolean DEFAULT true, p_lock_wait numeric DEFAULT 0) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_batch_loop_count int := 0; v_child_loop_total bigint := 0; v_child_min timestamptz; v_child_table text; v_control text; v_epoch text; v_function_name text; v_inner_loop_count int; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_expression text; v_partition_interval interval; v_row record; v_rowcount bigint; v_step_id bigint; v_sub_count int; v_total bigint := 0; v_trig_name text; v_type text; v_undo_count int := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition_time')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition_time already running.'; RETURN 0; END IF; SELECT partition_type , partition_interval::interval , control , jobmon , epoch INTO v_type , v_partition_interval , v_control , v_jobmon , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'time' OR partition_type = 'time-custom'); IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Check if any child tables are themselves partitioned or part of an inheritance tree. Prevent undo at this level if so. -- Need to either lock child tables at all levels or handle the proper removal of triggers on all child tables first -- before multi-level undo can be performed safely. FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table) LOOP SELECT count(*) INTO v_sub_count FROM pg_catalog.pg_inherits i JOIN pg_catalog.pg_class c ON i.inhparent = c.oid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_row.partition_tablename::name AND n.nspname = v_row.partition_schemaname::name; IF v_sub_count > 0 THEN RAISE EXCEPTION 'Child table for this parent has child table(s) itself (%). Run undo partitioning on this table or remove inheritance first to ensure all data is properly moved to parent', v_row.partition_schemaname||'.'||v_row.partition_tablename; END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN UNDO PARTITIONING: %s', p_parent_table)); v_step_id := add_step(v_job_id, format('Undoing partitioning for table %s', p_parent_table)); END IF; IF p_batch_interval IS NULL THEN p_batch_interval := v_partition_interval; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) end; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables. v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t JOIN pg_catalog.pg_class c ON t.tgrelid = c.oid WHERE tgname = v_trig_name::name AND c.relname = v_parent_tablename::name; SELECT proname INTO v_function_name FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid WHERE n.nspname = v_parent_schema::name AND proname = v_function_name::name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_parent_tablename); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; RETURN -1; END IF; END IF; -- END p_lock_wait IF EXECUTE format('DROP TRIGGER IF EXISTS %I ON %I.%I', v_trig_name, v_parent_schema, v_parent_tablename); END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later IF v_function_name IS NOT NULL THEN EXECUTE format('DROP FUNCTION IF EXISTS %I.%I()', v_parent_schema, v_function_name); END IF; IF v_jobmon_schema IS NOT NULL THEN IF (v_trig_name IS NOT NULL OR v_function_name IS NOT NULL) THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); ELSE PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process.'); END IF; END IF; <> LOOP -- Get ordered list of child table in set. Store in variable one at a time per loop until none are left or batch count is reached. -- This easily allows it to loop over same child table until empty or move onto next child table after it's dropped SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC') LIMIT 1; EXIT outer_child_loop WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Removing child partition: %s.%s', v_parent_schema, v_child_table)); END IF; EXECUTE format('SELECT min(%s) FROM %I.%I', v_partition_expression, v_parent_schema, v_child_table) INTO v_child_min; IF v_child_min IS NULL THEN -- No rows left in this child table. Remove from partition set. -- lockwait timeout for table drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_child_table); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; RETURN -1; END IF; END IF; -- END p_lock_wait IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_parent_schema , v_child_table , v_parent_schema , v_parent_tablename); IF p_keep_table = false THEN EXECUTE format('DROP TABLE %I.%I', v_parent_schema, v_child_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table DROPPED. Moved %s rows to parent', v_child_loop_total)); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table UNINHERITED, not DROPPED. Moved %s rows to parent', v_child_loop_total)); END IF; END IF; IF v_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_parent_schema||'.'||v_child_table; END IF; v_undo_count := v_undo_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached CONTINUE outer_child_loop; -- skip data moving steps below END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM %I.%I WHERE %I <= %L FOR UPDATE NOWAIT' , v_parent_schema , v_child_table , v_control , v_child_min + (p_batch_interval * v_inner_loop_count)); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; RETURN -1; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval EXECUTE format('WITH move_data AS ( DELETE FROM %I.%I WHERE %s <= %L RETURNING *) INSERT INTO %I.%I SELECT * FROM move_data' , v_parent_schema , v_child_table , v_partition_expression , v_child_min + (p_batch_interval * v_inner_loop_count) , v_parent_schema , v_parent_tablename); GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Moved %s rows to parent.', v_child_loop_total)); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; -- Check again if table is empty and go to outer loop again to drop it if so EXECUTE format('SELECT min(%s) FROM %I.%I', v_partition_expression, v_parent_schema, v_child_table) INTO v_child_min; CONTINUE outer_child_loop WHEN v_child_min IS NULL; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC') LIMIT 1; IF v_child_table IS NULL THEN DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) to the parent. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', format('Copied %s row(s) to the parent. Removed %s partitions.', v_total, v_undo_count)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_total; 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 UNDO PARTITIONING: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; -- Restore dropped object privileges DO $$ DECLARE v_row record; BEGIN FOR v_row IN SELECT statement FROM partman_preserve_privs_temp LOOP IF v_row.statement IS NOT NULL THEN EXECUTE v_row.statement; END IF; END LOOP; END $$; DROP TABLE IF EXISTS partman_preserve_privs_temp; pg_partman-4.6.0/updates/pg_partman--2.6.3--2.6.4.sql000066400000000000000000000051511412761706100214410ustar00rootroot00000000000000-- Note, if you installed update 2.6.3 prior to the release of 2.6.4, this update will not change anything, but is still required to be installed to allow the extension version update chain to work. -- While upgrading to version 2.6.3 or later, if you encounter the error: "cannot ALTER TABLE "part_config_sub" because it has pending trigger events" (or for the part_config table), then you must install that update by itself first and then install the update to 2.6.4 separately in a different transaction. Changing the values of a column in the same transaction as altering it to add/remove a constraint can cause the pending triggers error. The only way around that is to do the steps as separate transactions, which this update separates out from the previous one that caused the issue. Unfortunately, all extension updates run in a single transaction unless you upgrade manually to a specific version to control the transaction state between versions. (Github Issue #167). /* If you encounter this error, do the updates for 2.6.3 & 2.6.4 as follows: BEGIN; ALTER EXTENSION pg_partman UPDATE TO '2.6.3'; COMMIT; BEGIN; ALTER EXTENSION pg_partman UPDATE TO '2.6.4'; COMMIT; */ -- Redoing this again shouldn't matter ALTER TABLE @extschema@.part_config ALTER COLUMN epoch SET DEFAULT 'none'; ALTER TABLE @extschema@.part_config_sub ALTER COLUMN sub_epoch SET DEFAULT 'none'; -- Only recreate constraints if they don't already exist from a previous 2.6.3 update before 2.6.4 was available DO $$ DECLARE v_exists text; BEGIN SELECT conname INTO v_exists FROM pg_catalog.pg_constraint t JOIN pg_catalog.pg_class c ON t.conrelid = c.oid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE t.conname = 'part_config_epoch_check' AND c.relname = 'part_config' AND n.nspname = '@extschema@'; IF v_exists IS NULL THEN EXECUTE format(' ALTER TABLE @extschema@.part_config ADD CONSTRAINT part_config_epoch_check CHECK (@extschema@.check_epoch_type(epoch))'); END IF; v_exists := NULL; SELECT conname INTO v_exists FROM pg_catalog.pg_constraint t JOIN pg_catalog.pg_class c ON t.conrelid = c.oid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE t.conname = 'part_config_sub_epoch_check' AND c.relname = 'part_config_sub' AND n.nspname = '@extschema@'; IF v_exists IS NULL THEN EXECUTE format(' ALTER TABLE @extschema@.part_config_sub ADD CONSTRAINT part_config_sub_epoch_check CHECK (@extschema@.check_epoch_type(sub_epoch))'); END IF; END $$; pg_partman-4.6.0/updates/pg_partman--2.6.4--3.0.0.sql000066400000000000000000010654211412761706100214400ustar00rootroot00000000000000-- IMPORTANT NOTE: The initial version of pg_partman 3 had to be split into two updates due to changing both the data in the config table as well as adding constraints on that data. Depending on the data contained in the config table, doing this in a single-transaction update may not work (you may see an error about pending trigger events). If this is the case, please update to version 3.0.1 in a separate transaction from your update to 3.0.0. Example: /* BEGIN; ALTER EXTENSION pg_partman UPDATE TO '3.0.0'; COMMIT; BEGIN; ALTER EXTENSION pg_partman UPDATE TO '3.0.1'; COMMIT; */ -- Update 3.0.1 MUST be installed for version 3.x of pg_partman to work properly. As long as these updates are run within a few seconds of each other, there should be no issues. -- In preparation for native partitioning support, the "partition_type" parameter to create_parent() and create_sub_parent() has been simplified. -- The two main, valid values are now: "partman" or "native". "partman" is the same pre-3.0 partitioning method that has always existed. "native" uses the new native partitioning method that comes with PostgreSQL 10. See below for important information about native partitioning. -- Whether a table is partitioned by time or id/serial is now automatically determined by the data type of the partition column. -- smallint, integer, & bigint (int2, int4, int8 respectively) are automatically id/serial partitioning (unless the epoch flag is set) -- timestamptz, timestamp, & date are automatically time -- If the preset time intervals in the documentation are used (daily, weekly, etc), then the more efficient, static trigger function will be used. If any other time interval value is given, then the slower, but more flexible "time-custom" trigger will be used (See "Custom Time Interval Considerations" section of documentation). -- "time-custom" is also still a valid value, but not necessary unless you want to be explicit in your partitioning method. -- Added preliminary support for native partitioning methods that will be introduced in PostgreSQL 10. Note this feature is still in very early testing, and could change before the official 10.0 release. But since it's a significant new feature, I want to stay ahead of it and allow real-world testing as early as possible for those interested in it and also make sure it works with pg_partman's own trigger-based partitioning methods as well. (Github Issue #171) -- Use partition_type "native" in the create_parent() or create_sub_parent() functions. See pg_partman.md for more info -- Native Sub-partitioning is a DESTRUCTIVE processes and cannot be run on a table that has any data without losing that data. This is because a table has to be declared natively partitioned at creation (no ALTER statement exists) and the current child tables that must become parents have to be dropped and recreated as such. -- A new parameter flag for create_sub_parent() is included that must be set to "yes" to help ensure this is not accidentally done. -- Features NOT (yet) available in native: -- Indexes are not currently possible to create on a partitioned parent table. This means there is currently no automatic index support for native partitioning in pg_partman. Waiting to see what final implementation will be like before adding code to handle this. -- Data that does not match any children causes an error and is not preserved in the parent. -- Foreign keys from partition set to other tables (FKs from other tables to partition sets not supported at all for any partition methods). -- Removed automatic partition creation via trigger at 50% for id/serial partitioning. Was making trigger function generation code harder to maintain and is just a bad practice in general to be making child partitions on the fly with data insertion. This is also not possible with native partitioning, so rather phase out the feature now. Now must use automatic partitioning and schedule frequently enough to keep up with ingestion rate or manually call run_maintenance() via some other means to maintain new partition creation for id-based partition sets. -- The next maintenance run of any id-partitioned table will have its function remove the 50% code-block. Unable to do it as part of the extension update since that would then make the trigger function part of the extension. -- If you encounter any issues caused by this update and the removal of the 50% code, call create_function_id() to recreate the trigger. This function has been added to pg_partman.md for reference. -- Changed config column & function arguments from "use_run_maintenance" to "automatic_maintenance". This makes the option clearer since manual maintenance can still use the run_maintenance() function. -- Valid values are currently: "on", "off" -- A value of "on" means that a call of run_maintenance() with no specific table parameter given will cause maintenance to automatically run for that partition set. -- A value of "off" means that maintenance will not run in the above situation, but if you give a specific table parameter to run_maintenance(), maintenance can still be run manually for it. -- Note that all partition sets in part_config have set this value to "on" as part of the 3.0.0 upgrade. Since the semantics of the column value have changed, I went with the safe option of turning maintenance for all partitions sets on vs trying to figure out what the intention of previous settings were. Review your configuration after upgrading and the new meanings for this column's value to ensure things are set up properly for your database. This is most relevant if you had this turned off to avoid automatic serial partition creation at 50%. Now that all serial partitioning requires run_maintenance(), this meaning is no longer relevant. -- The undo_partition plpgsql functions now return both the number of partitions undone as well as the number of rows copied/moved. Result is returned as a record set, so these functions can be queried as a table to have columns/rows returned. The value of undone partitions will be -1 when an issue is encountered while running the function. -- Improved performance if infinite_time_partitions flag is set to true. No longer obtains max value of partition set needlessly. (Github Pull Request #169) -- Added create_partition_time(), create_partition_id(), create_function_time(), create_function_id() to documenation. Had thought they were only really useful internally, but turns out they can be pretty useful in general to fill in missing children gaps and manually fix partition trigger function. (Github Issue #161). -- Fixed bug with upgrading from version 1.x to 2.x of pg_partman if your version of PostgreSQL had been less than 9.2 at the time the 1.6.0 version of pg_partman came out. The custom_time_partitions table was only created if PostgreSQL was >= than 9.2 since it relies on the range data type. If you then later upgraded to 9.2 or greater, the custom time table was never created and can cause issue with upgrading as well as if a custom time partition set is ever created. Since v2.x of pg_partman only works with PostgreSQL 9.4 and later, forcing this table creation as of v2.x should no longer be an issue (Github Issue #159). -- Fixed bug in sub-partitioning that did not check for sub-partitions being equal to or larger than the top parent's interval. Also did not properly disallow weekly sub-partitions. If any of your current partition sets have weekly sub-partitions, this won't break them, but you should be aware that time boundaries will not line up properly in some cases due to the nature of ISO weeks. It is recommended that you repartition in a different pattern. -- Fixed bug when undoing a subpartition set that did not remove the entry in the part_config_sub table. (Github Issue #168). -- If the parent table given as an argument to the undo function had an entry in part_config_sub, it is now always removed. -- By default, if you undo a child table that is also partitioned, it will not stop additional sibling children of the parent partition set from being subpartitioned unless that parent is also undone. -- To handle the above situation where you may not be removing the parent but don't want any additional subpartitioned children, a new function has been added: stop_sub_partition(). -- Fixed bug in undo_partition.py that would stop undoing the partition set if it hit a child table with zero rows. Script now returns the number of partitions undone instead of the rowcount moved. -- Also fixed bug in undo_partition.py that would mask any python errors and let script run as if nothing bad happened. -- Fixed bug in sub-partitioning when a parent was partitioned by time and its children were partitioned by ID. In certain situations, all ID sub-sets were not getting their parent table created. -- Removed unused p_analyze parameter from create_function_id(). -- Began moving function description comments into the functions themselves so they're visible in the database as well. ALTER TABLE @extschema@.part_config RENAME COLUMN use_run_maintenance TO automatic_maintenance; ALTER TABLE @extschema@.part_config_sub RENAME COLUMN sub_use_run_maintenance TO sub_automatic_maintenance; ALTER TABLE @extschema@.part_config ALTER COLUMN automatic_maintenance TYPE text; ALTER TABLE @extschema@.part_config_sub ALTER COLUMN sub_automatic_maintenance TYPE text; UPDATE @extschema@.part_config SET automatic_maintenance = 'on'; UPDATE @extschema@.part_config_sub SET sub_automatic_maintenance = 'on'; CREATE TEMP TABLE partman_preserve_privs_temp (statement text); INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_parent(text, text, text, text, text[], int, text, text, boolean, text, text, boolean, boolean, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_parent'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_sub_parent(text, text, text, text, text, text[], int, text, boolean, text, text, boolean, boolean, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_sub_parent'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.check_subpartition_limits(text, text) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'check_subpartition_limits'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.undo_partition(text, int, boolean, boolean, numeric) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'undo_partition'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.undo_partition_id(text, int, bigint, boolean, numeric) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'undo_partition_id'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.undo_partition_time(text, int, interval, boolean, numeric) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'undo_partition_time'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_function_id(text, bigint) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_function_id'; DROP FUNCTION @extschema@.create_parent(text, text, text, text, text[], int, boolean, text, boolean, text, text, boolean, boolean, boolean); DROP FUNCTION @extschema@.create_sub_parent(text, text, text, text, text[], int, text, boolean, text, text, boolean, boolean, boolean); DROP FUNCTION @extschema@.check_subpart_sameconfig(text); DROP FUNCTION @extschema@.undo_partition(text, int, boolean, boolean, numeric); DROP FUNCTION @extschema@.undo_partition_id(text, int, bigint, boolean, numeric); DROP FUNCTION @extschema@.undo_partition_time(text, int, interval, boolean, numeric); DROP FUNCTION @extschema@.create_function_id(text, bigint, boolean); -- This was also added in the 1.8.7--2.0.0.sql file to account for changes to the table being done in subsequent updates CREATE TABLE IF NOT EXISTS @extschema@.custom_time_partitions ( parent_table text NOT NULL , child_table text NOT NULL , partition_range tstzrange NOT NULL , PRIMARY KEY (parent_table, child_table)); -- IF EXISTS clause to CREATE INDEX was not added until 9.5, so use DO block instead since we're 9.4 compatible DO $$ DECLARE v_exists smallint; BEGIN SELECT count(*) FROM pg_catalog.pg_class INTO v_exists WHERE relname = 'custom_time_partitions_partition_range_idx' AND relkind = 'i'; IF v_exists < 1 THEN CREATE INDEX custom_time_partitions_partition_range_idx ON custom_time_partitions USING gist (partition_range); END IF; END $$; SELECT pg_catalog.pg_extension_config_dump('custom_time_partitions', ''); CREATE OR REPLACE FUNCTION check_partition_type (p_type text) RETURNS boolean LANGUAGE plpgsql IMMUTABLE SECURITY DEFINER AS $$ DECLARE v_result boolean; BEGIN /* * Check for valid config table partition types */ SELECT p_type IN ('partman', 'time-custom', 'native') INTO v_result; RETURN v_result; END $$; CREATE FUNCTION check_automatic_maintenance_value (p_automatic_maintenance text) RETURNS boolean LANGUAGE plpgsql IMMUTABLE SECURITY DEFINER AS $$ DECLARE v_result boolean; BEGIN /* * Check for valid config values for automatic maintenance * (not boolean to allow future values) */ SELECT p_automatic_maintenance IN ('on', 'off') INTO v_result; RETURN v_result; END $$; /* * Custom view to help improve privilege lookups for pg_partman. * information_schema is a performance bottleneck since indexes aren't being used properly. */ CREATE OR REPLACE VIEW @extschema@.table_privs AS SELECT u_grantor.rolname AS grantor, grantee.rolname AS grantee, nc.nspname AS table_schema, c.relname AS table_name, c.prtype AS privilege_type FROM ( SELECT oid, relname, relnamespace, relkind, relowner, (aclexplode(coalesce(relacl, acldefault('r', relowner)))).* FROM pg_class ) AS c (oid, relname, relnamespace, relkind, relowner, grantor, grantee, prtype, grantable), pg_namespace nc, pg_authid u_grantor, ( SELECT oid, rolname FROM pg_authid UNION ALL SELECT 0::oid, 'PUBLIC' ) AS grantee (oid, rolname) WHERE c.relnamespace = nc.oid AND c.relkind IN ('r', 'v', 'p') AND c.grantee = grantee.oid AND c.grantor = u_grantor.oid AND c.prtype IN ('INSERT', 'SELECT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER') AND (pg_has_role(u_grantor.oid, 'USAGE') OR pg_has_role(grantee.oid, 'USAGE') OR grantee.rolname = 'PUBLIC' ); CREATE OR REPLACE FUNCTION apply_cluster(p_parent_schema text, p_parent_tablename text, p_child_schema text, p_child_tablename text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_indexdef text; v_relkind char; v_row record; v_sql text; BEGIN /* * Function to apply cluster from parent to child table * Adapted from code fork by https://github.com/dturon/pg_partman */ 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'); 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 = p_parent_schema AND c.relname = p_parent_tablename; IF v_relkind = 'p' THEN RAISE EXCEPTION 'This function cannot run on natively partitioned tables'; ELSIF v_relkind IS NULL THEN RAISE EXCEPTION 'Unable to find given table in system catalogs: %.%', v_parent_schema, v_parent_tablename; END IF; WITH parent_info AS ( SELECT c.oid AS parent_oid FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = p_parent_schema::name AND c.relname = p_parent_tablename::name ) SELECT substring(pg_get_indexdef(i.indexrelid) from ' USING .*$') AS index_def INTO v_parent_indexdef FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid JOIN parent_info p ON p.parent_oid = indrelid WHERE i.indisclustered = true; -- Loop over all existing indexes in child table to find one with matching definition FOR v_row IN WITH child_info AS ( SELECT c.oid AS child_oid 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 ) SELECT substring(pg_get_indexdef(i.indexrelid) from ' USING .*$') AS child_indexdef , c.relname AS child_indexname FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid JOIN child_info p ON p.child_oid = indrelid LOOP IF v_row.child_indexdef = v_parent_indexdef THEN v_sql = format('ALTER TABLE %I.%I CLUSTER ON %I', p_child_schema, p_child_tablename, v_row.child_indexname); RAISE DEBUG '%', v_sql; EXECUTE v_sql; END IF; END LOOP; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END; $$; CREATE OR REPLACE FUNCTION apply_constraints(p_parent_table text, p_child_table text DEFAULT NULL, p_analyze boolean DEFAULT FALSE, p_job_id bigint DEFAULT NULL, p_debug boolean DEFAULT FALSE) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_child_exists text; v_child_tablename text; v_col text; v_constraint_cols text[]; v_constraint_col_type text; v_constraint_name text; v_constraint_values record; v_control text; v_control_type text; v_datetime_string text; v_epoch text; v_existing_constraint_name text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_id bigint; v_last_partition_timestamp timestamptz; v_max_id bigint; v_max_timestamp timestamptz; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_optimize_constraint int; v_parent_schema text; v_parent_table text; v_parent_tablename text; v_partition_interval text; v_partition_suffix text; v_premake int; v_sql text; v_step_id bigint; v_suffix_position int; v_type text; BEGIN /* * Apply constraints managed by partman extension */ SELECT parent_table , partition_type , control , premake , partition_interval , optimize_constraint , epoch , datetime_string , constraint_cols , jobmon INTO v_parent_table , v_type , v_control , v_premake , v_partition_interval , v_optimize_constraint , v_epoch , v_datetime_string , v_constraint_cols , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND constraint_cols IS NOT NULL; IF v_constraint_cols IS NULL THEN IF p_debug THEN RAISE NOTICE 'Given parent table (%) not set up for constraint management (constraint_cols is NULL)', p_parent_table; END IF; -- Returns silently to allow this function to be simply called by maintenance processes without having to check if config options are set. RETURN; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(v_parent_table, '.', 1)::name AND tablename = split_part(v_parent_table, '.', 2)::name; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE CONSTRAINT: %s', v_parent_table)); ELSE v_job_id = p_job_id; END IF; END IF; -- If p_child_table is null, figure out the partition that is the one right before the optimize_constraint value backwards. IF p_child_table IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying additional constraints: Automatically determining most recent child on which to apply constraints'); END IF; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(v_parent_table, 'DESC') LIMIT 1; IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN SELECT child_start_time INTO v_last_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_partition_interval, v_parent_table); v_partition_suffix := to_char(v_last_partition_timestamp - (v_partition_interval::interval * (v_optimize_constraint + v_premake + 1) ), v_datetime_string); ELSIF v_control_type = 'id' THEN SELECT child_start_id INTO v_last_partition_id FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_partition_interval, v_parent_table); v_partition_suffix := (v_last_partition_id - (v_partition_interval::int * (v_optimize_constraint + v_premake + 1) ))::text; END IF; v_child_tablename := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); IF p_debug THEN RAISE NOTICE 'apply_constraint: v_parent_tablename: % , v_partition_suffix: %', v_parent_tablename, v_partition_suffix; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Target child table: %s.%s', v_parent_schema, v_child_tablename)); END IF; ELSE v_child_tablename = split_part(p_child_table, '.', 2); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying additional constraints: Checking if target child table exists'); END IF; SELECT tablename FROM pg_catalog.pg_tables INTO v_child_exists WHERE schemaname = v_parent_schema::name AND tablename = v_child_tablename::name; IF v_child_exists IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Target child table (%s) does not exist. Skipping constraint creation.', v_child_tablename)); IF p_job_id IS NULL THEN PERFORM close_job(v_job_id); END IF; END IF; IF p_debug THEN RAISE NOTICE 'Target child table (%) does not exist. Skipping constraint creation.', v_child_tablename; END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; FOREACH v_col IN ARRAY v_constraint_cols LOOP SELECT con.conname INTO v_existing_constraint_name FROM pg_catalog.pg_constraint con JOIN pg_class c ON c.oid = con.conrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN pg_catalog.pg_attribute a ON con.conrelid = a.attrelid WHERE c.relname = v_child_tablename::name AND n.nspname = v_parent_schema::name AND con.conname LIKE 'partmanconstr_%' AND con.contype = 'c' AND a.attname = v_col::name AND ARRAY[a.attnum] OPERATOR(pg_catalog.<@) con.conkey AND a.attisdropped = false; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Applying additional constraints: Applying new constraint on column: %s', v_col)); END IF; IF v_existing_constraint_name IS NOT NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Partman managed constraint already exists on this table (%s) and column (%s). Skipping creation.', v_child_tablename, v_col)); END IF; IF p_debug THEN RAISE NOTICE 'Partman managed constraint already exists on this table (%) and column (%). Skipping creation.', v_child_tablename, v_col ; END IF; CONTINUE; END IF; -- Ensure column name gets put on end of constraint name to help avoid naming conflicts v_constraint_name := @extschema@.check_name_length('partmanconstr_'||v_child_tablename, p_suffix := '_'||v_col); EXECUTE format('SELECT min(%I)::text AS min, max(%I)::text AS max FROM %I.%I', v_col, v_col, v_parent_schema, v_child_tablename) INTO v_constraint_values; IF v_constraint_values IS NOT NULL THEN v_sql := format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I <= %L)' , v_parent_schema , v_child_tablename , v_constraint_name , v_col , v_constraint_values.min , v_col , v_constraint_values.max); IF p_debug THEN RAISE NOTICE 'Constraint creation query: %', v_sql; END IF; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('New constraint created: %s', v_sql)); END IF; ELSE IF p_debug THEN RAISE NOTICE 'Given column (%) contains all NULLs. No constraint created', v_col; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Given column (%s) contains all NULLs. No constraint created', v_col)); END IF; END IF; END LOOP; IF p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Applying additional constraints: Running analyze on partition set: %s', v_parent_table)); END IF; IF p_debug THEN RAISE NOTICE 'Running analyze on partition set: %', v_parent_table; END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); 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 CONSTRAINT: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE OR REPLACE FUNCTION apply_foreign_keys(p_parent_table text, p_child_table text, p_job_id bigint DEFAULT NULL, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_count int := 0; v_job_id bigint; v_jobmon text; v_jobmon_schema text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_ref_schema text; v_ref_table text; v_relkind char; v_row record; v_schemaname text; v_sql text; v_step_id bigint; v_tablename text; BEGIN /* * Apply foreign keys that exist on the given parent to the given child table */ SELECT jobmon INTO v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_catalog.pg_namespace n, pg_catalog.pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; IF v_jobmon_schema IS NOT NULL THEN SELECT current_setting('search_path') INTO v_old_search_path; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', '@extschema@,'||v_jobmon_schema, 'false'); END IF; END IF; SELECT n.nspname, c.relname, c.relkind INTO v_parent_schema, v_parent_tablename, v_relkind 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_relkind = 'p' THEN RAISE EXCEPTION 'This function cannot run on natively partitioned tables'; ELSIF v_relkind IS NULL THEN RAISE EXCEPTION 'Unable to find given table in system catalogs: %.%', v_parent_schema, v_parent_tablename; END IF; SELECT n.nspname, c.relname INTO v_schemaname, v_tablename FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = split_part(p_child_table, '.', 1)::name AND c.relname = split_part(p_child_table, '.', 2)::name; IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN APPLYING FOREIGN KEYS: %s', p_parent_table)); ELSE -- Don't create a new job, add steps into given job v_job_id := p_job_id; END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Applying foreign keys to %s if they exist on parent', p_child_table)); END IF; IF v_tablename IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'CRITICAL', format('Target child table (%s) does not exist.', p_child_table)); PERFORM fail_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; RAISE EXCEPTION 'Target child table (%) does not exist.', p_child_table; RETURN; END IF; FOR v_row 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 JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_parent_tablename AND n.nspname = v_parent_schema AND contype = 'f' LOOP v_sql := format('ALTER TABLE %I.%I ADD %s' , v_schemaname , v_tablename , v_row.constraint_def); IF p_debug THEN RAISE NOTICE 'Constraint creation query: %', v_sql; END IF; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'FK applied'); END IF; v_count := v_count + 1; END LOOP; IF v_count = 0 AND v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'No FKs found on parent'); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END IF; 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 APPLYING FOREIGN KEYS: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE OR REPLACE FUNCTION apply_privileges(p_parent_schema text, p_parent_tablename text, p_child_schema text, p_child_tablename text, p_job_id bigint DEFAULT NULL) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_child_grant record; v_child_owner text; v_grantees text[]; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_match boolean; v_parent_grant record; v_parent_owner text; v_revoke text; v_row_revoke record; v_sql text; v_step_id bigint; BEGIN /* * Apply privileges and ownership that exist on a given parent to the given child table */ SELECT jobmon INTO v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_schema ||'.'|| p_parent_tablename; IF v_jobmon IS NULL THEN RAISE EXCEPTION 'Given table is not managed by this extention: %.%', p_parent_schema, p_parent_tablename; END IF; 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 = p_parent_schema::name AND c.relname = p_parent_tablename::name; SELECT pg_get_userbyid(c.relowner) INTO v_child_owner 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_parent_owner IS NULL THEN RAISE EXCEPTION 'Given parent table does not exist: %.%', v_parent_schema, v_parent_tablename; END IF; IF v_child_owner IS NULL THEN RAISE EXCEPTION 'Given child table does not exist: %.%', v_child_schema, v_child_tablename; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; END IF; IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN EXECUTE format('SELECT %I.add_job(%L)', v_jobmon_schema, format('PARTMAN APPLYING PRIVILEGES TO CHILD TABLE: %s.%s', p_child_schema, p_child_tablename)) INTO v_job_id; ELSE v_job_id := p_job_id; END IF; EXECUTE format('SELECT %I.add_step(%L, %L)', v_jobmon_schema, v_job_id, format('Setting new child table privileges for %s.%s', p_child_schema, p_child_tablename)) INTO v_step_id; END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.update_step(%L, %L, %L)' , v_jobmon_schema , v_step_id , 'PENDING' , format('Applying privileges on child partition: %s.%s' , p_child_schema , p_child_tablename) ); END IF; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types , grantee FROM @extschema@.table_privs WHERE table_schema = p_parent_schema::name AND table_name = p_parent_tablename::name GROUP BY grantee LOOP -- Compare parent & child grants. Don't re-apply if it already exists v_match := false; v_sql := NULL; FOR v_child_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types , grantee FROM @extschema@.table_privs WHERE table_schema = p_child_schema::name AND table_name = p_child_tablename::name GROUP BY grantee LOOP IF v_parent_grant.types = v_child_grant.types AND v_parent_grant.grantee = v_child_grant.grantee THEN v_match := true; END IF; END LOOP; IF v_match = false THEN IF v_parent_grant.grantee = 'PUBLIC' THEN v_sql := 'GRANT %s ON %I.%I TO %s'; ELSE v_sql := 'GRANT %s ON %I.%I TO %I'; END IF; EXECUTE format(v_sql , array_to_string(v_parent_grant.types, ',') , p_child_schema , p_child_tablename , v_parent_grant.grantee); v_sql := NULL; SELECT string_agg(r, ',') INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN IF v_parent_grant.grantee = 'PUBLIC' THEN v_sql := 'REVOKE %s ON %I.%I FROM %s CASCADE'; ELSE v_sql := 'REVOKE %s ON %I.%I FROM %I CASCADE'; END IF; EXECUTE format(v_sql , v_revoke , p_child_schema , p_child_tablename , v_parent_grant.grantee); v_sql := NULL; END IF; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN FOR v_row_revoke IN SELECT role FROM ( SELECT DISTINCT grantee::text AS role FROM @extschema@.table_privs WHERE table_schema = p_child_schema::name AND table_name = p_child_tablename::name EXCEPT SELECT unnest(v_grantees)) x LOOP IF v_row_revoke.role IS NOT NULL THEN IF v_row_revoke.role = 'PUBLIC' THEN v_sql := 'REVOKE ALL ON %I.%I FROM %s'; ELSE v_sql := 'REVOKE ALL ON %I.%I FROM %I'; END IF; EXECUTE format(v_sql , p_child_schema , p_child_tablename , v_row_revoke.role); END IF; END LOOP; END IF; IF v_parent_owner <> v_child_owner THEN EXECUTE format('ALTER TABLE %I.%I OWNER TO %I' , p_child_schema , p_child_tablename , v_parent_owner); END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.update_step(%L, %L, %L)', v_jobmon_schema, v_step_id, 'OK', 'Done'); IF p_job_id IS NULL THEN EXECUTE format('SELECT %I.close_job(%L)', v_jobmon_schema, v_job_id); END IF; END IF; 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 RE-APPLYING PRIVILEGES TO ALL CHILD TABLES OF: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE FUNCTION check_control_type(p_parent_schema text, p_parent_tablename text, p_control text) RETURNS TABLE (general_type text, exact_type text) LANGUAGE sql STABLE SECURITY DEFINER AS $$ /* * Return column type for given table & column in that table * Returns NULL of objects don't exist */ SELECT CASE WHEN typname IN ('timestamptz', 'timestamp', 'date') THEN 'time' WHEN typname IN ('int2', 'int4', 'int8') THEN 'id' END , typname::text FROM pg_catalog.pg_type t JOIN pg_catalog.pg_attribute a ON t.oid = a.atttypid JOIN pg_catalog.pg_class c ON a.attrelid = c.oid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = p_parent_schema::name AND c.relname = p_parent_tablename::name AND a.attname = p_control::name $$; CREATE OR REPLACE FUNCTION 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 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 schema given if it doesn't require truncation * Given security definer since it's called by the trigger functions */ 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 FUNCTION @extschema@.check_subpart_sameconfig(p_parent_table text) RETURNS TABLE (sub_partition_type text , sub_control text , sub_partition_interval text , sub_constraint_cols text[] , sub_premake int , sub_inherit_fk boolean , sub_retention text , sub_retention_schema text , sub_retention_keep_table boolean , sub_retention_keep_index boolean , sub_automatic_maintenance text , sub_epoch text , sub_optimize_trigger int , sub_optimize_constraint int , sub_infinite_time_partitions boolean , sub_jobmon boolean , sub_trigger_exception_handling boolean , sub_upsert text , sub_trigger_return_null boolean) LANGUAGE sql STABLE SECURITY DEFINER SET search_path = @extschema@,pg_temp AS $$ /* * Check for consistent data in part_config_sub table. Was unable to get this working properly as either a constraint or trigger. * Would either delay raising an error until the next write (which I cannot predict) or disallow future edits to update a sub-partition set's configuration. * This is called by run_maintainance() and at least provides a consistent way to check that I know will run. * If anyone can get a working constraint/trigger, please help! */ WITH parent_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE n1.nspname = split_part(p_parent_table, '.', 1)::name AND c1.relname = split_part(p_parent_table, '.', 2)::name ) , child_tables AS ( SELECT n.nspname||'.'||c.relname AS tablename FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN parent_info pi ON h.inhparent = pi.oid ) -- Column order here must match the RETURNS TABLE definition SELECT DISTINCT a.sub_partition_type , a.sub_control , a.sub_partition_interval , a.sub_constraint_cols , a.sub_premake , a.sub_inherit_fk , a.sub_retention , a.sub_retention_schema , a.sub_retention_keep_table , a.sub_retention_keep_index , a.sub_automatic_maintenance , a.sub_epoch , a.sub_optimize_trigger , a.sub_optimize_constraint , a.sub_infinite_time_partitions , a.sub_jobmon , a.sub_trigger_exception_handling , a.sub_upsert , a.sub_trigger_return_null FROM @extschema@.part_config_sub a JOIN child_tables b on a.sub_parent = b.tablename; $$; CREATE OR REPLACE FUNCTION check_version(p_check_version text) RETURNS boolean LANGUAGE plpgsql STABLE AS $$ DECLARE v_check_version text[]; v_current_version text[] := string_to_array(current_setting('server_version'), '.'); BEGIN /* * Check PostgreSQL version number. Parameter must be full 3 point version if prior to 10.0. Otherwise 2 point version. * Returns true if current version is greater than or equal to the parameter given. * If running version is devel, alpha, beta or rc, the check always returns true. */ v_check_version := string_to_array(p_check_version, '.'); IF substring(v_current_version[1] from 'devel') IS NOT NULL THEN -- You're running a test version. You're on your own if things fail. RETURN true; END IF; IF v_current_version[1]::int > v_check_version[1]::int THEN RETURN true; END IF; IF v_current_version[1]::int = v_check_version[1]::int THEN IF substring(v_current_version[2] from 'beta') IS NOT NULL OR substring(v_current_version[2] from 'alpha') IS NOT NULL OR substring(v_current_version[2] from 'rc') IS NOT NULL THEN -- You're running a test version. You're on your own if things fail. RETURN true; END IF; IF v_current_version[2]::int > v_check_version[2]::int THEN RETURN true; END IF; IF v_current_version[2]::int = v_check_version[2]::int THEN IF array_length(v_current_version, 1) <= 2 THEN -- Account for reduction to 2 number version in 10.0 RETURN true; END IF; IF v_current_version[3]::int >= v_check_version[3]::int THEN RETURN true; END IF; -- 0.0.x END IF; -- 0.x.0 END IF; -- x.0.0 RETURN false; END $$; CREATE OR REPLACE FUNCTION check_subpartition_limits(p_parent_table text, p_type text, OUT sub_min text, OUT sub_max text) RETURNS record LANGUAGE plpgsql AS $$ DECLARE v_parent_schema text; v_parent_tablename text; v_top_control text; v_top_control_type text; v_top_epoch text; v_top_interval text; v_top_schema text; v_top_tablename text; BEGIN /* * Check if parent table is a subpartition of an already existing partition set managed by pg_partman * If so, return the limits of what child tables can be created under the given parent table based on its own suffix * If not, return NULL. Allows caller to check for NULL and then know if the given parent has sub-partition limits. */ SELECT n.nspname, c.relname INTO v_parent_schema, v_parent_tablename 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; WITH top_oid AS ( SELECT i.inhparent AS top_parent_oid FROM pg_catalog.pg_class c JOIN pg_catalog.pg_inherits i ON c.oid = i.inhrelid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = v_parent_schema AND c.relname = v_parent_tablename ) SELECT n.nspname, c.relname, p.partition_interval, p.control, p.epoch INTO v_top_schema, v_top_tablename, v_top_interval, v_top_control, v_top_epoch FROM pg_catalog.pg_class c JOIN top_oid t ON c.oid = t.top_parent_oid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace JOIN @extschema@.part_config p ON p.parent_table = n.nspname||'.'||c.relname WHERE c.oid = t.top_parent_oid; SELECT general_type INTO v_top_control_type FROM @extschema@.check_control_type(v_top_schema, v_top_tablename, v_top_control); IF v_top_control_type = 'id' AND v_top_epoch <> 'none' THEN v_top_control_type := 'time'; END IF; -- If sub-partition is different type than top parent, no need to set limits IF p_type = v_top_control_type THEN IF p_type = 'time' THEN SELECT child_start_time::text, child_end_time::text INTO sub_min, sub_max FROM @extschema@.show_partition_info(p_parent_table, v_top_interval, v_top_schema||'.'||v_top_tablename); ELSIF p_type = 'id' THEN SELECT child_start_id::text, child_end_id::text INTO sub_min, sub_max FROM @extschema@.show_partition_info(p_parent_table, v_top_interval, v_top_schema||'.'||v_top_tablename); ELSE RAISE EXCEPTION 'Reached unknown state in check_subpartition_limits(). Please report what lead to this condition to author'; END IF; END IF; RETURN; END $$; CREATE FUNCTION create_function_id(p_parent_table text, p_job_id bigint DEFAULT NULL) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_control text; v_control_type text; v_count int; v_current_partition_name text; v_current_partition_id bigint; v_datetime_string text; v_final_partition_id bigint; v_function_name 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_position int; v_job_id bigint; v_jobmon text; v_jobmon_schema text; v_last_partition text; v_max bigint; v_next_partition_id bigint; v_next_partition_name text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_optimize_trigger int; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_premake int; v_prev_partition_id bigint; v_prev_partition_name text; v_relkind char; v_row_max_id record; v_run_maint text; v_step_id bigint; v_top_parent text := p_parent_table; v_trig_func text; v_trigger_exception_handling boolean; v_trigger_return_null boolean; v_upsert text; BEGIN /* * Create the trigger function for the parent table of an id-based partition set */ SELECT partition_interval::bigint , control , premake , optimize_trigger , automatic_maintenance , jobmon , trigger_exception_handling , upsert , trigger_return_null INTO v_partition_interval , v_control , v_premake , v_optimize_trigger , v_run_maint , v_jobmon , v_trigger_exception_handling , v_upsert , v_trigger_return_null FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'partman'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no non-native pg_partman config found for %', p_parent_table; END IF; SELECT n.nspname, c.relname, c.relkind INTO v_parent_schema, v_parent_tablename, v_relkind 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_relkind = 'p' THEN RAISE EXCEPTION 'This function cannot run on natively partitioned tables'; ELSIF v_relkind IS NULL THEN RAISE EXCEPTION 'Unable to find given table in system catalogs: %.%', v_parent_schema, v_parent_tablename; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run create_function_id on partition set without id based control column. Found: %', v_control_type; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE FUNCTION: %s', p_parent_table)); ELSE v_job_id = p_job_id; END IF; v_step_id := add_step(v_job_id, format('Creating partition function for table %s', p_parent_table)); END IF; -- Get the highest level top parent if multi-level partitioned in order to get proper max() value below 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 INTO v_higher_parent_schema, v_higher_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 WHERE p.partition_type = 'id'; IF v_higher_parent_table IS NOT NULL THEN -- initially set in DECLARE v_top_parent := v_higher_parent_schema||'.'||v_higher_parent_table; END IF; END LOOP; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_top_parent, 'DESC') LOOP EXECUTE format('SELECT max(%I) FROM %I.%I', v_control, v_row_max_id.partition_schemaname, v_row_max_id.partition_tablename) INTO v_max; IF v_max IS NOT NULL THEN EXIT; END IF; END LOOP; IF v_max IS NULL THEN v_max := 0; END IF; v_current_partition_id = v_max - (v_max % v_partition_interval); v_next_partition_id := v_current_partition_id + v_partition_interval; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_current_partition_id::text, TRUE); v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_current_partition_id bigint; v_current_partition_name text; v_id_position int; v_last_partition text := %L; v_next_partition_id bigint; v_next_partition_name text; v_partition_created boolean; BEGIN IF TG_OP = ''INSERT'' THEN IF NEW.%I >= %s AND NEW.%I < %s THEN ' , v_parent_schema , v_function_name , v_last_partition , v_control , v_current_partition_id , v_control , v_next_partition_id ); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_current_partition_name::name; IF v_count > 0 THEN v_trig_func := v_trig_func || format(' INSERT INTO %I.%I VALUES (NEW.*) %s; ', v_parent_schema, v_current_partition_name, v_upsert); ELSE v_trig_func := v_trig_func || ' -- Child table for current values does not exist in this partition set, so write to parent RETURN NEW;'; END IF; FOR i IN 1..v_optimize_trigger LOOP v_prev_partition_id := v_current_partition_id - (v_partition_interval * i); v_next_partition_id := v_current_partition_id + (v_partition_interval * i); v_final_partition_id := v_next_partition_id + v_partition_interval; v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, v_prev_partition_id::text, TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, v_next_partition_id::text, TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles optimize_trigger being larger than premake (to go back in time further) and edge case of changing optimize_trigger immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_prev_partition_name::name; IF v_count > 0 THEN -- Only handle previous partitions if they're starting above zero IF v_prev_partition_id >= 0 THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %s AND NEW.%I < %s THEN INSERT INTO %I.%I VALUES (NEW.*) %s; ' , v_control , v_prev_partition_id , v_control , v_prev_partition_id + v_partition_interval , v_parent_schema , v_prev_partition_name , v_upsert ); END IF; END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_next_partition_name::name; IF v_count > 0 THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %s AND NEW.%I < %s THEN INSERT INTO %I.%I VALUES (NEW.*) %s;' , v_control , v_next_partition_id , v_control , v_final_partition_id , v_parent_schema , v_next_partition_name , v_upsert ); END IF; END LOOP; v_trig_func := v_trig_func ||format(' ELSE v_current_partition_id := NEW.%I - (NEW.%I %% %s); v_current_partition_name := @extschema@.check_name_length(%L, v_current_partition_id::text, TRUE); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = %L::name AND tablename = v_current_partition_name::name; IF v_count > 0 THEN EXECUTE format(''INSERT INTO %%I.%%I VALUES($1.*) %s'', %L, v_current_partition_name) USING NEW; ELSE RETURN NEW; END IF; END IF;' , v_control , v_control , v_partition_interval , v_parent_tablename , v_parent_schema , v_upsert , v_parent_schema ); v_trig_func := v_trig_func ||' END IF;'; IF v_trigger_return_null IS TRUE THEN v_trig_func := v_trig_func ||' RETURN NULL;'; ELSE v_trig_func := v_trig_func ||' RETURN NEW;'; END IF; IF v_trigger_exception_handling THEN v_trig_func := v_trig_func ||' EXCEPTION WHEN OTHERS THEN RAISE WARNING ''pg_partman insert into child table failed, row inserted into parent (%.%). ERROR: %'', TG_TABLE_SCHEMA, TG_TABLE_NAME, COALESCE(SQLERRM, ''unknown''); RETURN NEW;'; END IF; v_trig_func := v_trig_func ||' END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for current id interval: %s to %s', v_current_partition_id, v_final_partition_id-1)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); 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 FUNCTION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''Partition function maintenance for table %s 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 $$; CREATE OR REPLACE FUNCTION create_function_time(p_parent_table text, p_job_id bigint DEFAULT NULL) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_control text; v_control_type text; v_count int; v_current_partition_name text; v_current_partition_timestamp timestamptz; v_datetime_string text; v_epoch text; v_final_partition_timestamp timestamptz; v_function_name text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_new_length int; v_next_partition_name text; v_next_partition_timestamp timestamptz; v_parent_schema text; v_parent_tablename text; v_partition_expression text; v_partition_interval interval; v_prev_partition_name text; v_prev_partition_timestamp timestamptz; v_relkind char; v_step_id bigint; v_trig_func text; v_optimize_trigger int; v_trigger_exception_handling boolean; v_trigger_return_null boolean; v_type text; v_upsert text; BEGIN /* * Create the trigger function for the parent table of a time-based partition set */ SELECT partition_type , partition_interval::interval , epoch , control , optimize_trigger , datetime_string , jobmon , trigger_exception_handling , upsert , trigger_return_null INTO v_type , v_partition_interval , v_epoch , v_control , v_optimize_trigger , v_datetime_string , v_jobmon , v_trigger_exception_handling , v_upsert , v_trigger_return_null FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'partman' OR partition_type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no non-native pg_partman config found for %', p_parent_table; END IF; SELECT n.nspname, c.relname, c.relkind INTO v_parent_schema, v_parent_tablename, v_relkind 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_relkind = 'p' THEN RAISE EXCEPTION 'This function cannot run on natively partitioned tables'; ELSIF v_relkind IS NULL THEN RAISE EXCEPTION 'Unable to find given table in system catalogs: %.%', v_parent_schema, v_parent_tablename; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'time' THEN IF (v_control_type = 'id' AND v_epoch = 'none') OR v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run on partition set without time based control column or epoch flag set with an id column. Found control: %, epoch: %', v_control_type, v_epoch; END IF; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE FUNCTION: %s', p_parent_table)); ELSE v_job_id = p_job_id; END IF; v_step_id := add_step(v_job_id, format('Creating partition function for table %s', p_parent_table)); END IF; v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(NEW.%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((NEW.%I/1000)::float)', v_control) ELSE format('NEW.%I', v_control) END; IF v_type = 'partman' THEN v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_partition_name text; v_partition_timestamp timestamptz; BEGIN IF TG_OP = ''INSERT'' THEN ' , v_parent_schema , v_function_name); CASE WHEN v_partition_interval = '15 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', %s) + ''15min''::interval * floor(date_part(''minute'', %1$s) / 15.0);' , v_partition_expression); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '15min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', %s) + ''30min''::interval * floor(date_part(''minute'', %1$s) / 30.0);' , v_partition_expression); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP) + '30min'::interval * floor(date_part('minute', CURRENT_TIMESTAMP) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', %s);', v_partition_expression); v_current_partition_timestamp := date_trunc('hour', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 day' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''day'', %s);', v_partition_expression); v_current_partition_timestamp := date_trunc('day', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 week' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''week'', %s);', v_partition_expression); v_current_partition_timestamp := date_trunc('week', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 month' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''month'', %s);', v_partition_expression); v_current_partition_timestamp := date_trunc('month', CURRENT_TIMESTAMP); WHEN v_partition_interval = '3 months' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''quarter'', %s);', v_partition_expression); v_current_partition_timestamp := date_trunc('quarter', CURRENT_TIMESTAMP); WHEN v_partition_interval = '1 year' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''year'', %s);', v_partition_expression); v_current_partition_timestamp := date_trunc('year', CURRENT_TIMESTAMP); END CASE; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, to_char(v_current_partition_timestamp, v_datetime_string), TRUE); v_next_partition_timestamp := v_current_partition_timestamp + v_partition_interval::interval; v_trig_func := v_trig_func ||format(' IF %s >= %L AND %1$s < %3$L THEN ' , v_partition_expression , v_current_partition_timestamp , v_next_partition_timestamp); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_current_partition_name::name; IF v_count > 0 THEN v_trig_func := v_trig_func || format(' INSERT INTO %I.%I VALUES (NEW.*) %s; ', v_parent_schema, v_current_partition_name, v_upsert); ELSE v_trig_func := v_trig_func || ' -- Child table for current values does not exist in this partition set, so write to parent RETURN NEW;'; END IF; FOR i IN 1..v_optimize_trigger LOOP v_prev_partition_timestamp := v_current_partition_timestamp - (v_partition_interval::interval * i); v_next_partition_timestamp := v_current_partition_timestamp + (v_partition_interval::interval * i); v_final_partition_timestamp := v_next_partition_timestamp + (v_partition_interval::interval); v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, to_char(v_prev_partition_timestamp, v_datetime_string), TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, to_char(v_next_partition_timestamp, v_datetime_string), TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles optimize_trigger being larger than premake (to go back in time further) and edge case of changing optimize_trigger immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_prev_partition_name::name; IF v_count > 0 THEN v_trig_func := v_trig_func ||format(' ELSIF %s >= %L AND %1$s < %3$L THEN INSERT INTO %I.%I VALUES (NEW.*) %s;' , v_partition_expression , v_prev_partition_timestamp , v_prev_partition_timestamp + v_partition_interval::interval , v_parent_schema , v_prev_partition_name , v_upsert); END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_next_partition_name::name; IF v_count > 0 THEN v_trig_func := v_trig_func ||format(' ELSIF %s >= %L AND %1$s < %3$L THEN INSERT INTO %I.%I VALUES (NEW.*) %s;' , v_partition_expression , v_next_partition_timestamp , v_final_partition_timestamp , v_parent_schema , v_next_partition_name , v_upsert); END IF; END LOOP; v_trig_func := v_trig_func||format(' ELSE v_partition_name := @extschema@.check_name_length(%L, to_char(v_partition_timestamp, %L), TRUE); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = %L::name AND tablename = v_partition_name::name; IF v_count > 0 THEN EXECUTE format(''INSERT INTO %%I.%%I VALUES($1.*) %s'', %L, v_partition_name) USING NEW; ELSE RETURN NEW; END IF; END IF;' , v_parent_tablename , v_datetime_string , v_parent_schema , v_upsert , v_parent_schema); v_trig_func := v_trig_func ||' END IF;'; IF v_trigger_return_null IS TRUE THEN v_trig_func := v_trig_func ||' RETURN NULL;'; ELSE v_trig_func := v_trig_func ||' RETURN NEW;'; END IF; IF v_trigger_exception_handling THEN v_trig_func := v_trig_func ||' EXCEPTION WHEN OTHERS THEN RAISE WARNING ''pg_partman insert into child table failed, row inserted into parent (%.%). ERROR: %'', TG_TABLE_SCHEMA, TG_TABLE_NAME, COALESCE(SQLERRM, ''unknown''); RETURN NEW;'; END IF; v_trig_func := v_trig_func ||' END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for current time interval: %s to %s' , v_current_partition_timestamp , v_final_partition_timestamp-'1sec'::interval)); END IF; ELSIF v_type = 'time-custom' THEN v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_child_schemaname text; v_child_table text; v_child_tablename text; v_upsert text; BEGIN ' , v_parent_schema , v_function_name); v_trig_func := v_trig_func || format(' SELECT c.child_table, p.upsert INTO v_child_table, v_upsert FROM @extschema@.custom_time_partitions c JOIN @extschema@.part_config p ON c.parent_table = p.parent_table WHERE c.partition_range @> %s AND c.parent_table = %L;' , v_partition_expression , v_parent_schema||'.'||v_parent_tablename); v_trig_func := v_trig_func || ' SELECT schemaname, tablename INTO v_child_schemaname, v_child_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(v_child_table, ''.'', 1)::name AND tablename = split_part(v_child_table, ''.'', 2)::name; IF v_child_schemaname IS NOT NULL AND v_child_tablename IS NOT NULL THEN EXECUTE format(''INSERT INTO %I.%I VALUES ($1.*) %s'', v_child_schemaname, v_child_tablename, v_upsert) USING NEW; ELSE RETURN NEW; END IF;'; IF v_trigger_return_null IS TRUE THEN v_trig_func := v_trig_func ||' RETURN NULL;'; ELSE v_trig_func := v_trig_func ||' RETURN NEW;'; END IF; IF v_trigger_exception_handling THEN v_trig_func := v_trig_func ||' EXCEPTION WHEN OTHERS THEN RAISE WARNING ''pg_partman insert into child table failed, row inserted into parent (%.%). ERROR: %'', TG_TABLE_SCHEMA, TG_TABLE_NAME, COALESCE(SQLERRM, ''unknown''); RETURN NEW;'; END IF; v_trig_func := v_trig_func ||' END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for custom time table: %s', p_parent_table)); END IF; ELSE RAISE EXCEPTION 'ERROR: Invalid time partitioning type given: %', v_type; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); 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 FUNCTION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''Partition function maintenance for table %s 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 $$; CREATE FUNCTION 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_trigger_return_null boolean DEFAULT true , p_jobmon boolean DEFAULT true , p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER 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_higher_control_type text; v_higher_parent_control 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_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_partition_id bigint; v_parent_partition_timestamp timestamptz; v_parent_schema text; v_parent_tablename 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_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_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); 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 @extschema@.check_version('9.5.0') = 'false' 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 INTO v_parent_schema, v_parent_tablename 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_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 (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 NOT @extschema@.check_version('10.0') 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 ... PARITIONED 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; ELSE IF @extschema@.check_version('10.0') 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; 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 ) 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 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) 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); 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) 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_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 INTO v_higher_parent_schema, v_higher_parent_table, v_higher_parent_control 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' 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) 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_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' 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 $$; CREATE OR REPLACE FUNCTION create_partition_id(p_parent_table text, p_partition_ids bigint[], p_analyze boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_control_type text; v_exists text; v_grantees text[]; v_hasoids boolean; v_id bigint; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_grant record; v_parent_schema text; v_parent_tablename text; v_parent_tablespace text; v_partition_interval bigint; v_partition_created boolean := false; v_partition_name text; v_partition_type text; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_sub_control text; v_sub_partition_type text; v_sub_id_max bigint; v_sub_id_min bigint; v_unlogged char; BEGIN /* * Function to create id partitions */ SELECT control , partition_type , partition_interval , inherit_fk , jobmon INTO v_control , v_partition_type , v_partition_interval , v_inherit_fk , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT n.nspname, c.relname, t.spcname INTO v_parent_schema, v_parent_tablename, v_parent_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 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; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'id' THEN RAISE EXCEPTION 'ERROR: Given parent table is not set up for id/serial partitioning'; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'id'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; FOREACH v_id IN ARRAY p_partition_ids LOOP -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_id_min IS NOT NULL THEN IF v_id < v_sub_id_min OR v_id > v_sub_id_max THEN CONTINUE; END IF; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_id::text, TRUE); -- If child table already exists, skip creation -- Have to check pg_class because if subpartitioned, table will not be in pg_tables SELECT c.relname INTO v_exists 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_partition_name::name; IF v_exists IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_id||' to '||(v_id + v_partition_interval)-1); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c 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; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; -- Close parentheses on LIKE are below due to differing requirements of native subpartitioning v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING STORAGE INCLUDING COMMENTS ' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); SELECT sub_partition_type, sub_control INTO v_sub_partition_type, v_sub_control FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; IF v_sub_partition_type = 'native' THEN -- Cannot include indexes since they cannot exist on native parents v_sql := v_sql || format(') PARTITION BY RANGE (%I) ', v_sub_control); ELSE v_sql := v_sql || format(' INCLUDING INDEXES) ', v_sub_control); END IF; SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c 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; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; IF v_partition_type = 'native' THEN EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , v_id , v_id + v_partition_interval); ELSE EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %s AND %I < %s )' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_control , v_id , v_control , v_id + v_partition_interval); EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I', v_parent_schema, v_partition_name, v_parent_schema, v_parent_tablename); -- Indexes cannot be created on the parent, so clustering cannot be used for native yet. PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); -- Foreign keys to other tables not supported in native IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; END IF; -- NOTE: Privileges currently not automatically inherited for native PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_automatic_maintenance , sub_infinite_time_partitions , sub_jobmon , sub_trigger_exception_handling FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Subpartitioning '||v_partition_name); END IF; 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_inherit_fk := %L , p_epoch := %L , p_jobmon := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_automatic_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions , trigger_exception_handling = v_row.sub_trigger_exception_handling WHERE parent_table = v_parent_schema||'.'||v_partition_name; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; -- end sub partitioning LOOP -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table, p_job_id := v_job_id, p_debug := p_debug); v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s', p_parent_table)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE OR REPLACE FUNCTION create_partition_time(p_parent_table text, p_partition_times timestamptz[], p_analyze boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_control_type text; v_datetime_string text; v_epoch text; v_exists smallint; v_grantees text[]; v_hasoids boolean; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_grant record; v_parent_schema text; v_parent_tablename text; v_part_col text; v_partition_created boolean := false; v_partition_name text; v_partition_suffix text; v_parent_tablespace text; v_partition_expression text; v_partition_interval interval; v_partition_timestamp_end timestamptz; v_partition_timestamp_start timestamptz; v_quarter text; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_sub_control text; v_sub_parent text; v_sub_partition_type text; v_sub_timestamp_max timestamptz; v_sub_timestamp_min timestamptz; v_trunc_value text; v_time timestamptz; v_partition_type text; v_unlogged char; v_year text; BEGIN /* * Function to create a child table in a time-based partition set */ SELECT c.partition_type , c.control , c.partition_interval , c.epoch , c.inherit_fk , c.jobmon , c.datetime_string INTO v_partition_type , v_control , v_partition_interval , v_epoch , v_inherit_fk , v_jobmon , v_datetime_string FROM @extschema@.part_config c WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT n.nspname, c.relname, t.spcname INTO v_parent_schema, v_parent_tablename, v_parent_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 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; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'time' THEN IF (v_control_type = 'id' AND v_epoch = 'none') OR v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run on partition set without time based control column or epoch flag set with an id column. Found control: %, epoch: %', v_control_type, v_epoch; END IF; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::timestamptz, sub_max::timestamptz INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'time'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) END; IF p_debug THEN RAISE NOTICE 'create_partition_time: v_partition_expression: %', v_partition_expression; END IF; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_timestamp_start := v_time; BEGIN v_partition_timestamp_end := v_time + v_partition_interval; 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_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_time||' skipped'); CONTINUE; END; -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_timestamp_min IS NOT NULL THEN IF v_time < v_sub_timestamp_min OR v_time > v_sub_timestamp_max THEN CONTINUE; END IF; END IF; -- This suffix generation code is in partition_data_time() as well v_partition_suffix := to_char(v_time, v_datetime_string); v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); -- Check if child exists. SELECT count(*) INTO v_exists 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_partition_name::name; IF v_exists > 0 THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Creating new partition %s.%s with interval from %s to %s' , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end-'1sec'::interval)); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c 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; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; -- Close parentheses on LIKE are below due to differing requirements of native subpartitioning v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING STORAGE INCLUDING COMMENTS ' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); SELECT sub_partition_type, sub_control INTO v_sub_partition_type, v_sub_control FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; IF v_sub_partition_type = 'native' THEN -- Cannot include indexes since they cannot exist on native parents v_sql := v_sql || format(') PARTITION BY RANGE (%I) ', v_sub_control); ELSE v_sql := v_sql || format(' INCLUDING INDEXES) ', v_sub_control); END IF; SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c 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; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; IF p_debug THEN RAISE NOTICE 'create_partition_time v_sql: %', v_sql; END IF; EXECUTE v_sql; IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; IF v_partition_type = 'native' THEN IF v_epoch = 'none' THEN -- Attach with normal, time-based values for native constraint EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end); ELSE -- Must attach with integer based values for native constraint and epoch IF v_epoch = 'seconds' THEN EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , EXTRACT('epoch' FROM v_partition_timestamp_start) , EXTRACT('epoch' FROM v_partition_timestamp_end)); ELSIF v_epoch = 'milliseconds' THEN EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , EXTRACT('epoch' FROM v_partition_timestamp_start) * 1000 , EXTRACT('epoch' FROM v_partition_timestamp_end) * 1000); END IF; -- Create secondary, time-based constraint since native's constraint is already integer based EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%s >= %L AND %4$s < %6$L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_partition_expression , v_partition_timestamp_start , v_partition_timestamp_end); END IF; ELSE -- Non-native always gets time-based constraint EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%s >= %L AND %4$s < %6$L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_partition_expression , v_partition_timestamp_start , v_partition_timestamp_end); IF v_epoch = 'seconds' THEN -- Non-native needs secondary, integer based constraint for epoch EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_int_check' , v_control , EXTRACT('epoch' from v_partition_timestamp_start) , v_control , EXTRACT('epoch' from v_partition_timestamp_end) ); ELSIF v_epoch = 'milliseconds' THEN EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_int_check' , v_control , EXTRACT('epoch' from v_partition_timestamp_start) * 1000 , v_control , EXTRACT('epoch' from v_partition_timestamp_end) * 1000); END IF; EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); -- If custom time, set extra config options. IF v_partition_type = 'time-custom' THEN INSERT INTO @extschema@.custom_time_partitions (parent_table, child_table, partition_range) VALUES ( p_parent_table, v_parent_schema||'.'||v_partition_name, tstzrange(v_partition_timestamp_start, v_partition_timestamp_end, '[)') ); END IF; -- Indexes cannot be created on the parent, so clustering cannot be used for native yet. PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); -- Foreign keys to other tables not supported in native IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; END IF; -- end native check -- NOTE: Privileges currently not automatically inherited for native PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_automatic_maintenance , sub_infinite_time_partitions , sub_jobmon , sub_trigger_exception_handling FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Subpartitioning %s.%s', v_parent_schema, v_partition_name)); END IF; 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_inherit_fk := %L , p_epoch := %L , p_jobmon := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_automatic_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_jobmon); IF p_debug THEN RAISE NOTICE 'create_partition_time (create_parent loop): %', v_sql; END IF; EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions , trigger_exception_handling = v_row.sub_trigger_exception_handling WHERE parent_table = v_parent_schema||'.'||v_partition_name; END LOOP; -- end sub partitioning LOOP -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table, p_job_id := v_job_id, p_debug := p_debug); v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s. Attempted intervals: %s', p_parent_table, p_partition_times)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; 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_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE OR REPLACE FUNCTION 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 , p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER 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_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 INTO v_parent_type, v_parent_interval, v_control, v_run_maint, v_parent_epoch 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 @extschema@.check_version('9.5.0') = 'false' 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 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) 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); 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' 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); 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); 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_child_schema::name AND c.relname = v_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_jobmon := %L , p_debug := %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 , p_jobmon , p_debug); 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 create_trigger(p_parent_table text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_function_name text; v_new_length int; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_relkind char; v_trig_name text; v_trig_sql text; BEGIN /* * Function to create partitioning trigger on parent table */ 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'); SELECT n.nspname, c.relname, c.relkind INTO v_parent_schema, v_parent_tablename, v_relkind 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_relkind = 'p' THEN RAISE EXCEPTION 'This function cannot run on natively partitioned tables'; ELSIF v_relkind IS NULL THEN RAISE EXCEPTION 'Unable to find given table in system catalogs: %.%', v_parent_schema, v_parent_tablename; END IF; v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); -- Ensure function name matches the naming pattern v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); v_trig_sql := format('CREATE TRIGGER %I BEFORE INSERT ON %I.%I FOR EACH ROW EXECUTE PROCEDURE %I.%I()' , v_trig_name , v_parent_schema , v_parent_tablename , v_parent_schema , v_function_name); EXECUTE v_trig_sql; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END $$; CREATE OR REPLACE FUNCTION drop_partition_id(p_parent_table text, p_retention bigint DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_control text; v_control_type text; v_drop_count int := 0; v_index record; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_max bigint; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_partition_id bigint; v_partition_type text; v_retention bigint; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_row record; v_row_max_id record; v_step_id bigint; BEGIN /* * Function to drop child tables from an id-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman drop_partition_id')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_id already running.'; RETURN 0; END IF; IF p_retention IS NULL THEN SELECT partition_interval::bigint , partition_type , control , retention::bigint , retention_keep_table , retention_keep_index , retention_schema , jobmon INTO v_partition_interval , v_partition_type , v_control , v_retention , v_retention_keep_table , v_retention_keep_index , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND retention IS NOT NULL; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE -- Allow override of configuration options SELECT partition_interval::bigint , partition_type , control , retention_keep_table , retention_keep_index , retention_schema , jobmon INTO v_partition_interval , v_partition_type , v_control , v_retention_keep_table , v_retention_keep_index , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; v_retention := p_retention; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'id' THEN RAISE EXCEPTION 'Data type of control column in given partition set is not an integer type'; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%I) FROM %I.%I', v_control, v_row_max_id.partition_schemaname, v_row_max_id.partition_tablename) INTO v_max; IF v_max IS NOT NULL THEN EXIT; END IF; END LOOP; -- Loop through child tables of the given parent FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'ASC') LOOP SELECT child_start_id INTO v_partition_id FROM @extschema@.show_partition_info(v_row.partition_schemaname||'.'||v_row.partition_tablename , v_partition_interval::text , p_parent_table); -- Add one interval since partition names contain the start of the constraint period IF v_retention <= (v_max - (v_partition_id + v_partition_interval)) THEN -- Only create a jobmon entry if there's actual retention work done IF v_jobmon_schema IS NOT NULL AND v_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN DROP ID PARTITION: %s', p_parent_table)); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Detach/Uninherit table %s.%s from %s', v_row.partition_schemaname, v_row.partition_tablename, p_parent_table)); END IF; IF v_partition_type = 'native' THEN EXECUTE format('ALTER TABLE %I.%I DETACH PARTITION %I.%I' , v_parent_schema , v_parent_tablename , v_row.partition_schemaname , v_row.partition_tablename); ELSE EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_row.partition_schemaname , v_row.partition_tablename , v_parent_schema , v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop table %s.%s', v_row.partition_schemaname, v_row.partition_tablename)); END IF; EXECUTE format('DROP TABLE %I.%I CASCADE', v_row.partition_schemaname, v_row.partition_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN FOR v_index IN WITH child_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE c1.relname = v_row.partition_tablename::name AND n1.nspname = v_row.partition_schema::name ) SELECT c.relname as name , con.conname FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid LEFT JOIN pg_catalog.pg_constraint con ON i.indexrelid = con.conindid JOIN child_info ON i.indrelid = child_info.oid LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop index %s from %s.%s' , v_index.name , v_row.partition_schemaname , v_row.partition_tablename)); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I DROP CONSTRAINT %I', v_row.partition_schemaname, v_row.partition_tablename, v_index.conname); ELSE EXECUTE format('DROP INDEX %I.%I', v_row.partition_schemaname, v_index.name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Moving table %s.%s to schema %s' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema)); END IF; EXECUTE format('ALTER TABLE %I.%I SET SCHEMA %I' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if -- If child table is a subpartition, remove it from part_config & part_config_sub (should cascade due to FK) DELETE FROM @extschema@.part_config WHERE parent_table = v_row.partition_schemaname ||'.'||v_row.partition_tablename; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', format('%s partitions dropped.', v_drop_count)); 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_drop_count; 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 DROP ID PARTITION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE OR REPLACE FUNCTION drop_partition_time(p_parent_table text, p_retention interval DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_control text; v_control_type text; v_datetime_string text; v_drop_count int := 0; v_epoch text; v_index record; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_partition_timestamp timestamptz; v_partition_type text; v_retention interval; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_row record; v_step_id bigint; BEGIN /* * Function to drop child tables from a time-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman drop_partition_time')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_time already running.'; RETURN 0; END IF; -- Allow override of configuration options IF p_retention IS NULL THEN SELECT partition_type , control , partition_interval::interval , epoch , retention::interval , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_partition_type , v_control , v_partition_interval , v_epoch , v_retention , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND retention IS NOT NULL; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE SELECT partition_type , partition_interval::interval , epoch , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_partition_type , v_partition_interval , v_epoch , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; v_retention := p_retention; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'time' THEN IF (v_control_type = 'id' AND v_epoch = 'none') OR v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run on partition set without time based control column or epoch flag set with an id column. Found control: %, epoch: %', v_control_type, v_epoch; END IF; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- Loop through child tables of the given parent FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'DESC') LOOP -- pull out datetime portion of partition's tablename to make the next one SELECT child_start_time INTO v_partition_timestamp FROM @extschema@.show_partition_info(v_row.partition_schemaname||'.'||v_row.partition_tablename , v_partition_interval::text , p_parent_table); -- Add one interval since partition names contain the start of the constraint period IF v_retention < (CURRENT_TIMESTAMP - (v_partition_timestamp + v_partition_interval)) THEN -- Only create a jobmon entry if there's actual retention work done IF v_jobmon_schema IS NOT NULL AND v_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN DROP TIME PARTITION: %s', p_parent_table)); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Detach/Uninherit table %s.%s from %s' , v_row.partition_schemaname , v_row.partition_tablename , p_parent_table)); END IF; IF v_partition_type = 'native' THEN EXECUTE format('ALTER TABLE %I.%I DETACH PARTITION %I.%I' , v_parent_schema , v_parent_tablename , v_row.partition_schemaname , v_row.partition_tablename); ELSE EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_row.partition_schemaname , v_row.partition_tablename , v_parent_schema , v_parent_tablename); END IF; IF v_partition_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_row.partition_schemaname||'.'||v_row.partition_tablename; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop table %s.%s', v_row.partition_schemaname, v_row.partition_tablename)); END IF; EXECUTE format('DROP TABLE %I.%I CASCADE', v_row.partition_schemaname, v_row.partition_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN FOR v_index IN WITH child_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE c1.relname = v_row.partition_tablename::name AND n1.nspname = v_row.partition_schemaname::name ) SELECT c.relname as name , con.conname FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid LEFT JOIN pg_catalog.pg_constraint con ON i.indexrelid = con.conindid JOIN child_info ON i.indrelid = child_info.oid LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop index %s from %s.%s' , v_index.name , v_row.partition_schemaname , v_row.partition_tablename)); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I DROP CONSTRAINT %I' , v_row.partition_schemaname , v_row.partition_tablename , v_index.conname); ELSE EXECUTE format('DROP INDEX %I.%I', v_parent_schema, v_index.name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Moving table %s.%s to schema %s' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema)); END IF; EXECUTE format('ALTER TABLE %I.%I SET SCHEMA %I', v_row.partition_schemaname, v_row.partition_tablename, v_retention_schema); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if -- If child table is a subpartition, remove it from part_config & part_config_sub (should cascade due to FK) DELETE FROM @extschema@.part_config WHERE parent_table = v_row.partition_schemaname||'.'||v_row.partition_tablename; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', format('%s partitions dropped.', v_drop_count)); 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_drop_count; 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 DROP TIME PARTITION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE OR REPLACE FUNCTION partition_data_id(p_parent_table text , p_batch_count int DEFAULT 1 , p_batch_interval bigint DEFAULT NULL , p_lock_wait numeric DEFAULT 0 , p_order text DEFAULT 'ASC' , p_analyze boolean DEFAULT true) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_control_type text; v_current_partition_name text; v_epoch text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_id bigint; v_min_partition_id bigint; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_partition_id bigint[]; v_rowcount bigint; v_sql text; v_start_control bigint; v_total_rows bigint := 0; BEGIN /* * Populate the child table(s) of an id-based partition set with old data from the original parent */ SELECT partition_interval::bigint , control , epoch INTO v_partition_interval , v_control , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'partman'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: No entry in part_config found for non-native partitioning for given table: %', p_parent_table; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'id' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN RAISE EXCEPTION 'Control column for given partition set is not id/serial based or epoch flag is set for time-based partitioning.'; END IF; 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'); IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; FOR i IN 1..p_batch_count LOOP IF p_order = 'ASC' THEN EXECUTE format('SELECT min(%I) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; IF v_start_control IS NULL THEN EXIT; END IF; v_min_partition_id = v_start_control - (v_start_control % v_partition_interval); v_partition_id := ARRAY[v_min_partition_id]; -- Check if custom batch interval overflows current partition maximum IF (v_start_control + p_batch_interval) >= (v_min_partition_id + v_partition_interval) THEN v_max_partition_id := v_min_partition_id + v_partition_interval; ELSE v_max_partition_id := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN EXECUTE 'SELECT max('||v_control||') FROM ONLY '||p_parent_table INTO v_start_control; IF v_start_control IS NULL THEN EXIT; END IF; v_min_partition_id = v_start_control - (v_start_control % v_partition_interval); -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_id := v_min_partition_id + v_partition_interval; v_partition_id := ARRAY[v_min_partition_id]; -- Make sure minimum doesn't underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_id THEN v_min_partition_id = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN v_sql := format('SELECT * FROM ONLY %I.%I WHERE %I >= %s AND %I < %s FOR UPDATE NOWAIT' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_id , v_control , v_max_partition_id); EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; PERFORM @extschema@.create_partition_id(p_parent_table, v_partition_id, p_analyze); v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_min_partition_id::text, TRUE); EXECUTE format('WITH partition_data AS ( DELETE FROM ONLY %I.%I WHERE %I >= %s AND %I < %s RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_id , v_control , v_max_partition_id , v_parent_schema , v_current_partition_name); GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; PERFORM @extschema@.create_function_id(p_parent_table); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_total_rows; END $$; CREATE OR REPLACE FUNCTION partition_data_time( p_parent_table text , p_batch_count int DEFAULT 1 , p_batch_interval interval DEFAULT NULL , p_lock_wait numeric DEFAULT 0 , p_order text DEFAULT 'ASC' , p_analyze boolean DEFAULT true) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_control_type text; v_datetime_string text; v_current_partition_name text; v_epoch text; v_last_partition text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_timestamp timestamptz; v_min_partition_timestamp timestamptz; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_expression text; v_partition_interval interval; v_partition_suffix text; v_partition_timestamp timestamptz[]; v_quarter text; v_rowcount bigint; v_start_control timestamptz; v_time_position int; v_total_rows bigint := 0; v_type text; v_year text; BEGIN /* * Populate the child table(s) of a time-based partition set with old data from the original parent */ SELECT partition_type , partition_interval::interval , control , datetime_string , epoch INTO v_type , v_partition_interval , v_control , v_datetime_string , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type IN ('partman', 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: No entry in part_config found for non-native partitioning for given table: %', p_parent_table; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'time' THEN IF (v_control_type = 'id' AND v_epoch = 'none') OR v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run on partition set without time based control column or epoch flag set with an id column. Found control: %, epoch: %', v_control_type, v_epoch; END IF; END IF; 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'); IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) END; FOR i IN 1..p_batch_count LOOP IF p_order = 'ASC' THEN EXECUTE format('SELECT min(%s) FROM ONLY %I.%I', v_partition_expression, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSIF p_order = 'DESC' THEN EXECUTE format('SELECT max(%s) FROM ONLY %I.%I', v_partition_expression, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; IF v_start_control IS NULL THEN EXIT; END IF; IF v_type = 'partman' THEN CASE WHEN v_partition_interval = '15 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '15min'::interval * floor(date_part('minute', v_start_control) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '30min'::interval * floor(date_part('minute', v_start_control) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control); WHEN v_partition_interval = '1 day' THEN v_min_partition_timestamp := date_trunc('day', v_start_control); WHEN v_partition_interval = '1 week' THEN v_min_partition_timestamp := date_trunc('week', v_start_control); WHEN v_partition_interval = '1 month' THEN v_min_partition_timestamp := date_trunc('month', v_start_control); WHEN v_partition_interval = '3 months' THEN v_min_partition_timestamp := date_trunc('quarter', v_start_control); WHEN v_partition_interval = '1 year' THEN v_min_partition_timestamp := date_trunc('year', v_start_control); END CASE; ELSIF v_type = 'time-custom' THEN SELECT child_start_time INTO v_min_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition , v_partition_interval::text , p_parent_table); v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; LOOP IF v_start_control >= v_min_partition_timestamp AND v_start_control < v_max_partition_timestamp THEN EXIT; ELSE BEGIN IF v_start_control > v_max_partition_timestamp THEN -- Keep going forward in time, checking if child partition time interval encompasses the current v_start_control value v_min_partition_timestamp := v_max_partition_timestamp; v_max_partition_timestamp := v_max_partition_timestamp + v_partition_interval; ELSE -- Keep going backwards in time, checking if child partition time interval encompasses the current v_start_control value v_max_partition_timestamp := v_min_partition_timestamp; v_min_partition_timestamp := v_min_partition_timestamp - v_partition_interval; END IF; EXCEPTION WHEN datetime_field_overflow THEN RAISE EXCEPTION 'Attempted partition time interval is outside PostgreSQL''s supported time range. Unable to create partition with interval before timestamp % ', v_min_partition_interval; END; END IF; END LOOP; END IF; v_partition_timestamp := ARRAY[v_min_partition_timestamp]; IF p_order = 'ASC' THEN -- Ensure batch interval given as parameter doesn't cause maximum to overflow the current partition maximum IF (v_start_control + p_batch_interval) >= (v_min_partition_timestamp + v_partition_interval) THEN v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; ELSE v_max_partition_timestamp := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; -- Ensure batch interval given as parameter doesn't cause minimum to underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_timestamp THEN v_min_partition_timestamp = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM ONLY %I.%I WHERE %s >= %L AND %3$s < %5$L FOR UPDATE NOWAIT' , v_parent_schema , v_parent_tablename , v_partition_expression , v_min_partition_timestamp , v_max_partition_timestamp); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; PERFORM @extschema@.create_partition_time(p_parent_table, v_partition_timestamp, p_analyze); -- This suffix generation code is in create_partition_time() as well v_partition_suffix := to_char(v_min_partition_timestamp, v_datetime_string); v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); EXECUTE format('WITH partition_data AS ( DELETE FROM ONLY %I.%I WHERE %s >= %L AND %3$s < %5$L RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_parent_schema , v_parent_tablename , v_partition_expression , v_min_partition_timestamp , v_max_partition_timestamp , v_parent_schema , v_current_partition_name); GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; PERFORM @extschema@.create_function_time(p_parent_table); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_total_rows; END $$; CREATE OR REPLACE FUNCTION run_maintenance(p_parent_table text DEFAULT NULL, p_analyze boolean DEFAULT true, p_jobmon boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_check_subpart int; v_control_type text; v_create_count int := 0; v_current_partition text; v_current_partition_id bigint; v_current_partition_timestamp timestamptz; v_drop_count int := 0; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_created boolean; v_last_partition_id bigint; v_last_partition_timestamp timestamptz; v_max_id_parent bigint; v_max_time_parent timestamptz; v_new_search_path text := '@extschema@,pg_temp'; v_next_partition_id bigint; v_next_partition_timestamp timestamptz; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_expression text; v_premade_count int; v_premake_id_max bigint; v_premake_id_min bigint; v_premake_timestamp_min timestamptz; v_premake_timestamp_max timestamptz; v_row record; v_row_max_id record; v_row_max_time record; v_row_sub record; v_step_id bigint; v_step_overflow_id bigint; v_sub_id_max bigint; v_sub_id_max_suffix bigint; v_sub_id_min bigint; v_sub_parent text; v_sub_timestamp_max timestamptz; v_sub_timestamp_max_suffix timestamptz; v_sub_timestamp_min timestamptz; v_tablename text; v_tables_list_sql text; BEGIN /* * Function to manage pre-creation of the next partitions in a set. * Also manages dropping old partitions if the retention option is set. * If p_parent_table is passed, will only run run_maintenance() on that one table (no matter what the configuration table may have set for it) * Otherwise, will run on all tables in the config table with p_automatic_maintenance() set to true. * For large partition sets, running analyze can cause maintenance to take longer than expected. Can set p_analyze to false to avoid a forced analyze run. * Be aware that constraint exclusion may not work properly until an analyze on the partition set is run. */ v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman maintenance already running.'; RETURN; 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'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN RUN MAINTENANCE'); v_step_id := add_step(v_job_id, 'Running maintenance loop'); END IF; v_row := NULL; -- Ensure it's reset v_tables_list_sql := 'SELECT parent_table , partition_type , partition_interval , control , premake , undo_in_progress , sub_partition_set_full , epoch , infinite_time_partitions , retention FROM @extschema@.part_config WHERE undo_in_progress = false'; IF p_parent_table IS NULL THEN v_tables_list_sql := v_tables_list_sql || ' AND automatic_maintenance = ''on'''; ELSE v_tables_list_sql := v_tables_list_sql || format(' AND parent_table = %L', p_parent_table); END IF; FOR v_row IN EXECUTE v_tables_list_sql LOOP CONTINUE WHEN v_row.undo_in_progress; -- Check for consistent data in part_config_sub table. Was unable to get this working properly as either a constraint or trigger. -- Would either delay raising an error until the next write (which I cannot predict) or disallow future edits to update a sub-partition set's configuration. -- This way at least provides a consistent way to check that I know will run. If anyone can get a working constraint/trigger, please help! SELECT sub_parent INTO v_sub_parent FROM @extschema@.part_config_sub WHERE sub_parent = v_row.parent_table; IF v_sub_parent IS NOT NULL THEN SELECT count(*) INTO v_check_subpart FROM @extschema@.check_subpart_sameconfig(v_row.parent_table); IF v_check_subpart > 1 THEN RAISE EXCEPTION 'Inconsistent data in part_config_sub table. Sub-partition tables that are themselves sub-partitions cannot have differing configuration values among their siblings. Run this query: "SELECT * FROM @extschema@.check_subpart_sameconfig(''%'');" This should only return a single row or nothing. If multiple rows are returned, results are all children of the given parent. Update the differing values to be consistent for your desired values.', v_row.sub_parent; END IF; END IF; SELECT n.nspname, c.relname INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = split_part(v_row.parent_table, '.', 1)::name AND c.relname = split_part(v_row.parent_table, '.', 2)::name; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_row.control); v_partition_expression := CASE WHEN v_row.epoch = 'seconds' THEN format('to_timestamp(%I)', v_row.control) WHEN v_row.epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_row.control) ELSE format('%I', v_row.control) END; IF p_debug THEN RAISE NOTICE 'run_maint: v_partition_expression: %', v_partition_expression; END IF; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LIMIT 1; IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_last_partition: %', v_row.parent_table, v_last_partition; END IF; IF v_control_type = 'time' OR (v_control_type = 'id' AND v_row.epoch <> 'none') THEN -- Run retention if needed IF v_row.retention IS NOT NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); IF v_drop_count > 0 AND v_row.partition_type <> 'native' THEN PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); END IF; END IF; IF v_row.sub_partition_set_full THEN CONTINUE; END IF; SELECT child_start_time INTO v_last_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_row.partition_interval, v_row.parent_table); -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. IF v_row.infinite_time_partitions IS TRUE THEN -- Set it to "now" so new partitions continue to be created -- For infinite_time_partitions, don't bother getting the max value in the partitions v_current_partition_timestamp = CURRENT_TIMESTAMP; ELSE FOR v_row_max_time IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%s)::text FROM %I.%I' , v_partition_expression , v_row_max_time.partition_schemaname , v_row_max_time.partition_tablename ) INTO v_current_partition_timestamp; IF v_current_partition_timestamp IS NOT NULL THEN SELECT suffix_timestamp INTO v_current_partition_timestamp FROM @extschema@.show_partition_name(v_row.parent_table, v_current_partition_timestamp::text); EXIT; END IF; END LOOP; END IF; -- end infinite time check -- Check for values in the parent table. If they are there and greater than all child values, use that instead -- This allows maintenance to continue working properly if there is a large gap in data insertion. Data will remain in parent, but new tables will be created EXECUTE format('SELECT max(%s) FROM ONLY %I.%I', v_partition_expression, v_parent_schema, v_parent_tablename) INTO v_max_time_parent; IF p_debug THEN RAISE NOTICE 'run_maint: v_current_partition_timestamp: %, v_max_time_parent: %', v_current_partition_timestamp, v_max_time_parent; END IF; IF v_max_time_parent > v_current_partition_timestamp THEN SELECT suffix_timestamp INTO v_current_partition_timestamp FROM @extschema@.show_partition_name(v_row.parent_table, v_max_time_parent::text); END IF; IF v_current_partition_timestamp IS NULL THEN -- Partition set is completely empty and infinite time partitions not set -- Nothing to do CONTINUE; END IF; -- If this is a subpartition, determine if the last child table has been made. If so, mark it as full so future maintenance runs can skip it SELECT sub_min::timestamptz, sub_max::timestamptz INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'time'); IF v_sub_timestamp_max IS NOT NULL THEN SELECT suffix_timestamp INTO v_sub_timestamp_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_timestamp_max::text); IF v_sub_timestamp_max_suffix = v_last_partition_timestamp THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; -- Check and see how many premade partitions there are. v_premade_count = round(EXTRACT('epoch' FROM age(v_last_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); v_next_partition_timestamp := v_last_partition_timestamp; IF p_debug THEN RAISE NOTICE 'run_maint before loop: current_partition_timestamp: %, v_premade_count: %, v_sub_timestamp_min: %, v_sub_timestamp_max: %' , v_current_partition_timestamp , v_premade_count , v_sub_timestamp_min , v_sub_timestamp_max; END IF; -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed WHILE (v_premade_count < v_row.premake) LOOP IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_premade_count: %, v_next_partition_timestamp: %', v_row.parent_table, v_premade_count, v_next_partition_timestamp; END IF; IF v_next_partition_timestamp < v_sub_timestamp_min OR v_next_partition_timestamp > v_sub_timestamp_max THEN -- With subpartitioning, no need to run if the timestamp is not in the parent table's range EXIT; END IF; BEGIN v_next_partition_timestamp := v_next_partition_timestamp + v_row.partition_interval::interval; EXCEPTION WHEN datetime_field_overflow THEN v_premade_count := v_row.premake; -- do this so it can exit the premake check loop and continue in the outer for loop IF v_jobmon_schema IS NOT NULL THEN 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', format('Child partition creation skipped for parent table: %s', v_partition_time)); END IF; RAISE WARNING 'Attempted partition time interval is outside PostgreSQL''s supported time range. Child partition creation skipped for parent table %', v_row.parent_table; CONTINUE; END; v_last_partition_created := @extschema@.create_partition_time(v_row.parent_table , ARRAY[v_next_partition_timestamp] , p_analyze , p_debug := p_debug); IF v_last_partition_created THEN v_create_count := v_create_count + 1; IF v_row.partition_type <> 'native' THEN PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); END IF; END IF; v_premade_count = round(EXTRACT('epoch' FROM age(v_next_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); END LOOP; ELSIF v_control_type = 'id' THEN -- Run retention if needed IF v_row.retention IS NOT NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); IF v_drop_count > 0 AND v_row.partition_type <> 'native' THEN PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); END IF; END IF; IF v_row.sub_partition_set_full THEN CONTINUE; END IF; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%I)::text FROM %I.%I' , v_row.control , v_row_max_id.partition_schemaname , v_row_max_id.partition_tablename) INTO v_current_partition_id; IF v_current_partition_id IS NOT NULL THEN SELECT suffix_id INTO v_current_partition_id FROM @extschema@.show_partition_name(v_row.parent_table, v_current_partition_id::text); EXIT; END IF; END LOOP; -- Check for values in the parent table. If they are there and greater than all child values, use that instead -- This allows maintenance to continue working properly if there is a large gap in data insertion. Data will remain in parent, but new tables will be created EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_row.control, v_parent_schema, v_parent_tablename) INTO v_max_id_parent; IF v_max_id_parent > v_current_partition_id THEN SELECT suffix_id INTO v_current_partition_id FROM @extschema@.show_partition_name(v_row.parent_table, v_max_id_parent::text); END IF; IF v_current_partition_id IS NULL THEN -- Partition set is completely empty. Nothing to do CONTINUE; END IF; SELECT child_start_id INTO v_last_partition_id FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_row.partition_interval, v_row.parent_table); -- Determine if this table is a child of a subpartition parent. If so, get limits to see if run_maintenance even needs to run for it. SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'id'); IF v_sub_id_max IS NOT NULL THEN SELECT suffix_id INTO v_sub_id_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_id_max::text); IF v_sub_id_max_suffix = v_last_partition_id THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; v_next_partition_id := v_last_partition_id; v_premade_count := ((v_last_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed. WHILE (v_premade_count < v_row.premake) LOOP IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_premade_count: %, v_next_partition_id: %', v_row.parent_table, v_premade_count, v_next_partition_id; END IF; IF v_next_partition_id < v_sub_id_min OR v_next_partition_id > v_sub_id_max THEN -- With subpartitioning, no need to run if the id is not in the parent table's range EXIT; END IF; v_next_partition_id := v_next_partition_id + v_row.partition_interval::bigint; v_last_partition_created := @extschema@.create_partition_id(v_row.parent_table, ARRAY[v_next_partition_id], p_analyze); IF v_last_partition_created THEN v_create_count := v_create_count + 1; IF v_row.partition_type <> 'native' THEN PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); END IF; END IF; v_premade_count := ((v_next_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); END LOOP; END IF; -- end main IF check for time or id END LOOP; -- end of creation loop IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Partition maintenance finished. %s partitions made. %s partitions dropped.', v_create_count, v_drop_count)); 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'); 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 RUN MAINTENANCE'')', v_jobmon_schema) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE OR REPLACE FUNCTION show_partition_info(p_child_table text , p_partition_interval text DEFAULT NULL , p_parent_table text DEFAULT NULL , OUT child_start_time timestamptz , OUT child_end_time timestamptz , OUT child_start_id bigint , OUT child_end_id bigint , OUT suffix text) RETURNS record LANGUAGE plpgsql STABLE SECURITY DEFINER AS $$ DECLARE v_child_schema text; v_child_tablename text; v_control text; v_control_type text; v_datetime_string text; v_epoch text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_table text; v_partition_interval text; v_partition_type text; v_quarter text; v_suffix text; v_suffix_position int; v_year text; BEGIN /* * Show the data boundries for a given child table as well as the suffix that will be used. * Passing the parent table argument improves performance by avoiding a catalog lookup. * Passing an interval lets you set one different than the default configured one if desired. */ 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'); SELECT n.nspname, c.relname INTO v_child_schema, v_child_tablename FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = split_part(p_child_table, '.', 1)::name AND c.relname = split_part(p_child_table, '.', 2)::name; IF v_child_tablename IS NULL THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RAISE EXCEPTION 'Child table given does not exist (%)', p_child_table; END IF; IF p_parent_table IS NULL THEN SELECT n.nspname||'.'|| c.relname INTO v_parent_table FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhparent JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhrelid::regclass = p_child_table::regclass; ELSE v_parent_table := p_parent_table; END IF; IF p_partition_interval IS NULL THEN SELECT control, partition_interval, partition_type, datetime_string, epoch INTO v_control, v_partition_interval, v_partition_type, v_datetime_string, v_epoch FROM @extschema@.part_config WHERE parent_table = v_parent_table; ELSE v_partition_interval := p_partition_interval; SELECT control, partition_type, datetime_string, epoch INTO v_control, v_partition_type, v_datetime_string, v_epoch FROM @extschema@.part_config WHERE parent_table = v_parent_table; END IF; IF v_control IS NULL THEN RAISE EXCEPTION 'Parent table of given child not managed by pg_partman: %', v_parent_table; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_child_schema, v_child_tablename, v_control); v_suffix_position := (length(v_child_tablename) - position('p_' in reverse(v_child_tablename))) + 2; v_suffix := substring(v_child_tablename from v_suffix_position); IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN IF v_partition_interval::interval <> '3 months' OR (v_partition_interval::interval = '3 months' AND v_partition_type = 'time-custom') THEN child_start_time := to_timestamp(v_suffix, v_datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(v_suffix, 'q', 1); v_quarter := split_part(v_suffix, 'q', 2); CASE WHEN v_quarter = '1' THEN child_start_time := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN child_start_time := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN child_start_time := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN child_start_time := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; child_end_time := (child_start_time + v_partition_interval::interval) - '1 second'::interval; ELSIF v_control_type = 'id' THEN child_start_id := v_suffix::bigint; child_end_id := (child_start_id + v_partition_interval::bigint) - 1; ELSE RAISE EXCEPTION 'Invalid partition type encountered in show_partition_info()'; END IF; suffix = v_suffix; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN; END $$; CREATE OR REPLACE FUNCTION show_partition_name(p_parent_table text, p_value text, OUT partition_table text, OUT suffix_timestamp timestamptz, OUT suffix_id bigint, OUT table_exists boolean) RETURNS record LANGUAGE plpgsql STABLE AS $$ DECLARE v_child_exists text; v_control text; v_control_type text; v_datetime_string text; v_epoch text; v_max_range timestamptz; v_min_range timestamptz; v_parent_schema text; v_parent_tablename text; v_partition_interval text; v_type text; BEGIN /* * Given a parent table and partition value, return the name of the child partition it would go in. * If using epoch time partitioning, give the text representation of the timestamp NOT the epoch integer value (use to_timestamp() to convert epoch values). * Also returns just the suffix value and true if the child table exists or false if it does not */ SELECT partition_type , control , partition_interval , datetime_string , epoch INTO v_type , v_control , v_partition_interval , v_datetime_string , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_type IS NULL THEN RAISE EXCEPTION 'Parent table given is not managed by pg_partman (%)', p_parent_table; END IF; SELECT n.nspname, c.relname INTO v_parent_schema, v_parent_tablename 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_tablename IS NULL THEN RAISE EXCEPTION 'Parent table given does not exist (%)', p_parent_table; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF ( (v_control_type = 'time') OR (v_control_type = 'id' AND v_epoch <> 'none') ) AND v_type <> 'time-custom' THEN CASE WHEN v_partition_interval::interval = '15 mins' THEN suffix_timestamp := date_trunc('hour', p_value::timestamptz) + '15min'::interval * floor(date_part('minute', p_value::timestamptz) / 15.0); WHEN v_partition_interval::interval = '30 mins' THEN suffix_timestamp := date_trunc('hour', p_value::timestamptz) + '30min'::interval * floor(date_part('minute', p_value::timestamptz) / 30.0); WHEN v_partition_interval::interval = '1 hour' THEN suffix_timestamp := date_trunc('hour', p_value::timestamptz); WHEN v_partition_interval::interval = '1 day' THEN suffix_timestamp := date_trunc('day', p_value::timestamptz); WHEN v_partition_interval::interval = '1 week' THEN suffix_timestamp := date_trunc('week', p_value::timestamptz); WHEN v_partition_interval::interval = '1 month' THEN suffix_timestamp := date_trunc('month', p_value::timestamptz); WHEN v_partition_interval::interval = '3 months' THEN suffix_timestamp := date_trunc('quarter', p_value::timestamptz); WHEN v_partition_interval::interval = '1 year' THEN suffix_timestamp := date_trunc('year', p_value::timestamptz); END CASE; partition_table := v_parent_schema||'.'||@extschema@.check_name_length(v_parent_tablename, to_char(suffix_timestamp, v_datetime_string), TRUE); ELSIF v_control_type = 'id' AND v_type <> 'time-custom' THEN suffix_id := (p_value::bigint - (p_value::bigint % v_partition_interval::bigint)); partition_table := v_parent_schema||'.'||@extschema@.check_name_length(v_parent_tablename, suffix_id::text, TRUE); ELSIF v_type = 'time-custom' THEN SELECT child_table, lower(partition_range) INTO partition_table, suffix_timestamp FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND partition_range @> p_value::timestamptz; IF partition_table IS NULL THEN SELECT max(upper(partition_range)) INTO v_max_range FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table; SELECT min(lower(partition_range)) INTO v_min_range FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table; IF p_value::timestamptz >= v_max_range THEN suffix_timestamp := v_max_range; LOOP -- Keep incrementing higher until given value is below the upper range suffix_timestamp := suffix_timestamp + v_partition_interval::interval; IF p_value::timestamptz < suffix_timestamp THEN -- Have to subtract one interval because the value would actually be in the partition previous -- to this partition timestamp since the partition names contain the lower boundary suffix_timestamp := suffix_timestamp - v_partition_interval::interval; EXIT; END IF; END LOOP; ELSIF p_value::timestamptz < v_min_range THEN suffix_timestamp := v_min_range; LOOP -- Keep decrementing lower until given value is below or equal to the lower range suffix_timestamp := suffix_timestamp - v_partition_interval::interval; IF p_value::timestamptz >= suffix_timestamp THEN EXIT; END IF; END LOOP; ELSE RAISE EXCEPTION 'Unable to determine a valid child table for the given parent table and value'; END IF; partition_table := v_parent_schema||'.'||@extschema@.check_name_length(v_parent_tablename, to_char(suffix_timestamp, v_datetime_string), TRUE); END IF; END IF; SELECT tablename INTO v_child_exists FROM pg_catalog.pg_tables WHERE schemaname = split_part(partition_table, '.', 1)::name AND tablename = split_part(partition_table, '.', 2)::name; IF v_child_exists IS NOT NULL THEN table_exists := true; ELSE table_exists := false; END IF; RETURN; END $$; CREATE OR REPLACE FUNCTION show_partitions (p_parent_table text, p_order text DEFAULT 'ASC') RETURNS TABLE (partition_schemaname text, partition_tablename text) LANGUAGE plpgsql STABLE SECURITY DEFINER AS $$ DECLARE v_control text; v_control_type text; v_datetime_string text; v_epoch text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval text; v_quarter text; v_type text; v_year text; BEGIN /* * Function to list all child partitions in a set in logical order. */ 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'); IF upper(p_order) NOT IN ('ASC', 'DESC') THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RAISE EXCEPTION 'p_order paramter must be one of the following values: ASC, DESC'; END IF; SELECT partition_type , partition_interval , datetime_string , control , epoch INTO v_type , v_partition_interval , v_datetime_string , v_control , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table; SELECT n.nspname, c.relname INTO v_parent_schema, v_parent_tablename 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; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN IF v_partition_interval::interval <> '3 months' OR (v_partition_interval::interval = '3 months' AND v_type = 'time-custom') THEN RETURN QUERY EXECUTE format('SELECT n.nspname::text AS partition_schemaname, c.relname::text AS partition_name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = ''%I.%I''::regclass ORDER BY to_timestamp(substring(c.relname from ((length(c.relname) - position(''p_'' in reverse(c.relname))) + 2) ), %L) %s' , v_parent_schema , v_parent_tablename , v_datetime_string , p_order); ELSE -- For quarterly, to_timestamp() doesn't recognize "Q" in datetime string. -- First order by just the year, then order by the quarter number (should be last character in table name) RETURN QUERY EXECUTE format('SELECT n.nspname::text AS partition_schemaname, c.relname::text AS partition_name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = ''%I.%I''::regclass ORDER BY to_timestamp(substring(c.relname from ((length(c.relname) - position(''p_'' in reverse(c.relname))) + 2) for 4), ''YYYY'') %s , substring(reverse(c.relname) from 1 for 1) %s' , v_parent_schema , v_parent_tablename , p_order , p_order); END IF; ELSIF v_control_type = 'id' THEN RETURN QUERY EXECUTE format('SELECT n.nspname::text AS partition_schemaname, c.relname::text AS partition_name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = ''%I.%I''::regclass ORDER BY substring(c.relname from ((length(c.relname) - position(''p_'' in reverse(c.relname))) + 2) )::bigint %s' , v_parent_schema , v_parent_tablename , p_order); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END $$; CREATE FUNCTION stop_sub_partition(p_parent_table text, p_jobmon boolean DEFAULT true) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_job_id bigint; v_jobmon_schema text; v_step_id bigint; BEGIN /* * Stop a given parent table from causing its children to be subpartitioned */ 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; END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.add_job(''PARTMAN STOP SUBPARTITIONING'')', v_jobmon_schema) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''Stopped subpartitioning for %s'')', v_jobmon_schema, v_job_id, p_parent_table) INTO v_step_id; END IF; DELETE FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.update_step(%s, %L, %L)', v_jobmon_schema, v_step_id, 'OK', 'Done'); EXECUTE format('SELECT %I.close_job(%s)', v_jobmon_schema, v_job_id); END IF; RETURN true; END $$; CREATE FUNCTION undo_partition(p_parent_table text, p_batch_count int DEFAULT 1, p_keep_table boolean DEFAULT true, p_jobmon boolean DEFAULT true, p_lock_wait numeric DEFAULT 0, OUT partitions_undone int, OUT rows_undone bigint) RETURNS record LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_batch_loop_count bigint := 0; v_child_count bigint; v_child_table text; v_copy_sql text; v_function_name text; v_job_id bigint; v_jobmon_schema text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_relkind char; v_rowcount bigint; v_step_id bigint; v_top_parent_table text; v_total bigint := 0; v_trig_name text; v_type text; v_undo_count int := 0; BEGIN /* * Function to undo partitioning. Copies data to parent without removing any data from children. * Will actually work on any non-native parent/child table set, not just ones created by pg_partman. */ v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition already running.'; partitions_undone = -1; RETURN; 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'); SELECT n.nspname, c.relname, c.relkind INTO v_parent_schema, v_parent_tablename, v_relkind 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_relkind = 'p' THEN RAISE EXCEPTION 'Given parent table uses native paritioning. Data cannot be copied to the parent with native. See undo_partition_native().'; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN UNDO PARTITIONING: %s', p_parent_table)); v_step_id := add_step(v_job_id, format('Undoing partitioning for table %s', p_parent_table)); END IF; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables and stop new id partitions from being made. v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t JOIN pg_catalog.pg_class c ON t.tgrelid = c.oid WHERE tgname = v_trig_name::name AND c.relname = v_parent_tablename::name; SELECT proname INTO v_function_name FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid WHERE n.nspname = v_parent_schema::name AND proname = v_function_name::name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_parent_tablename); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; RETURN; END IF; END IF; -- END p_lock_wait IF EXECUTE format('DROP TRIGGER IF EXISTS %I ON %I.%I', v_trig_name, v_parent_schema, v_parent_tablename); END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later IF v_function_name IS NOT NULL THEN EXECUTE format('DROP FUNCTION IF EXISTS %I.%I()', v_parent_schema, v_function_name); END IF; IF v_jobmon_schema IS NOT NULL THEN IF (v_trig_name IS NOT NULL OR v_function_name IS NOT NULL) THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); ELSE PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process.'); END IF; END IF; WHILE v_batch_loop_count < p_batch_count LOOP -- Get ordered list of child table in set. Store in variable one at a time per loop until none are left. -- Not using show_partitions() so it can work on non-pg_partman partition sets WITH parent_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE c1.relname = v_parent_tablename::name AND n1.nspname = v_parent_schema::name ) SELECT c.relname INTO v_child_table FROM pg_catalog.pg_inherits i JOIN pg_catalog.pg_class c ON i.inhrelid = c.oid JOIN parent_info p ON i.inhparent = p.oid ORDER BY i.inhrelid ASC; EXIT WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Removing child partition: %s.%s', v_parent_schema, v_child_table)); END IF; -- lockwait timeout for table drop v_lock_obtained := FALSE; -- reset for reuse in loop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_child_table); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; RETURN; END IF; END IF; -- END p_lock_wait IF v_copy_sql := format('INSERT INTO %I.%I SELECT * FROM %I.%I' , v_parent_schema , v_parent_tablename , v_parent_schema , v_child_table); EXECUTE v_copy_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_parent_schema , v_child_table , v_parent_schema , v_parent_tablename); IF p_keep_table = false THEN EXECUTE format('DROP TABLE %I.%I', v_parent_schema, v_child_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table DROPPED. Moved %s rows to parent', v_rowcount)); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table UNINHERITED, not DROPPED. Copied %s rows to parent', v_rowcount)); END IF; END IF; SELECT partition_type INTO v_type FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_parent_schema||'.'||v_child_table; END IF; v_batch_loop_count := v_batch_loop_count + 1; v_undo_count := v_undo_count + 1; END LOOP; -- v_batch_loop_count IF v_undo_count = 0 THEN -- FOR loop never ran, so there's no child tables left. DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; DELETE FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; -- If subpartitioned, remove the parent of this table from part_config_sub 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; DELETE FROM @extschema@.part_config_sub WHERE sub_parent = v_top_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman (if it existed)'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) from % child table(s) to the parent: %', v_total, v_undo_count, p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', format('Copied %s row(s) from %s child table(s) to the parent', v_total, v_undo_count)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); partitions_undone := v_undo_count; rows_undone := v_total; RETURN; 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 UNDO PARTITIONING: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE FUNCTION undo_partition_id(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval bigint DEFAULT NULL, p_keep_table boolean DEFAULT true, p_lock_wait numeric DEFAULT 0, OUT partitions_undone int, OUT rows_undone bigint) RETURNS record LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_batch_loop_count int := 0; v_child_loop_total bigint := 0; v_child_min bigint; v_child_table text; v_control text; v_control_type text; v_exists int; v_function_name text; v_inner_loop_count int; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_move_sql text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_int_text text; v_partition_interval bigint; v_row record; v_rowcount bigint; v_step_id bigint; v_sub_count int; v_trig_name text; v_total bigint := 0; v_undo_count int := 0; BEGIN /* * Function to undo id-based partitioning created by this extension */ v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition_id')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition_id already running.'; partitions_undone = -1; RETURN; END IF; SELECT partition_interval , control , jobmon INTO v_partition_int_text , v_control , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'partman'; IF v_control IS NULL THEN RAISE EXCEPTION 'No non-native configuration found for pg_partman for given parent table: %', p_parent_table; END IF; SELECT n.nspname, c.relname INTO v_parent_schema, v_parent_tablename 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; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'id' THEN RAISE EXCEPTION 'ERROR: Given parent table is not set up for id/serial partitioning'; END IF; v_partition_interval := v_partition_int_text::bigint; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Check if any child tables are themselves partitioned or part of an inheritance tree. Prevent undo at this level if so. -- Need to either lock child tables at all levels or handle the proper removal of triggers on all child tables first -- before multi-level undo can be performed safely. FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table) LOOP SELECT count(*) INTO v_sub_count FROM pg_catalog.pg_inherits i JOIN pg_catalog.pg_class c ON i.inhparent = c.oid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_row.partition_tablename::name AND n.nspname = v_row.partition_schemaname::name; IF v_sub_count > 0 THEN RAISE EXCEPTION 'Child table for this parent has child table(s) itself (%). Run undo partitioning on this table or remove inheritance first to ensure all data is properly moved to parent', v_row.partition_schemaname||'.'||v_row.partition_tablename; END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN UNDO PARTITIONING: %s', p_parent_table)); v_step_id := add_step(v_job_id, format('Undoing partitioning for table %s', p_parent_table)); END IF; IF p_batch_interval IS NULL THEN p_batch_interval := v_partition_interval; END IF; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables and stop new id partitions from being made. v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t JOIN pg_catalog.pg_class c ON t.tgrelid = c.oid WHERE tgname = v_trig_name::name AND c.relname = v_parent_tablename::name; SELECT proname INTO v_function_name FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid WHERE n.nspname = v_parent_schema::name AND proname = v_function_name::name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_parent_tablename); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; partitions_undone = -1; RETURN; END IF; END IF; -- END p_lock_wait IF EXECUTE format('DROP TRIGGER IF EXISTS %I ON %I.%I', v_trig_name, v_parent_schema, v_parent_tablename); END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later IF v_function_name IS NOT NULL THEN EXECUTE format('DROP FUNCTION IF EXISTS %I.%I()', v_parent_schema, v_function_name); END IF; IF v_jobmon_schema IS NOT NULL THEN IF (v_trig_name IS NOT NULL OR v_function_name IS NOT NULL) THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); ELSE PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process.'); END IF; END IF; <> LOOP -- Get ordered list of child table in set. Store in variable one at a time per loop until none are left or batch count is reached. -- This easily allows it to loop over same child table until empty or move onto next child table after it's dropped SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC') LIMIT 1; EXIT WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Removing child partition: %s.%s', v_parent_schema, v_child_table)); END IF; EXECUTE format('SELECT min(%I) FROM %I.%I', v_control, v_parent_schema, v_child_table) INTO v_child_min; IF v_child_min IS NULL THEN -- No rows left in this child table. Remove from partition set. -- lockwait timeout for table drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_child_table); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; partitions_undone = -1; RETURN; END IF; END IF; -- END p_lock_wait IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_parent_schema , v_child_table , v_parent_schema , v_parent_tablename); IF p_keep_table = false THEN EXECUTE format('DROP TABLE %I.%I', v_parent_schema, v_child_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table DROPPED. Moved %s rows to parent', v_child_loop_total)); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table UNINHERITED, not DROPPED. Moved %s rows to parent', v_child_loop_total)); END IF; END IF; v_undo_count := v_undo_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached CONTINUE outer_child_loop; -- skip data moving steps below END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP -- lockwait timeout for row batches IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM %I.%I WHERE %I <= %s FOR UPDATE NOWAIT' , v_parent_schema , v_child_table , v_control , v_child_min + (p_batch_interval * v_inner_loop_count)); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; partitions_undone = -1; RETURN; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval v_move_sql := format('WITH move_data AS ( DELETE FROM %I.%I WHERE %I <= %s RETURNING *) INSERT INTO %I.%I SELECT * FROM move_data' , v_parent_schema , v_child_table , v_control , v_child_min + (p_batch_interval * v_inner_loop_count) , v_parent_schema , v_parent_tablename); EXECUTE v_move_sql; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Moved %s rows to parent.', v_child_loop_total)); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; -- Check again if table is empty and go to outer loop again to drop it if so EXECUTE format('SELECT min(%I) FROM %I.%I', v_control, v_parent_schema, v_child_table) INTO v_child_min; CONTINUE outer_child_loop WHEN v_child_min IS NULL; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC') LIMIT 1; IF v_child_table IS NULL THEN DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; DELETE FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) to the parent. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', format('Copied %s row(s) to the parent. Removed %s partitions.', v_total, v_undo_count)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); partitions_undone := v_undo_count; rows_undone := v_total; 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 UNDO PARTITIONING: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE FUNCTION undo_partition_time(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval interval DEFAULT NULL, p_keep_table boolean DEFAULT true, p_lock_wait numeric DEFAULT 0, OUT partitions_undone int, OUT rows_undone bigint) RETURNS record LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_batch_loop_count int := 0; v_child_loop_total bigint := 0; v_child_min timestamptz; v_child_table text; v_control text; v_control_type text; v_epoch text; v_function_name text; v_inner_loop_count int; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_expression text; v_partition_int_text text; v_partition_interval interval; v_row record; v_rowcount bigint; v_step_id bigint; v_sub_count int; v_total bigint := 0; v_trig_name text; v_type text; v_undo_count int := 0; BEGIN /* * Function to undo time-based partitioning created by this extension */ v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition_time')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition_time already running.'; partitions_undone = -1; RETURN; END IF; SELECT partition_type , partition_interval , control , jobmon , epoch INTO v_type , v_partition_int_text , v_control , v_jobmon , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type IN ('partman', 'time-custom'); IF v_control IS NULL THEN RAISE EXCEPTION 'No non-native configuration found for pg_partman for given parent table: %', p_parent_table; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'time' THEN IF (v_control_type = 'id' AND v_epoch = 'none') OR v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run on partition set without time based control column or epoch flag set with an id column. Found control: %, epoch: %', v_control_type, v_epoch; END IF; END IF; v_partition_interval := v_partition_int_text::interval; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Check if any child tables are themselves partitioned or part of an inheritance tree. Prevent undo at this level if so. -- Need to either lock child tables at all levels or handle the proper removal of triggers on all child tables first -- before multi-level undo can be performed safely. FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table) LOOP SELECT count(*) INTO v_sub_count FROM pg_catalog.pg_inherits i JOIN pg_catalog.pg_class c ON i.inhparent = c.oid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_row.partition_tablename::name AND n.nspname = v_row.partition_schemaname::name; IF v_sub_count > 0 THEN RAISE EXCEPTION 'Child table for this parent has child table(s) itself (%). Run undo partitioning on this table or remove inheritance first to ensure all data is properly moved to parent', v_row.partition_schemaname||'.'||v_row.partition_tablename; END IF; END LOOP; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN UNDO PARTITIONING: %s', p_parent_table)); v_step_id := add_step(v_job_id, format('Undoing partitioning for table %s', p_parent_table)); END IF; IF p_batch_interval IS NULL THEN p_batch_interval := v_partition_interval; END IF; v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) end; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; -- Stop data going into child tables. v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t JOIN pg_catalog.pg_class c ON t.tgrelid = c.oid WHERE tgname = v_trig_name::name AND c.relname = v_parent_tablename::name; SELECT proname INTO v_function_name FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid WHERE n.nspname = v_parent_schema::name AND proname = v_function_name::name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_parent_tablename); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; partitions_undone = -1; RETURN; END IF; END IF; -- END p_lock_wait IF EXECUTE format('DROP TRIGGER IF EXISTS %I ON %I.%I', v_trig_name, v_parent_schema, v_parent_tablename); END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later IF v_function_name IS NOT NULL THEN EXECUTE format('DROP FUNCTION IF EXISTS %I.%I()', v_parent_schema, v_function_name); END IF; IF v_jobmon_schema IS NOT NULL THEN IF (v_trig_name IS NOT NULL OR v_function_name IS NOT NULL) THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); ELSE PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process.'); END IF; END IF; <> LOOP -- Get ordered list of child table in set. Store in variable one at a time per loop until none are left or batch count is reached. -- This easily allows it to loop over same child table until empty or move onto next child table after it's dropped SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC') LIMIT 1; EXIT outer_child_loop WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Removing child partition: %s.%s', v_parent_schema, v_child_table)); END IF; EXECUTE format('SELECT min(%s) FROM %I.%I', v_partition_expression, v_parent_schema, v_child_table) INTO v_child_min; IF v_child_min IS NULL THEN -- No rows left in this child table. Remove from partition set. -- lockwait timeout for table drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_child_table); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; partitions_undone = -1; RETURN; END IF; END IF; -- END p_lock_wait IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_parent_schema , v_child_table , v_parent_schema , v_parent_tablename); IF p_keep_table = false THEN EXECUTE format('DROP TABLE %I.%I', v_parent_schema, v_child_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table DROPPED. Moved %s rows to parent', v_child_loop_total)); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table UNINHERITED, not DROPPED. Moved %s rows to parent', v_child_loop_total)); END IF; END IF; IF v_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_parent_schema||'.'||v_child_table; END IF; v_undo_count := v_undo_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached CONTINUE outer_child_loop; -- skip data moving steps below END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM %I.%I WHERE %I <= %L FOR UPDATE NOWAIT' , v_parent_schema , v_child_table , v_control , v_child_min + (p_batch_interval * v_inner_loop_count)); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; partitions_undone = -1; RETURN; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval EXECUTE format('WITH move_data AS ( DELETE FROM %I.%I WHERE %s <= %L RETURNING *) INSERT INTO %I.%I SELECT * FROM move_data' , v_parent_schema , v_child_table , v_partition_expression , v_child_min + (p_batch_interval * v_inner_loop_count) , v_parent_schema , v_parent_tablename); GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Moved %s rows to parent.', v_child_loop_total)); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; -- Check again if table is empty and go to outer loop again to drop it if so EXECUTE format('SELECT min(%s) FROM %I.%I', v_partition_expression, v_parent_schema, v_child_table) INTO v_child_min; CONTINUE outer_child_loop WHEN v_child_min IS NULL; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC') LIMIT 1; IF v_child_table IS NULL THEN DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; DELETE FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Copied % row(s) to the parent. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', format('Copied %s row(s) to the parent. Removed %s partitions.', v_total, v_undo_count)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); partitions_undone := v_undo_count; rows_undone := v_total; 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 UNDO PARTITIONING: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE FUNCTION undo_partition_native(p_parent_table text, p_target_table text, p_batch_count int DEFAULT 1, p_batch_interval text DEFAULT NULL, p_keep_table boolean DEFAULT true, p_lock_wait numeric DEFAULT 0, OUT partitions_undone int, OUT rows_undone bigint) RETURNS record LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_batch_interval_id bigint; v_batch_interval_time interval; v_batch_loop_count int := 0; v_child_loop_total bigint := 0; v_child_table text; v_control text; v_control_type text; v_child_min_id bigint; v_child_min_time timestamptz; v_epoch text; v_jobmon boolean; v_jobmon_schema text; v_job_id bigint; v_inner_loop_count int; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_new_search_path text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_expression text; v_partition_interval text; v_relkind char; v_row record; v_rowcount bigint; v_step_id bigint; v_sub_count int; v_target_schema text; v_target_tablename text; v_total bigint := 0; v_undo_count int := 0; BEGIN /* * Function to undo native partitioning. * Moves data to new, target table since data cannot be moved to parent. * Leaves old parent table as is and does not change name of new table. * Note that target schema can be different than old parent. * Should work on native partitioned tables not managed by pg_partman as well. */ v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition_native')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition_native already running.'; partitions_undone = -1; RETURN; END IF; IF p_parent_table = p_target_table THEN RAISE EXCEPTION 'Target table cannot be the same as the parent table'; END IF; SELECT partition_interval::text , control , jobmon , epoch INTO v_partition_interval , v_control , v_jobmon , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'native'; IF v_control IS NULL THEN RAISE EXCEPTION 'No native configuration found for pg_partman for given parent table: %', p_parent_table; END IF; SELECT n.nspname, c.relname, c.relkind INTO v_parent_schema, v_parent_tablename, v_relkind 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_control IS NULL THEN -- not managed by pg_partman -- Note in docs that this only works on undoing the same partition data types that pg_partman itself supports (single column, time/id, no expressions) IF v_relkind <> 'p' THEN RAISE EXCEPTION 'Given parent table is not managed by pg_partman nor natively partitioned.'; END IF; SELECT a.attname INTO v_control 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_parent_schema::name AND c.relname = v_parent_tablename::name AND attnum IN (SELECT unnest(partattrs) FROM pg_catalog.pg_partitioned_table p WHERE a.attrelid = p.partrelid); IF v_control IS NULL THEN RAISE EXCEPTION 'Unexpected error encountered looking up native partition column. Please report bug to extension author.'; END IF; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN IF p_batch_interval IS NULL THEN v_batch_interval_time := v_partition_interval::interval; ELSE v_batch_interval_time := p_batch_interval::interval; END IF; ELSIF v_control_type = 'id' THEN IF p_batch_interval IS NULL THEN v_batch_interval_id := v_partition_interval::bigint; ELSE v_batch_interval_id := p_batch_interval::bigint; END IF; ELSE RAISE EXCEPTION 'Data type of control column in given partition set must be either data/time or integer.'; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Check if any child tables are themselves partitioned or part of an inheritance tree. Prevent undo at this level if so. -- Need to either lock child tables at all levels or handle the proper removal of triggers on all child tables first -- before multi-level undo can be performed safely. FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table) LOOP SELECT count(*) INTO v_sub_count FROM pg_catalog.pg_inherits i JOIN pg_catalog.pg_class c ON i.inhparent = c.oid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_row.partition_tablename::name AND n.nspname = v_row.partition_schemaname::name; IF v_sub_count > 0 THEN RAISE EXCEPTION 'Child table for this parent has child table(s) itself (%). Run undo partitioning on this table or remove inheritance first to ensure all data is properly moved to parent', v_row.partition_schemaname||'.'||v_row.partition_tablename; END IF; END LOOP; SELECT n.nspname, c.relname INTO v_target_schema, v_target_tablename FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = split_part(p_target_table, '.', 1)::name AND c.relname = split_part(p_target_table, '.', 2)::name; IF v_target_tablename IS NULL THEN RAISE EXCEPTION 'Given target table not found in system catalogs: %', p_target_table; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN UNDO PARTITIONING: %s', p_parent_table)); v_step_id := add_step(v_job_id, format('Undoing partitioning for table %s', p_parent_table)); END IF; v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) END; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process.'); END IF; <> LOOP -- Get ordered list of child table in set. Store in variable one at a time per loop until none are left or batch count is reached. -- This easily allows it to loop over same child table until empty or move onto next child table after it's dropped SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC') LIMIT 1; EXIT outer_child_loop WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Removing child partition: %s.%s', v_parent_schema, v_child_table)); END IF; IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN EXECUTE format('SELECT min(%s) FROM %I.%I', v_partition_expression, v_parent_schema, v_child_table) INTO v_child_min_time; ELSIF v_control_type = 'id' THEN EXECUTE format('SELECT min(%s) FROM %I.%I', v_partition_expression, v_parent_schema, v_child_table) INTO v_child_min_id; END IF; IF v_child_min_time IS NULL AND v_child_min_id IS NULL THEN -- No rows left in this child table. Remove from partition set. -- lockwait timeout for table drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_child_table); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; partitions_undone = -1; RETURN; END IF; END IF; -- END p_lock_wait IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE format('ALTER TABLE %I.%I DETACH PARTITION %I.%I' , v_parent_schema , v_parent_tablename , v_parent_schema , v_child_table); IF p_keep_table = false THEN EXECUTE format('DROP TABLE %I.%I', v_parent_schema, v_child_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table DROPPED. Moved %s rows to target table', v_child_loop_total)); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table DETACHED from parent, not DROPPED. Moved %s rows to target table', v_child_loop_total)); END IF; END IF; v_undo_count := v_undo_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached CONTINUE outer_child_loop; -- skip data moving steps below END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM %I.%I WHERE %I <= %L FOR UPDATE NOWAIT' , v_parent_schema , v_child_table , v_control , v_child_min_time + (v_batch_interval_time * v_inner_loop_count)); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; partitions_undone = -1; RETURN; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval EXECUTE format('WITH move_data AS ( DELETE FROM %I.%I WHERE %s <= %L RETURNING *) INSERT INTO %I.%I SELECT * FROM move_data' , v_parent_schema , v_child_table , v_partition_expression , v_child_min_time + (v_batch_interval_time * v_inner_loop_count) , v_target_schema , v_target_tablename); GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Moved %s rows to target table.', v_child_loop_total)); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; -- Check again if table is empty and go to outer loop again to drop it if so EXECUTE format('SELECT min(%s) FROM %I.%I', v_partition_expression, v_parent_schema, v_child_table) INTO v_child_min_time; CONTINUE outer_child_loop WHEN v_child_min_time IS NULL; ELSIF v_control_type = 'id' THEN IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM %I.%I WHERE %I <= %L FOR UPDATE NOWAIT' , v_parent_schema , v_child_table , v_control , v_child_min_id + (v_batch_interval_id * v_inner_loop_count)); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; partitions_undone = -1; RETURN; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval EXECUTE format('WITH move_data AS ( DELETE FROM %I.%I WHERE %s <= %L RETURNING *) INSERT INTO %I.%I SELECT * FROM move_data' , v_parent_schema , v_child_table , v_partition_expression , v_child_min_id + (v_batch_interval_id * v_inner_loop_count) , v_target_schema , v_target_tablename); GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Moved %s rows to target table.', v_child_loop_total)); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; -- Check again if table is empty and go to outer loop again to drop it if so EXECUTE format('SELECT min(%s) FROM %I.%I', v_partition_expression, v_parent_schema, v_child_table) INTO v_child_min_id; CONTINUE outer_child_loop WHEN v_child_min_id IS NULL; END IF; -- end v_control_type check EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC') LIMIT 1; IF v_child_table IS NULL THEN DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Moved % row(s) to the target table. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', format('Moved %s row(s) to the target table. Removed %s partitions.', v_total, v_undo_count)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); partitions_undone := v_undo_count; rows_undone := v_total; 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 UNDO PARTITIONING: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; -- Restore dropped object privileges DO $$ DECLARE v_row record; BEGIN FOR v_row IN SELECT statement FROM partman_preserve_privs_temp LOOP IF v_row.statement IS NOT NULL THEN EXECUTE v_row.statement; END IF; END LOOP; END $$; DROP TABLE IF EXISTS partman_preserve_privs_temp; pg_partman-4.6.0/updates/pg_partman--3.0.0--3.0.1.sql000066400000000000000000000034511412761706100214220ustar00rootroot00000000000000-- IMPORTANT NOTE: The initial version of pg_partman 3 had to be split into two updates due to changing both the data in the config table as well as adding constraints on that data. Depending on the data contained in the config table, doing this in a single-transaction update may not work (you may see an error about pending trigger events). If this is the case, please update to version 3.0.1 in a separate transaction from your update to 3.0.0. Example: /* BEGIN; ALTER EXTENSION pg_partman UPDATE TO '3.0.0'; COMMIT; BEGIN; ALTER EXTENSION pg_partman UPDATE TO '3.0.1'; COMMIT; */ -- Update 3.0.1 MUST be installed for version 3.x of pg_partman to work properly. As long as these updates are run within a few seconds of each other, there should be no issues. ALTER TABLE @extschema@.part_config DROP CONSTRAINT part_config_type_check; ALTER TABLE @extschema@.part_config_sub DROP CONSTRAINT part_config_sub_type_check; UPDATE @extschema@.part_config SET partition_type = 'partman' WHERE partition_type <> 'time-custom'; UPDATE @extschema@.part_config_sub SET sub_partition_type = 'partman' WHERE sub_partition_type <> 'time-custom'; ALTER TABLE @extschema@.part_config ADD CONSTRAINT part_config_type_check CHECK (@extschema@.check_partition_type(partition_type)); ALTER TABLE @extschema@.part_config_sub ADD CONSTRAINT part_config_sub_type_check CHECK (@extschema@.check_partition_type(sub_partition_type)); ALTER TABLE @extschema@.part_config ADD CONSTRAINT part_config_automatic_maintenance_check CHECK (@extschema@.check_automatic_maintenance_value(automatic_maintenance)); ALTER TABLE @extschema@.part_config_sub ADD CONSTRAINT part_config_sub_automatic_maintenance_check CHECK (@extschema@.check_automatic_maintenance_value(sub_automatic_maintenance)); pg_partman-4.6.0/updates/pg_partman--3.0.1--3.0.2.sql000066400000000000000000002416571412761706100214400ustar00rootroot00000000000000-- Allow maintenance background worker to restart itself if it crashes. Previously if the main background worker process exited for any reason, the only way to restart it was to restart the whole postgresql instance. Now it will try to restart itself every 10 minutes. A warning will be issued into the logs if this event occurs. (Github Issue #180). -- NOTE: You will have to restart the PostgreSQL cluster to load the new library with the auto-restart option. -- Remove internal version checking function and use simpler comparison method using integer based version number (Pull Request #183). -- Apply fixes from running plpgsql_check (https://github.com/okbob/plpgsql_check/) DROP FUNCTION IF EXISTS @extschema@.check_version(text); CREATE OR REPLACE FUNCTION 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_trigger_return_null boolean DEFAULT true , p_jobmon boolean DEFAULT true , p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER 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_higher_control_type text; v_higher_parent_control 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_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_partition_id bigint; v_parent_partition_timestamp timestamptz; v_parent_schema text; v_parent_tablename 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_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_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); 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 INTO v_parent_schema, v_parent_tablename 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_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 (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 ... PARITIONED 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; ELSE 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; 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 ) 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 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) 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); 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) 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_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 INTO v_higher_parent_schema, v_higher_parent_table, v_higher_parent_control 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' 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) 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_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' 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 $$; CREATE OR REPLACE FUNCTION 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 , p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER 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_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 INTO v_parent_type, v_parent_interval, v_control, v_run_maint, v_parent_epoch 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 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) 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); 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' 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); 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); 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_jobmon := %L , p_debug := %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 , p_jobmon , p_debug); 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 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 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 * Given security definer since it's called by the trigger functions */ 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 apply_cluster(p_parent_schema text, p_parent_tablename text, p_child_schema text, p_child_tablename text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_indexdef text; v_relkind char; v_row record; v_sql text; BEGIN /* * Function to apply cluster from parent to child table * Adapted from code fork by https://github.com/dturon/pg_partman */ 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'); 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 = p_parent_schema AND c.relname = p_parent_tablename; IF v_relkind = 'p' THEN RAISE EXCEPTION 'This function cannot run on natively partitioned tables'; ELSIF v_relkind IS NULL THEN RAISE EXCEPTION 'Unable to find given table in system catalogs: %.%', p_parent_schema, p_parent_tablename; END IF; WITH parent_info AS ( SELECT c.oid AS parent_oid FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = p_parent_schema::name AND c.relname = p_parent_tablename::name ) SELECT substring(pg_get_indexdef(i.indexrelid) from ' USING .*$') AS index_def INTO v_parent_indexdef FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid JOIN parent_info p ON p.parent_oid = indrelid WHERE i.indisclustered = true; -- Loop over all existing indexes in child table to find one with matching definition FOR v_row IN WITH child_info AS ( SELECT c.oid AS child_oid 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 ) SELECT substring(pg_get_indexdef(i.indexrelid) from ' USING .*$') AS child_indexdef , c.relname AS child_indexname FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid JOIN child_info p ON p.child_oid = indrelid LOOP IF v_row.child_indexdef = v_parent_indexdef THEN v_sql = format('ALTER TABLE %I.%I CLUSTER ON %I', p_child_schema, p_child_tablename, v_row.child_indexname); RAISE DEBUG '%', v_sql; EXECUTE v_sql; END IF; END LOOP; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END; $$; CREATE OR REPLACE FUNCTION apply_privileges(p_parent_schema text, p_parent_tablename text, p_child_schema text, p_child_tablename text, p_job_id bigint DEFAULT NULL) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_child_grant record; v_child_owner text; v_grantees text[]; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_match boolean; v_parent_grant record; v_parent_owner text; v_revoke text; v_row_revoke record; v_sql text; v_step_id bigint; BEGIN /* * Apply privileges and ownership that exist on a given parent to the given child table */ SELECT jobmon INTO v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_schema ||'.'|| p_parent_tablename; IF v_jobmon IS NULL THEN RAISE EXCEPTION 'Given table is not managed by this extention: %.%', p_parent_schema, p_parent_tablename; END IF; 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 = p_parent_schema::name AND c.relname = p_parent_tablename::name; SELECT pg_get_userbyid(c.relowner) INTO v_child_owner 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_parent_owner IS NULL THEN RAISE EXCEPTION 'Given parent table does not exist: %.%', p_parent_schema, p_parent_tablename; END IF; IF v_child_owner IS NULL THEN RAISE EXCEPTION 'Given child table does not exist: %.%', p_child_schema, p_child_tablename; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; END IF; IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN EXECUTE format('SELECT %I.add_job(%L)', v_jobmon_schema, format('PARTMAN APPLYING PRIVILEGES TO CHILD TABLE: %s.%s', p_child_schema, p_child_tablename)) INTO v_job_id; ELSE v_job_id := p_job_id; END IF; EXECUTE format('SELECT %I.add_step(%L, %L)', v_jobmon_schema, v_job_id, format('Setting new child table privileges for %s.%s', p_child_schema, p_child_tablename)) INTO v_step_id; END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.update_step(%L, %L, %L)' , v_jobmon_schema , v_step_id , 'PENDING' , format('Applying privileges on child partition: %s.%s' , p_child_schema , p_child_tablename) ); END IF; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types , grantee FROM @extschema@.table_privs WHERE table_schema = p_parent_schema::name AND table_name = p_parent_tablename::name GROUP BY grantee LOOP -- Compare parent & child grants. Don't re-apply if it already exists v_match := false; v_sql := NULL; FOR v_child_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types , grantee FROM @extschema@.table_privs WHERE table_schema = p_child_schema::name AND table_name = p_child_tablename::name GROUP BY grantee LOOP IF v_parent_grant.types = v_child_grant.types AND v_parent_grant.grantee = v_child_grant.grantee THEN v_match := true; END IF; END LOOP; IF v_match = false THEN IF v_parent_grant.grantee = 'PUBLIC' THEN v_sql := 'GRANT %s ON %I.%I TO %s'; ELSE v_sql := 'GRANT %s ON %I.%I TO %I'; END IF; EXECUTE format(v_sql , array_to_string(v_parent_grant.types, ',') , p_child_schema , p_child_tablename , v_parent_grant.grantee); v_sql := NULL; SELECT string_agg(r, ',') INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN IF v_parent_grant.grantee = 'PUBLIC' THEN v_sql := 'REVOKE %s ON %I.%I FROM %s CASCADE'; ELSE v_sql := 'REVOKE %s ON %I.%I FROM %I CASCADE'; END IF; EXECUTE format(v_sql , v_revoke , p_child_schema , p_child_tablename , v_parent_grant.grantee); v_sql := NULL; END IF; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN FOR v_row_revoke IN SELECT role FROM ( SELECT DISTINCT grantee::text AS role FROM @extschema@.table_privs WHERE table_schema = p_child_schema::name AND table_name = p_child_tablename::name EXCEPT SELECT unnest(v_grantees)) x LOOP IF v_row_revoke.role IS NOT NULL THEN IF v_row_revoke.role = 'PUBLIC' THEN v_sql := 'REVOKE ALL ON %I.%I FROM %s'; ELSE v_sql := 'REVOKE ALL ON %I.%I FROM %I'; END IF; EXECUTE format(v_sql , p_child_schema , p_child_tablename , v_row_revoke.role); END IF; END LOOP; END IF; IF v_parent_owner <> v_child_owner THEN EXECUTE format('ALTER TABLE %I.%I OWNER TO %I' , p_child_schema , p_child_tablename , v_parent_owner); END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.update_step(%L, %L, %L)', v_jobmon_schema, v_step_id, 'OK', 'Done'); IF p_job_id IS NULL THEN EXECUTE format('SELECT %I.close_job(%L)', v_jobmon_schema, v_job_id); END IF; END IF; 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 RE-APPLYING PRIVILEGES TO ALL CHILD TABLES OF: %s'')', v_jobmon_schema, p_parent_tablename) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', v_jobmon_schema, v_job_id, p_parent_tablename) 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 $$; CREATE OR REPLACE FUNCTION create_trigger(p_parent_table text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_function_name text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_relkind char; v_trig_name text; v_trig_sql text; BEGIN /* * Function to create partitioning trigger on parent table */ 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'); SELECT n.nspname, c.relname, c.relkind INTO v_parent_schema, v_parent_tablename, v_relkind 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_relkind = 'p' THEN RAISE EXCEPTION 'This function cannot run on natively partitioned tables'; ELSIF v_relkind IS NULL THEN RAISE EXCEPTION 'Unable to find given table in system catalogs: %.%', v_parent_schema, v_parent_tablename; END IF; v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); -- Ensure function name matches the naming pattern v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); v_trig_sql := format('CREATE TRIGGER %I BEFORE INSERT ON %I.%I FOR EACH ROW EXECUTE PROCEDURE %I.%I()' , v_trig_name , v_parent_schema , v_parent_tablename , v_parent_schema , v_function_name); EXECUTE v_trig_sql; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END $$; /* * Function to ensure a column is dropped in all child tables, no matter when it was created */ CREATE OR REPLACE FUNCTION drop_partition_column(p_parent_table text, p_column text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_parent_oid oid; v_parent_schema text; v_parent_tablename text; v_row record; BEGIN SELECT c.oid, n.nspname, c.relname INTO v_parent_oid, v_parent_schema, v_parent_tablename 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 'Given parent table does not exist: %', p_parent_table; END IF; EXECUTE format('ALTER TABLE %I.%I DROP COLUMN IF EXISTS %I', v_parent_schema, v_parent_tablename, p_column); FOR v_row IN SELECT n.nspname AS child_schema, c.relname AS child_table 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 inhparent = v_parent_oid LOOP EXECUTE format('ALTER TABLE %I.%I DROP COLUMN IF EXISTS %I', v_row.child_schema, v_row.child_table, p_column); END LOOP; END $$; CREATE OR REPLACE FUNCTION partition_data_time( p_parent_table text , p_batch_count int DEFAULT 1 , p_batch_interval interval DEFAULT NULL , p_lock_wait numeric DEFAULT 0 , p_order text DEFAULT 'ASC' , p_analyze boolean DEFAULT true) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_control text; v_control_type text; v_datetime_string text; v_current_partition_name text; v_epoch text; v_last_partition text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_timestamp timestamptz; v_min_partition_timestamp timestamptz; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_expression text; v_partition_interval interval; v_partition_suffix text; v_partition_timestamp timestamptz[]; v_rowcount bigint; v_start_control timestamptz; v_total_rows bigint := 0; v_type text; BEGIN /* * Populate the child table(s) of a time-based partition set with old data from the original parent */ SELECT partition_type , partition_interval::interval , control , datetime_string , epoch INTO v_type , v_partition_interval , v_control , v_datetime_string , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type IN ('partman', 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: No entry in part_config found for non-native partitioning for given table: %', p_parent_table; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'time' THEN IF (v_control_type = 'id' AND v_epoch = 'none') OR v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run on partition set without time based control column or epoch flag set with an id column. Found control: %, epoch: %', v_control_type, v_epoch; END IF; END IF; 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'); IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) END; FOR i IN 1..p_batch_count LOOP IF p_order = 'ASC' THEN EXECUTE format('SELECT min(%s) FROM ONLY %I.%I', v_partition_expression, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSIF p_order = 'DESC' THEN EXECUTE format('SELECT max(%s) FROM ONLY %I.%I', v_partition_expression, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; IF v_start_control IS NULL THEN EXIT; END IF; IF v_type = 'partman' THEN CASE WHEN v_partition_interval = '15 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '15min'::interval * floor(date_part('minute', v_start_control) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '30min'::interval * floor(date_part('minute', v_start_control) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control); WHEN v_partition_interval = '1 day' THEN v_min_partition_timestamp := date_trunc('day', v_start_control); WHEN v_partition_interval = '1 week' THEN v_min_partition_timestamp := date_trunc('week', v_start_control); WHEN v_partition_interval = '1 month' THEN v_min_partition_timestamp := date_trunc('month', v_start_control); WHEN v_partition_interval = '3 months' THEN v_min_partition_timestamp := date_trunc('quarter', v_start_control); WHEN v_partition_interval = '1 year' THEN v_min_partition_timestamp := date_trunc('year', v_start_control); END CASE; ELSIF v_type = 'time-custom' THEN SELECT child_start_time INTO v_min_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition , v_partition_interval::text , p_parent_table); v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; LOOP IF v_start_control >= v_min_partition_timestamp AND v_start_control < v_max_partition_timestamp THEN EXIT; ELSE BEGIN IF v_start_control > v_max_partition_timestamp THEN -- Keep going forward in time, checking if child partition time interval encompasses the current v_start_control value v_min_partition_timestamp := v_max_partition_timestamp; v_max_partition_timestamp := v_max_partition_timestamp + v_partition_interval; ELSE -- Keep going backwards in time, checking if child partition time interval encompasses the current v_start_control value v_max_partition_timestamp := v_min_partition_timestamp; v_min_partition_timestamp := v_min_partition_timestamp - v_partition_interval; END IF; EXCEPTION WHEN datetime_field_overflow THEN RAISE EXCEPTION 'Attempted partition time interval is outside PostgreSQL''s supported time range. Unable to create partition with interval before timestamp % ', v_min_partition_timestamp; END; END IF; END LOOP; END IF; v_partition_timestamp := ARRAY[v_min_partition_timestamp]; IF p_order = 'ASC' THEN -- Ensure batch interval given as parameter doesn't cause maximum to overflow the current partition maximum IF (v_start_control + p_batch_interval) >= (v_min_partition_timestamp + v_partition_interval) THEN v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; ELSE v_max_partition_timestamp := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; -- Ensure batch interval given as parameter doesn't cause minimum to underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_timestamp THEN v_min_partition_timestamp = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM ONLY %I.%I WHERE %s >= %L AND %3$s < %5$L FOR UPDATE NOWAIT' , v_parent_schema , v_parent_tablename , v_partition_expression , v_min_partition_timestamp , v_max_partition_timestamp); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; PERFORM @extschema@.create_partition_time(p_parent_table, v_partition_timestamp, p_analyze); -- This suffix generation code is in create_partition_time() as well v_partition_suffix := to_char(v_min_partition_timestamp, v_datetime_string); v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); EXECUTE format('WITH partition_data AS ( DELETE FROM ONLY %I.%I WHERE %s >= %L AND %3$s < %5$L RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_parent_schema , v_parent_tablename , v_partition_expression , v_min_partition_timestamp , v_max_partition_timestamp , v_parent_schema , v_current_partition_name); GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; PERFORM @extschema@.create_function_time(p_parent_table); EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_total_rows; END $$; CREATE OR REPLACE FUNCTION create_function_id(p_parent_table text, p_job_id bigint DEFAULT NULL) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_control text; v_control_type text; v_count int; v_current_partition_name text; v_current_partition_id bigint; v_datetime_string text; v_final_partition_id bigint; v_function_name 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_position int; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_max bigint; v_next_partition_id bigint; v_next_partition_name text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_optimize_trigger int; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_premake int; v_prev_partition_id bigint; v_prev_partition_name text; v_relkind char; v_row_max_id record; v_run_maint text; v_step_id bigint; v_top_parent text := p_parent_table; v_trig_func text; v_trigger_exception_handling boolean; v_trigger_return_null boolean; v_upsert text; BEGIN /* * Create the trigger function for the parent table of an id-based partition set */ SELECT partition_interval::bigint , control , premake , optimize_trigger , automatic_maintenance , jobmon , trigger_exception_handling , upsert , trigger_return_null INTO v_partition_interval , v_control , v_premake , v_optimize_trigger , v_run_maint , v_jobmon , v_trigger_exception_handling , v_upsert , v_trigger_return_null FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'partman'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no non-native pg_partman config found for %', p_parent_table; END IF; SELECT n.nspname, c.relname, c.relkind INTO v_parent_schema, v_parent_tablename, v_relkind 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_relkind = 'p' THEN RAISE EXCEPTION 'This function cannot run on natively partitioned tables'; ELSIF v_relkind IS NULL THEN RAISE EXCEPTION 'Unable to find given table in system catalogs: %.%', v_parent_schema, v_parent_tablename; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run create_function_id on partition set without id based control column. Found: %', v_control_type; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE FUNCTION: %s', p_parent_table)); ELSE v_job_id = p_job_id; END IF; v_step_id := add_step(v_job_id, format('Creating partition function for table %s', p_parent_table)); END IF; -- Get the highest level top parent if multi-level partitioned in order to get proper max() value below 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 INTO v_higher_parent_schema, v_higher_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 WHERE p.partition_type = 'id'; IF v_higher_parent_table IS NOT NULL THEN -- initially set in DECLARE v_top_parent := v_higher_parent_schema||'.'||v_higher_parent_table; END IF; END LOOP; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_top_parent, 'DESC') LOOP EXECUTE format('SELECT max(%I) FROM %I.%I', v_control, v_row_max_id.partition_schemaname, v_row_max_id.partition_tablename) INTO v_max; IF v_max IS NOT NULL THEN EXIT; END IF; END LOOP; IF v_max IS NULL THEN v_max := 0; END IF; v_current_partition_id = v_max - (v_max % v_partition_interval); v_next_partition_id := v_current_partition_id + v_partition_interval; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_current_partition_id::text, TRUE); v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_current_partition_id bigint; v_current_partition_name text; v_id_position int; v_last_partition text := %L; v_next_partition_id bigint; v_next_partition_name text; v_partition_created boolean; BEGIN IF TG_OP = ''INSERT'' THEN IF NEW.%I >= %s AND NEW.%I < %s THEN ' , v_parent_schema , v_function_name , v_last_partition , v_control , v_current_partition_id , v_control , v_next_partition_id ); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_current_partition_name::name; IF v_count > 0 THEN v_trig_func := v_trig_func || format(' INSERT INTO %I.%I VALUES (NEW.*) %s; ', v_parent_schema, v_current_partition_name, v_upsert); ELSE v_trig_func := v_trig_func || ' -- Child table for current values does not exist in this partition set, so write to parent RETURN NEW;'; END IF; FOR i IN 1..v_optimize_trigger LOOP v_prev_partition_id := v_current_partition_id - (v_partition_interval * i); v_next_partition_id := v_current_partition_id + (v_partition_interval * i); v_final_partition_id := v_next_partition_id + v_partition_interval; v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, v_prev_partition_id::text, TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, v_next_partition_id::text, TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles optimize_trigger being larger than premake (to go back in time further) and edge case of changing optimize_trigger immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_prev_partition_name::name; IF v_count > 0 THEN -- Only handle previous partitions if they're starting above zero IF v_prev_partition_id >= 0 THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %s AND NEW.%I < %s THEN INSERT INTO %I.%I VALUES (NEW.*) %s; ' , v_control , v_prev_partition_id , v_control , v_prev_partition_id + v_partition_interval , v_parent_schema , v_prev_partition_name , v_upsert ); END IF; END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_next_partition_name::name; IF v_count > 0 THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %s AND NEW.%I < %s THEN INSERT INTO %I.%I VALUES (NEW.*) %s;' , v_control , v_next_partition_id , v_control , v_final_partition_id , v_parent_schema , v_next_partition_name , v_upsert ); END IF; END LOOP; v_trig_func := v_trig_func ||format(' ELSE v_current_partition_id := NEW.%I - (NEW.%I %% %s); v_current_partition_name := @extschema@.check_name_length(%L, v_current_partition_id::text, TRUE); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = %L::name AND tablename = v_current_partition_name::name; IF v_count > 0 THEN EXECUTE format(''INSERT INTO %%I.%%I VALUES($1.*) %s'', %L, v_current_partition_name) USING NEW; ELSE RETURN NEW; END IF; END IF;' , v_control , v_control , v_partition_interval , v_parent_tablename , v_parent_schema , v_upsert , v_parent_schema ); v_trig_func := v_trig_func ||' END IF;'; IF v_trigger_return_null IS TRUE THEN v_trig_func := v_trig_func ||' RETURN NULL;'; ELSE v_trig_func := v_trig_func ||' RETURN NEW;'; END IF; IF v_trigger_exception_handling THEN v_trig_func := v_trig_func ||' EXCEPTION WHEN OTHERS THEN RAISE WARNING ''pg_partman insert into child table failed, row inserted into parent (%.%). ERROR: %'', TG_TABLE_SCHEMA, TG_TABLE_NAME, COALESCE(SQLERRM, ''unknown''); RETURN NEW;'; END IF; v_trig_func := v_trig_func ||' END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for current id interval: %s to %s', v_current_partition_id, v_final_partition_id-1)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); 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 FUNCTION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''Partition function maintenance for table %s 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 $$; CREATE OR REPLACE FUNCTION show_partitions (p_parent_table text, p_order text DEFAULT 'ASC') RETURNS TABLE (partition_schemaname text, partition_tablename text) LANGUAGE plpgsql STABLE SECURITY DEFINER AS $$ DECLARE v_control text; v_control_type text; v_datetime_string text; v_epoch text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval text; v_type text; BEGIN /* * Function to list all child partitions in a set in logical order. */ 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'); IF upper(p_order) NOT IN ('ASC', 'DESC') THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RAISE EXCEPTION 'p_order paramter must be one of the following values: ASC, DESC'; END IF; SELECT partition_type , partition_interval , datetime_string , control , epoch INTO v_type , v_partition_interval , v_datetime_string , v_control , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table; SELECT n.nspname, c.relname INTO v_parent_schema, v_parent_tablename 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; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN IF v_partition_interval::interval <> '3 months' OR (v_partition_interval::interval = '3 months' AND v_type = 'time-custom') THEN RETURN QUERY EXECUTE format('SELECT n.nspname::text AS partition_schemaname, c.relname::text AS partition_name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = ''%I.%I''::regclass ORDER BY to_timestamp(substring(c.relname from ((length(c.relname) - position(''p_'' in reverse(c.relname))) + 2) ), %L) %s' , v_parent_schema , v_parent_tablename , v_datetime_string , p_order); ELSE -- For quarterly, to_timestamp() doesn't recognize "Q" in datetime string. -- First order by just the year, then order by the quarter number (should be last character in table name) RETURN QUERY EXECUTE format('SELECT n.nspname::text AS partition_schemaname, c.relname::text AS partition_name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = ''%I.%I''::regclass ORDER BY to_timestamp(substring(c.relname from ((length(c.relname) - position(''p_'' in reverse(c.relname))) + 2) for 4), ''YYYY'') %s , substring(reverse(c.relname) from 1 for 1) %s' , v_parent_schema , v_parent_tablename , p_order , p_order); END IF; ELSIF v_control_type = 'id' THEN RETURN QUERY EXECUTE format('SELECT n.nspname::text AS partition_schemaname, c.relname::text AS partition_name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = ''%I.%I''::regclass ORDER BY substring(c.relname from ((length(c.relname) - position(''p_'' in reverse(c.relname))) + 2) )::bigint %s' , v_parent_schema , v_parent_tablename , p_order); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END $$; pg_partman-4.6.0/updates/pg_partman--3.0.2--3.1.0.sql000066400000000000000000004434761412761706100214430ustar00rootroot00000000000000-- Add support for index & foreign key inheritance in native partitioning for PostgreSQL 10. This is done using a template table that is automatically created in the same schema the pg_partman was installed to. The template table is based off the given parent and any indexes or foreign keys that you would like to be created on child tables can be created on this table. Note that the initial child tables created when create_parent() is called will not have these indexes or FKs unless you run reapply_indexes.py and/or reapply_foreign_keys.py after creating them on the template table. You can also have the initial child tables contain indexes/FKs if you premake the template table and provide it as a parameter to create_parent(). -- Updated reapply_indexes.py and reapply_foreign_keys.py scripts to handle template table feature for native partitioning -- IMPORTANT NOTE: This is hopefully only a temporary option until there is built in support for applying these table properties natively. Once native support for these features is added, this template table will be phased out quickly. -- Retention policy will no longer allow the final child table of a partition set to be dropped. pg_partman always requires at least one child table to work properly. If retention attempts to remove the final child table in a partition set, a WARNING is issued but the child table is not dropped. (Github Issue #189) -- Fixed bug in partition trigger function creation for time-based partitioning where the target tables defined in the trigger code itself may not match the child tables that exist. This could occur only if the create_function_time() function was called independently of normal maintenance and the child tables were behind "now" as of when the function was ran. The target tables in the function are now based on the data that exists in the table as long as infinite_time_partitions is not set. If that is set, the tables defined in the trigger are always based on the current time as of when the function itself is run (same behavior as before). This function is rarely called independently and this is not an issue that would occur with normal maintenance runs since the partition trigger function is never rewritten unless a new child table is actually created. (Github Issue #176) -- The show_partition_name() function now returns the child table schema as a separate field. CREATE TEMP TABLE partman_preserve_privs_temp (statement text); INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.show_partition_name(text, text) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'show_partition_name'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_parent(text, text, text, text, text[], int, text, text, boolean, text, text, boolean, text, boolean, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = ''; DROP FUNCTION @extschema@.show_partition_name(text, text); DROP FUNCTION @extschema@.create_parent(text, text, text, text, text[], int, text, text, boolean, text, text, boolean, boolean, boolean); ALTER TABLE @extschema@.part_config ADD COLUMN template_table text; ALTER TABLE @extschema@.part_config_sub ADD COLUMN sub_template_table text; CREATE FUNCTION show_partition_name(p_parent_table text, p_value text, OUT partition_schema text, OUT partition_table text, OUT suffix_timestamp timestamptz, OUT suffix_id bigint, OUT table_exists boolean) RETURNS record LANGUAGE plpgsql STABLE AS $$ DECLARE v_child_exists text; v_control text; v_control_type text; v_datetime_string text; v_epoch text; v_max_range timestamptz; v_min_range timestamptz; v_parent_schema text; v_parent_tablename text; v_partition_interval text; v_type text; BEGIN /* * Given a parent table and partition value, return the name of the child partition it would go in. * If using epoch time partitioning, give the text representation of the timestamp NOT the epoch integer value (use to_timestamp() to convert epoch values). * Also returns just the suffix value and true if the child table exists or false if it does not */ SELECT partition_type , control , partition_interval , datetime_string , epoch INTO v_type , v_control , v_partition_interval , v_datetime_string , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_type IS NULL THEN RAISE EXCEPTION 'Parent table given is not managed by pg_partman (%)', p_parent_table; END IF; SELECT n.nspname, c.relname INTO v_parent_schema, v_parent_tablename 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_tablename IS NULL THEN RAISE EXCEPTION 'Parent table given does not exist (%)', p_parent_table; END IF; partition_schema := v_parent_schema; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF ( (v_control_type = 'time') OR (v_control_type = 'id' AND v_epoch <> 'none') ) AND v_type <> 'time-custom' THEN CASE WHEN v_partition_interval::interval = '15 mins' THEN suffix_timestamp := date_trunc('hour', p_value::timestamptz) + '15min'::interval * floor(date_part('minute', p_value::timestamptz) / 15.0); WHEN v_partition_interval::interval = '30 mins' THEN suffix_timestamp := date_trunc('hour', p_value::timestamptz) + '30min'::interval * floor(date_part('minute', p_value::timestamptz) / 30.0); WHEN v_partition_interval::interval = '1 hour' THEN suffix_timestamp := date_trunc('hour', p_value::timestamptz); WHEN v_partition_interval::interval = '1 day' THEN suffix_timestamp := date_trunc('day', p_value::timestamptz); WHEN v_partition_interval::interval = '1 week' THEN suffix_timestamp := date_trunc('week', p_value::timestamptz); WHEN v_partition_interval::interval = '1 month' THEN suffix_timestamp := date_trunc('month', p_value::timestamptz); WHEN v_partition_interval::interval = '3 months' THEN suffix_timestamp := date_trunc('quarter', p_value::timestamptz); WHEN v_partition_interval::interval = '1 year' THEN suffix_timestamp := date_trunc('year', p_value::timestamptz); END CASE; partition_schema := v_parent_schema; partition_table := @extschema@.check_name_length(v_parent_tablename, to_char(suffix_timestamp, v_datetime_string), TRUE); ELSIF v_control_type = 'id' AND v_type <> 'time-custom' THEN suffix_id := (p_value::bigint - (p_value::bigint % v_partition_interval::bigint)); partition_table := @extschema@.check_name_length(v_parent_tablename, suffix_id::text, TRUE); ELSIF v_type = 'time-custom' THEN SELECT child_table, lower(partition_range) INTO partition_table, suffix_timestamp FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND partition_range @> p_value::timestamptz; IF partition_table IS NULL THEN SELECT max(upper(partition_range)) INTO v_max_range FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table; SELECT min(lower(partition_range)) INTO v_min_range FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table; IF p_value::timestamptz >= v_max_range THEN suffix_timestamp := v_max_range; LOOP -- Keep incrementing higher until given value is below the upper range suffix_timestamp := suffix_timestamp + v_partition_interval::interval; IF p_value::timestamptz < suffix_timestamp THEN -- Have to subtract one interval because the value would actually be in the partition previous -- to this partition timestamp since the partition names contain the lower boundary suffix_timestamp := suffix_timestamp - v_partition_interval::interval; EXIT; END IF; END LOOP; ELSIF p_value::timestamptz < v_min_range THEN suffix_timestamp := v_min_range; LOOP -- Keep decrementing lower until given value is below or equal to the lower range suffix_timestamp := suffix_timestamp - v_partition_interval::interval; IF p_value::timestamptz >= suffix_timestamp THEN EXIT; END IF; END LOOP; ELSE RAISE EXCEPTION 'Unable to determine a valid child table for the given parent table and value'; END IF; partition_table := @extschema@.check_name_length(v_parent_tablename, to_char(suffix_timestamp, v_datetime_string), TRUE); END IF; END IF; SELECT tablename INTO v_child_exists FROM pg_catalog.pg_tables WHERE schemaname = partition_schema::name AND tablename = partition_table::name; IF v_child_exists IS NOT NULL THEN table_exists := true; ELSE table_exists := false; END IF; RETURN; END $$; CREATE OR REPLACE FUNCTION drop_partition_time(p_parent_table text, p_retention interval DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_control text; v_control_type text; v_count int; v_datetime_string text; v_drop_count int := 0; v_epoch text; v_index record; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_partition_timestamp timestamptz; v_partition_type text; v_retention interval; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_row record; v_step_id bigint; BEGIN /* * Function to drop child tables from a time-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman drop_partition_time')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_time already running.'; RETURN 0; END IF; -- Allow override of configuration options IF p_retention IS NULL THEN SELECT partition_type , control , partition_interval::interval , epoch , retention::interval , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_partition_type , v_control , v_partition_interval , v_epoch , v_retention , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND retention IS NOT NULL; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE SELECT partition_type , partition_interval::interval , epoch , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_partition_type , v_partition_interval , v_epoch , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; v_retention := p_retention; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'time' THEN IF (v_control_type = 'id' AND v_epoch = 'none') OR v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run on partition set without time based control column or epoch flag set with an id column. Found control: %, epoch: %', v_control_type, v_epoch; END IF; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- Loop through child tables of the given parent FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'DESC') LOOP -- pull out datetime portion of partition's tablename to make the next one SELECT child_start_time INTO v_partition_timestamp FROM @extschema@.show_partition_info(v_row.partition_schemaname||'.'||v_row.partition_tablename , v_partition_interval::text , p_parent_table); -- Add one interval since partition names contain the start of the constraint period IF v_retention < (CURRENT_TIMESTAMP - (v_partition_timestamp + v_partition_interval)) THEN -- Do not allow final partition to be dropped SELECT count(*) INTO v_count FROM @extschema@.show_partitions(p_parent_table); IF v_count = 1 THEN RAISE WARNING 'Attempt to drop final partition in partition set % as part of retention policy. If you see this message multiple times for the same table, advise reviewing retention policy and/or data entry into the partition set. Also consider setting "infinite_time_partitions = true" if there are large gaps in data insertion.).', p_parent_table; CONTINUE; END IF; -- Only create a jobmon entry if there's actual retention work done IF v_jobmon_schema IS NOT NULL AND v_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN DROP TIME PARTITION: %s', p_parent_table)); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Detach/Uninherit table %s.%s from %s' , v_row.partition_schemaname , v_row.partition_tablename , p_parent_table)); END IF; IF v_partition_type = 'native' THEN EXECUTE format('ALTER TABLE %I.%I DETACH PARTITION %I.%I' , v_parent_schema , v_parent_tablename , v_row.partition_schemaname , v_row.partition_tablename); ELSE EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_row.partition_schemaname , v_row.partition_tablename , v_parent_schema , v_parent_tablename); END IF; IF v_partition_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_row.partition_schemaname||'.'||v_row.partition_tablename; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop table %s.%s', v_row.partition_schemaname, v_row.partition_tablename)); END IF; EXECUTE format('DROP TABLE %I.%I CASCADE', v_row.partition_schemaname, v_row.partition_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN FOR v_index IN WITH child_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE c1.relname = v_row.partition_tablename::name AND n1.nspname = v_row.partition_schemaname::name ) SELECT c.relname as name , con.conname FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid LEFT JOIN pg_catalog.pg_constraint con ON i.indexrelid = con.conindid JOIN child_info ON i.indrelid = child_info.oid LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop index %s from %s.%s' , v_index.name , v_row.partition_schemaname , v_row.partition_tablename)); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I DROP CONSTRAINT %I' , v_row.partition_schemaname , v_row.partition_tablename , v_index.conname); ELSE EXECUTE format('DROP INDEX %I.%I', v_parent_schema, v_index.name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Moving table %s.%s to schema %s' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema)); END IF; EXECUTE format('ALTER TABLE %I.%I SET SCHEMA %I', v_row.partition_schemaname, v_row.partition_tablename, v_retention_schema); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if -- If child table is a subpartition, remove it from part_config & part_config_sub (should cascade due to FK) DELETE FROM @extschema@.part_config WHERE parent_table = v_row.partition_schemaname||'.'||v_row.partition_tablename; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', format('%s partitions dropped.', v_drop_count)); 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_drop_count; 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 DROP TIME PARTITION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE OR REPLACE FUNCTION drop_partition_id(p_parent_table text, p_retention bigint DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_control text; v_control_type text; v_count int; v_drop_count int := 0; v_index record; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_max bigint; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_partition_id bigint; v_partition_type text; v_retention bigint; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_row record; v_row_max_id record; v_step_id bigint; BEGIN /* * Function to drop child tables from an id-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman drop_partition_id')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_id already running.'; RETURN 0; END IF; IF p_retention IS NULL THEN SELECT partition_interval::bigint , partition_type , control , retention::bigint , retention_keep_table , retention_keep_index , retention_schema , jobmon INTO v_partition_interval , v_partition_type , v_control , v_retention , v_retention_keep_table , v_retention_keep_index , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND retention IS NOT NULL; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE -- Allow override of configuration options SELECT partition_interval::bigint , partition_type , control , retention_keep_table , retention_keep_index , retention_schema , jobmon INTO v_partition_interval , v_partition_type , v_control , v_retention_keep_table , v_retention_keep_index , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; v_retention := p_retention; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'id' THEN RAISE EXCEPTION 'Data type of control column in given partition set is not an integer type'; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%I) FROM %I.%I', v_control, v_row_max_id.partition_schemaname, v_row_max_id.partition_tablename) INTO v_max; IF v_max IS NOT NULL THEN EXIT; END IF; END LOOP; -- Loop through child tables of the given parent FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'ASC') LOOP SELECT child_start_id INTO v_partition_id FROM @extschema@.show_partition_info(v_row.partition_schemaname||'.'||v_row.partition_tablename , v_partition_interval::text , p_parent_table); -- Add one interval since partition names contain the start of the constraint period IF v_retention <= (v_max - (v_partition_id + v_partition_interval)) THEN -- Do not allow final partition to be dropped SELECT count(*) INTO v_count FROM @extschema@.show_partitions(p_parent_table); IF v_count = 1 THEN RAISE WARNING 'Attempt to drop final partition in partition set % as part of retention policy. Advise reviewing retention policy and/or data entry into the partition set.', p_parent_table; CONTINUE; END IF; -- Only create a jobmon entry if there's actual retention work done IF v_jobmon_schema IS NOT NULL AND v_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN DROP ID PARTITION: %s', p_parent_table)); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Detach/Uninherit table %s.%s from %s', v_row.partition_schemaname, v_row.partition_tablename, p_parent_table)); END IF; IF v_partition_type = 'native' THEN EXECUTE format('ALTER TABLE %I.%I DETACH PARTITION %I.%I' , v_parent_schema , v_parent_tablename , v_row.partition_schemaname , v_row.partition_tablename); ELSE EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_row.partition_schemaname , v_row.partition_tablename , v_parent_schema , v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop table %s.%s', v_row.partition_schemaname, v_row.partition_tablename)); END IF; EXECUTE format('DROP TABLE %I.%I CASCADE', v_row.partition_schemaname, v_row.partition_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN FOR v_index IN WITH child_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE c1.relname = v_row.partition_tablename::name AND n1.nspname = v_row.partition_schema::name ) SELECT c.relname as name , con.conname FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid LEFT JOIN pg_catalog.pg_constraint con ON i.indexrelid = con.conindid JOIN child_info ON i.indrelid = child_info.oid LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop index %s from %s.%s' , v_index.name , v_row.partition_schemaname , v_row.partition_tablename)); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I DROP CONSTRAINT %I', v_row.partition_schemaname, v_row.partition_tablename, v_index.conname); ELSE EXECUTE format('DROP INDEX %I.%I', v_row.partition_schemaname, v_index.name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Moving table %s.%s to schema %s' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema)); END IF; EXECUTE format('ALTER TABLE %I.%I SET SCHEMA %I' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if -- If child table is a subpartition, remove it from part_config & part_config_sub (should cascade due to FK) DELETE FROM @extschema@.part_config WHERE parent_table = v_row.partition_schemaname ||'.'||v_row.partition_tablename; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', format('%s partitions dropped.', v_drop_count)); 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_drop_count; 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 DROP ID PARTITION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE OR REPLACE FUNCTION create_function_time(p_parent_table text, p_job_id bigint DEFAULT NULL) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_control text; v_control_type text; v_count int; v_current_partition_name text; v_current_partition_timestamp timestamptz; v_datetime_string text; v_epoch text; v_final_partition_timestamp timestamptz; v_function_name text; v_infinite_time_partitions boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_new_length int; v_next_partition_name text; v_next_partition_timestamp timestamptz; v_parent_schema text; v_parent_tablename text; v_partition_expression text; v_partition_interval interval; v_prev_partition_name text; v_prev_partition_timestamp timestamptz; v_relkind char; v_row_max_time record; v_step_id bigint; v_trig_func text; v_optimize_trigger int; v_table_exists boolean; v_trigger_exception_handling boolean; v_trigger_return_null boolean; v_type text; v_upsert text; BEGIN /* * Create the trigger function for the parent table of a time-based partition set */ SELECT partition_type , partition_interval::interval , epoch , control , optimize_trigger , datetime_string , jobmon , trigger_exception_handling , upsert , trigger_return_null , infinite_time_partitions INTO v_type , v_partition_interval , v_epoch , v_control , v_optimize_trigger , v_datetime_string , v_jobmon , v_trigger_exception_handling , v_upsert , v_trigger_return_null , v_infinite_time_partitions FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'partman' OR partition_type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no non-native pg_partman config found for %', p_parent_table; END IF; SELECT n.nspname, c.relname, c.relkind INTO v_parent_schema, v_parent_tablename, v_relkind 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_relkind = 'p' THEN RAISE EXCEPTION 'This function cannot run on natively partitioned tables'; ELSIF v_relkind IS NULL THEN RAISE EXCEPTION 'Unable to find given table in system catalogs: %.%', v_parent_schema, v_parent_tablename; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'time' THEN IF (v_control_type = 'id' AND v_epoch = 'none') OR v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run on partition set without time based control column or epoch flag set with an id column. Found control: %, epoch: %', v_control_type, v_epoch; END IF; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE FUNCTION: %s', p_parent_table)); ELSE v_job_id = p_job_id; END IF; v_step_id := add_step(v_job_id, format('Creating partition function for table %s', p_parent_table)); END IF; IF v_infinite_time_partitions IS TRUE THEN -- Set it to "now" to line up with maintenance always making new partitions despite no new data -- Also, don't need to bother getting the max value in the partitions v_current_partition_timestamp := CURRENT_TIMESTAMP; ELSE v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) END; FOR v_row_max_time IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%s)::text FROM %I.%I' , v_partition_expression , v_row_max_time.partition_schemaname , v_row_max_time.partition_tablename ) INTO v_current_partition_timestamp; IF v_current_partition_timestamp IS NOT NULL THEN EXIT; END IF; END LOOP; IF v_current_partition_timestamp IS NULL THEN v_current_partition_timestamp := CURRENT_TIMESTAMP; END IF; END IF; -- end infinite time check -- Reset for use in trigger function v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(NEW.%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((NEW.%I/1000)::float)', v_control) ELSE format('NEW.%I', v_control) END; IF v_type = 'partman' THEN v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_partition_name text; v_partition_timestamp timestamptz; BEGIN IF TG_OP = ''INSERT'' THEN ' , v_parent_schema , v_function_name); SELECT suffix_timestamp, partition_table, table_exists INTO v_current_partition_timestamp, v_current_partition_name, v_table_exists FROM @extschema@.show_partition_name(p_parent_table, v_current_partition_timestamp::text); CASE WHEN v_partition_interval = '15 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', %s) + ''15min''::interval * floor(date_part(''minute'', %1$s) / 15.0);' , v_partition_expression); WHEN v_partition_interval = '30 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', %s) + ''30min''::interval * floor(date_part(''minute'', %1$s) / 30.0);' , v_partition_expression); WHEN v_partition_interval = '1 hour' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', %s);', v_partition_expression); WHEN v_partition_interval = '1 day' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''day'', %s);', v_partition_expression); WHEN v_partition_interval = '1 week' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''week'', %s);', v_partition_expression); WHEN v_partition_interval = '1 month' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''month'', %s);', v_partition_expression); WHEN v_partition_interval = '3 months' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''quarter'', %s);', v_partition_expression); WHEN v_partition_interval = '1 year' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''year'', %s);', v_partition_expression); END CASE; v_next_partition_timestamp := v_current_partition_timestamp + v_partition_interval::interval; v_trig_func := v_trig_func ||format(' IF %s >= %L AND %1$s < %3$L THEN ' , v_partition_expression , v_current_partition_timestamp , v_next_partition_timestamp); IF v_table_exists THEN v_trig_func := v_trig_func || format(' INSERT INTO %I.%I VALUES (NEW.*) %s; ', v_parent_schema, v_current_partition_name, v_upsert); ELSE v_trig_func := v_trig_func || ' -- Child table for current values does not exist in this partition set, so write to parent RETURN NEW;'; END IF; FOR i IN 1..v_optimize_trigger LOOP v_prev_partition_timestamp := v_current_partition_timestamp - (v_partition_interval::interval * i); v_next_partition_timestamp := v_current_partition_timestamp + (v_partition_interval::interval * i); v_final_partition_timestamp := v_next_partition_timestamp + (v_partition_interval::interval); v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, to_char(v_prev_partition_timestamp, v_datetime_string), TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, to_char(v_next_partition_timestamp, v_datetime_string), TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles optimize_trigger being larger than premake (to go back in time further) and edge case of changing optimize_trigger immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_prev_partition_name::name; IF v_count > 0 THEN v_trig_func := v_trig_func ||format(' ELSIF %s >= %L AND %1$s < %3$L THEN INSERT INTO %I.%I VALUES (NEW.*) %s;' , v_partition_expression , v_prev_partition_timestamp , v_prev_partition_timestamp + v_partition_interval::interval , v_parent_schema , v_prev_partition_name , v_upsert); END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_next_partition_name::name; IF v_count > 0 THEN v_trig_func := v_trig_func ||format(' ELSIF %s >= %L AND %1$s < %3$L THEN INSERT INTO %I.%I VALUES (NEW.*) %s;' , v_partition_expression , v_next_partition_timestamp , v_final_partition_timestamp , v_parent_schema , v_next_partition_name , v_upsert); END IF; END LOOP; v_trig_func := v_trig_func||format(' ELSE v_partition_name := @extschema@.check_name_length(%L, to_char(v_partition_timestamp, %L), TRUE); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = %L::name AND tablename = v_partition_name::name; IF v_count > 0 THEN EXECUTE format(''INSERT INTO %%I.%%I VALUES($1.*) %s'', %L, v_partition_name) USING NEW; ELSE RETURN NEW; END IF; END IF;' , v_parent_tablename , v_datetime_string , v_parent_schema , v_upsert , v_parent_schema); v_trig_func := v_trig_func ||' END IF;'; IF v_trigger_return_null IS TRUE THEN v_trig_func := v_trig_func ||' RETURN NULL;'; ELSE v_trig_func := v_trig_func ||' RETURN NEW;'; END IF; IF v_trigger_exception_handling THEN v_trig_func := v_trig_func ||' EXCEPTION WHEN OTHERS THEN RAISE WARNING ''pg_partman insert into child table failed, row inserted into parent (%.%). ERROR: %'', TG_TABLE_SCHEMA, TG_TABLE_NAME, COALESCE(SQLERRM, ''unknown''); RETURN NEW;'; END IF; v_trig_func := v_trig_func ||' END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for current time interval: %s to %s' , v_current_partition_timestamp , v_final_partition_timestamp-'1sec'::interval)); END IF; ELSIF v_type = 'time-custom' THEN v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_child_schemaname text; v_child_table text; v_child_tablename text; v_upsert text; BEGIN ' , v_parent_schema , v_function_name); v_trig_func := v_trig_func || format(' SELECT c.child_table, p.upsert INTO v_child_table, v_upsert FROM @extschema@.custom_time_partitions c JOIN @extschema@.part_config p ON c.parent_table = p.parent_table WHERE c.partition_range @> %s AND c.parent_table = %L;' , v_partition_expression , v_parent_schema||'.'||v_parent_tablename); v_trig_func := v_trig_func || ' SELECT schemaname, tablename INTO v_child_schemaname, v_child_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(v_child_table, ''.'', 1)::name AND tablename = split_part(v_child_table, ''.'', 2)::name; IF v_child_schemaname IS NOT NULL AND v_child_tablename IS NOT NULL THEN EXECUTE format(''INSERT INTO %I.%I VALUES ($1.*) %s'', v_child_schemaname, v_child_tablename, v_upsert) USING NEW; ELSE RETURN NEW; END IF;'; IF v_trigger_return_null IS TRUE THEN v_trig_func := v_trig_func ||' RETURN NULL;'; ELSE v_trig_func := v_trig_func ||' RETURN NEW;'; END IF; IF v_trigger_exception_handling THEN v_trig_func := v_trig_func ||' EXCEPTION WHEN OTHERS THEN RAISE WARNING ''pg_partman insert into child table failed, row inserted into parent (%.%). ERROR: %'', TG_TABLE_SCHEMA, TG_TABLE_NAME, COALESCE(SQLERRM, ''unknown''); RETURN NEW;'; END IF; v_trig_func := v_trig_func ||' END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for custom time table: %s', p_parent_table)); END IF; ELSE RAISE EXCEPTION 'ERROR: Invalid time partitioning type given: %', v_type; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); 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 FUNCTION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''Partition function maintenance for table %s 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 $$; CREATE FUNCTION 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_trigger_return_null boolean DEFAULT true , p_template_table text DEFAULT NULL , p_jobmon boolean DEFAULT true , p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER 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_higher_control_type text; v_higher_parent_control 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_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_partition_id bigint; v_parent_partition_timestamp timestamptz; v_parent_schema text; v_parent_tablename 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_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); 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 INTO v_parent_schema, v_parent_tablename 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_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 (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 ... PARITIONED 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); 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 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; 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 ) 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 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) 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); 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) 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); 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 INTO v_higher_parent_schema, v_higher_parent_table, v_higher_parent_control 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' 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) 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); 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' 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 $$; CREATE FUNCTION inherit_template_properties (p_parent_table text, p_child_schema text, p_child_tablename text) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_child_relkind char; v_child_schema text; v_child_tablename text; v_fk_list record; v_index_list record; v_parent_oid oid; v_parent_table text; v_sql text; v_template_oid oid; v_template_table text; 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 */ SELECT parent_table, template_table INTO v_parent_table, v_template_table 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; SELECT c.oid INTO v_template_oid FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = split_part(v_template_table, '.', 1)::name AND c.relname = split_part(v_template_table, '.', 2)::name; IF v_child_tablename IS NULL THEN RAISE EXCEPTION 'Unable to find configured template table in system catalogs: %', v_template_table; END IF; -- Index creation FOR v_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 , c.relname AS index_name FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid WHERE i.indrelid = v_template_oid AND i.indisvalid ORDER BY 1 LOOP 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, '","') || '"'); RAISE DEBUG '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 INDEX ON %I.%I %s', v_child_schema, v_child_tablename, v_index_list.statement); RAISE DEBUG 'Create index: %', v_sql; EXECUTE v_sql; END IF; END LOOP; -- End index creation -- Foreign key creation 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 'Create FK: %', v_sql; EXECUTE v_sql; END LOOP; -- End foreign key creation RETURN true; END $$; CREATE OR REPLACE FUNCTION create_partition_id(p_parent_table text, p_partition_ids bigint[], p_analyze boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_control_type text; v_exists text; v_grantees text[]; v_hasoids boolean; v_id bigint; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_grant record; v_parent_schema text; v_parent_tablename text; v_parent_tablespace text; v_partition_interval bigint; v_partition_created boolean := false; v_partition_name text; v_partition_type text; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_sub_control text; v_sub_partition_type text; v_sub_id_max bigint; v_sub_id_min bigint; v_template_table text; v_unlogged char; BEGIN /* * Function to create id partitions */ SELECT control , partition_type , partition_interval , inherit_fk , jobmon , template_table INTO v_control , v_partition_type , v_partition_interval , v_inherit_fk , v_jobmon , v_template_table FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT n.nspname, c.relname, t.spcname INTO v_parent_schema, v_parent_tablename, v_parent_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 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; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'id' THEN RAISE EXCEPTION 'ERROR: Given parent table is not set up for id/serial partitioning'; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'id'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; FOREACH v_id IN ARRAY p_partition_ids LOOP -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_id_min IS NOT NULL THEN IF v_id < v_sub_id_min OR v_id > v_sub_id_max THEN CONTINUE; END IF; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_id::text, TRUE); -- If child table already exists, skip creation -- Have to check pg_class because if subpartitioned, table will not be in pg_tables SELECT c.relname INTO v_exists 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_partition_name::name; IF v_exists IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_id||' to '||(v_id + v_partition_interval)-1); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c 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; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; -- Close parentheses on LIKE are below due to differing requirements of native subpartitioning v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING STORAGE INCLUDING COMMENTS ' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); SELECT sub_partition_type, sub_control INTO v_sub_partition_type, v_sub_control FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; IF v_sub_partition_type = 'native' THEN -- NOTE: Need to handle this differently when index inheritance is supported natively -- Cannot include indexes since they cannot exist on native parents. v_sql := v_sql || format(') PARTITION BY RANGE (%I) ', v_sub_control); ELSE v_sql := v_sql || format(' INCLUDING INDEXES) ', v_sub_control); END IF; SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c 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; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; IF v_partition_type = 'native' THEN IF v_template_table IS NOT NULL THEN PERFORM @extschema@.inherit_template_properties(p_parent_table, v_parent_schema, v_partition_name); END IF; EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , v_id , v_id + v_partition_interval); ELSE EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %s AND %I < %s )' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_control , v_id , v_control , v_id + v_partition_interval); EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I', v_parent_schema, v_partition_name, v_parent_schema, v_parent_tablename); -- Indexes cannot be created on the parent, so clustering cannot be used for native yet. PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); -- Foreign keys to other tables not supported in native IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; END IF; -- NOTE: Privileges currently not automatically inherited for native PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_automatic_maintenance , sub_infinite_time_partitions , sub_jobmon , sub_trigger_exception_handling , sub_template_table FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Subpartitioning '||v_partition_name); END IF; 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_inherit_fk := %L , p_epoch := %L , p_template_table := %L , p_jobmon := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_automatic_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_template_table , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions , trigger_exception_handling = v_row.sub_trigger_exception_handling WHERE parent_table = v_parent_schema||'.'||v_partition_name; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; -- end sub partitioning LOOP -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table, p_job_id := v_job_id, p_debug := p_debug); v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s', p_parent_table)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE OR REPLACE FUNCTION create_partition_time(p_parent_table text, p_partition_times timestamptz[], p_analyze boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_control_type text; v_datetime_string text; v_epoch text; v_exists smallint; v_grantees text[]; v_hasoids boolean; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_grant record; v_parent_schema text; v_parent_tablename text; v_part_col text; v_partition_created boolean := false; v_partition_name text; v_partition_suffix text; v_parent_tablespace text; v_partition_expression text; v_partition_interval interval; v_partition_timestamp_end timestamptz; v_partition_timestamp_start timestamptz; v_quarter text; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_sub_control text; v_sub_parent text; v_sub_partition_type text; v_sub_timestamp_max timestamptz; v_sub_timestamp_min timestamptz; v_template_table text; v_trunc_value text; v_time timestamptz; v_partition_type text; v_unlogged char; v_year text; BEGIN /* * Function to create a child table in a time-based partition set */ SELECT partition_type , control , partition_interval , epoch , inherit_fk , jobmon , datetime_string , template_table INTO v_partition_type , v_control , v_partition_interval , v_epoch , v_inherit_fk , v_jobmon , v_datetime_string , v_template_table FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT n.nspname, c.relname, t.spcname INTO v_parent_schema, v_parent_tablename, v_parent_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 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; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'time' THEN IF (v_control_type = 'id' AND v_epoch = 'none') OR v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run on partition set without time based control column or epoch flag set with an id column. Found control: %, epoch: %', v_control_type, v_epoch; END IF; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::timestamptz, sub_max::timestamptz INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'time'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) END; IF p_debug THEN RAISE NOTICE 'create_partition_time: v_partition_expression: %', v_partition_expression; END IF; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_timestamp_start := v_time; BEGIN v_partition_timestamp_end := v_time + v_partition_interval; 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_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_time||' skipped'); CONTINUE; END; -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_timestamp_min IS NOT NULL THEN IF v_time < v_sub_timestamp_min OR v_time > v_sub_timestamp_max THEN CONTINUE; END IF; END IF; -- This suffix generation code is in partition_data_time() as well v_partition_suffix := to_char(v_time, v_datetime_string); v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); -- Check if child exists. SELECT count(*) INTO v_exists 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_partition_name::name; IF v_exists > 0 THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Creating new partition %s.%s with interval from %s to %s' , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end-'1sec'::interval)); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c 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; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; -- Close parentheses on LIKE are below due to differing requirements of native subpartitioning v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING STORAGE INCLUDING COMMENTS ' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); SELECT sub_partition_type, sub_control INTO v_sub_partition_type, v_sub_control FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; IF v_sub_partition_type = 'native' THEN -- NOTE: Need to handle this differently when index inheritance is supported natively -- Cannot include indexes since they cannot exist on native parents v_sql := v_sql || format(') PARTITION BY RANGE (%I) ', v_sub_control); ELSE v_sql := v_sql || format(' INCLUDING INDEXES) ', v_sub_control); END IF; SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c 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; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; IF p_debug THEN RAISE NOTICE 'create_partition_time v_sql: %', v_sql; END IF; EXECUTE v_sql; IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; IF v_partition_type = 'native' THEN IF v_template_table IS NOT NULL THEN PERFORM @extschema@.inherit_template_properties(p_parent_table, v_parent_schema, v_partition_name); END IF; IF v_epoch = 'none' THEN -- Attach with normal, time-based values for native constraint EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end); ELSE -- Must attach with integer based values for native constraint and epoch IF v_epoch = 'seconds' THEN EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , EXTRACT('epoch' FROM v_partition_timestamp_start) , EXTRACT('epoch' FROM v_partition_timestamp_end)); ELSIF v_epoch = 'milliseconds' THEN EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , EXTRACT('epoch' FROM v_partition_timestamp_start) * 1000 , EXTRACT('epoch' FROM v_partition_timestamp_end) * 1000); END IF; -- Create secondary, time-based constraint since native's constraint is already integer based EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%s >= %L AND %4$s < %6$L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_partition_expression , v_partition_timestamp_start , v_partition_timestamp_end); END IF; ELSE -- Non-native always gets time-based constraint EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%s >= %L AND %4$s < %6$L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_partition_expression , v_partition_timestamp_start , v_partition_timestamp_end); IF v_epoch = 'seconds' THEN -- Non-native needs secondary, integer based constraint for epoch EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_int_check' , v_control , EXTRACT('epoch' from v_partition_timestamp_start) , v_control , EXTRACT('epoch' from v_partition_timestamp_end) ); ELSIF v_epoch = 'milliseconds' THEN EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_int_check' , v_control , EXTRACT('epoch' from v_partition_timestamp_start) * 1000 , v_control , EXTRACT('epoch' from v_partition_timestamp_end) * 1000); END IF; EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); -- If custom time, set extra config options. IF v_partition_type = 'time-custom' THEN INSERT INTO @extschema@.custom_time_partitions (parent_table, child_table, partition_range) VALUES ( p_parent_table, v_parent_schema||'.'||v_partition_name, tstzrange(v_partition_timestamp_start, v_partition_timestamp_end, '[)') ); END IF; -- Indexes cannot be created on the parent, so clustering cannot be used for native yet. PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); -- Foreign keys to other tables not supported in native IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; END IF; -- end native check -- NOTE: Privileges currently not automatically inherited for native PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_automatic_maintenance , sub_infinite_time_partitions , sub_jobmon , sub_trigger_exception_handling , sub_template_table FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Subpartitioning %s.%s', v_parent_schema, v_partition_name)); END IF; 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_inherit_fk := %L , p_epoch := %L , p_template_table := %L , p_jobmon := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_automatic_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_template_table , v_row.sub_jobmon); IF p_debug THEN RAISE NOTICE 'create_partition_time (create_parent loop): %', v_sql; END IF; EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions , trigger_exception_handling = v_row.sub_trigger_exception_handling WHERE parent_table = v_parent_schema||'.'||v_partition_name; END LOOP; -- end sub partitioning LOOP -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table, p_job_id := v_job_id, p_debug := p_debug); v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s. Attempted intervals: %s', p_parent_table, p_partition_times)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; 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_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE OR REPLACE FUNCTION 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 , p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER 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' 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); 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); 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 , p_debug := %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 , p_debug); 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 undo_partition_native(p_parent_table text, p_target_table text, p_batch_count int DEFAULT 1, p_batch_interval text DEFAULT NULL, p_keep_table boolean DEFAULT true, p_lock_wait numeric DEFAULT 0, OUT partitions_undone int, OUT rows_undone bigint) RETURNS record LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_batch_interval_id bigint; v_batch_interval_time interval; v_batch_loop_count int := 0; v_child_loop_total bigint := 0; v_child_table text; v_control text; v_control_type text; v_child_min_id bigint; v_child_min_time timestamptz; v_epoch text; v_jobmon boolean; v_jobmon_schema text; v_job_id bigint; v_inner_loop_count int; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_new_search_path text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_expression text; v_partition_interval text; v_relkind char; v_row record; v_rowcount bigint; v_step_id bigint; v_sub_count int; v_target_schema text; v_target_tablename text; v_template_schema text; v_template_siblings int; v_template_table text; v_template_tablename text; v_total bigint := 0; v_undo_count int := 0; BEGIN /* * Function to undo native partitioning. * Moves data to new, target table since data cannot be moved to parent. * Leaves old parent table as is and does not change name of new table. * Note that target schema can be different than old parent. * Should work on native partitioned tables not managed by pg_partman as well. */ v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition_native')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition_native already running.'; partitions_undone = -1; RETURN; END IF; IF p_parent_table = p_target_table THEN RAISE EXCEPTION 'Target table cannot be the same as the parent table'; END IF; SELECT partition_interval::text , control , jobmon , epoch , template_table INTO v_partition_interval , v_control , v_jobmon , v_epoch , v_template_table FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'native'; IF v_control IS NULL THEN RAISE EXCEPTION 'No native configuration found for pg_partman for given parent table: %', p_parent_table; END IF; SELECT n.nspname, c.relname, c.relkind INTO v_parent_schema, v_parent_tablename, v_relkind 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_control IS NULL THEN -- not managed by pg_partman -- Note in docs that this only works on undoing the same partition data types that pg_partman itself supports (single column, time/id, no expressions) IF v_relkind <> 'p' THEN RAISE EXCEPTION 'Given parent table is not managed by pg_partman nor natively partitioned.'; END IF; SELECT a.attname INTO v_control 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_parent_schema::name AND c.relname = v_parent_tablename::name AND attnum IN (SELECT unnest(partattrs) FROM pg_catalog.pg_partitioned_table p WHERE a.attrelid = p.partrelid); IF v_control IS NULL THEN RAISE EXCEPTION 'Unexpected error encountered looking up native partition column. Please report bug to extension author.'; END IF; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN IF p_batch_interval IS NULL THEN v_batch_interval_time := v_partition_interval::interval; ELSE v_batch_interval_time := p_batch_interval::interval; END IF; ELSIF v_control_type = 'id' THEN IF p_batch_interval IS NULL THEN v_batch_interval_id := v_partition_interval::bigint; ELSE v_batch_interval_id := p_batch_interval::bigint; END IF; ELSE RAISE EXCEPTION 'Data type of control column in given partition set must be either data/time or integer.'; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Check if any child tables are themselves partitioned or part of an inheritance tree. Prevent undo at this level if so. -- Need to lock child tables at all levels before multi-level undo can be performed safely. FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table) LOOP SELECT count(*) INTO v_sub_count FROM pg_catalog.pg_inherits i JOIN pg_catalog.pg_class c ON i.inhparent = c.oid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_row.partition_tablename::name AND n.nspname = v_row.partition_schemaname::name; IF v_sub_count > 0 THEN RAISE EXCEPTION 'Child table for this parent has child table(s) itself (%). Run undo partitioning on this table or remove inheritance first to ensure all data is properly moved to parent', v_row.partition_schemaname||'.'||v_row.partition_tablename; END IF; END LOOP; SELECT n.nspname, c.relname INTO v_target_schema, v_target_tablename FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = split_part(p_target_table, '.', 1)::name AND c.relname = split_part(p_target_table, '.', 2)::name; IF v_target_tablename IS NULL THEN RAISE EXCEPTION 'Given target table not found in system catalogs: %', p_target_table; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN UNDO PARTITIONING: %s', p_parent_table)); v_step_id := add_step(v_job_id, format('Undoing partitioning for table %s', p_parent_table)); END IF; v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) END; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process.'); END IF; <> LOOP -- Get ordered list of child table in set. Store in variable one at a time per loop until none are left or batch count is reached. -- This easily allows it to loop over same child table until empty or move onto next child table after it's dropped SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC') LIMIT 1; EXIT outer_child_loop WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Removing child partition: %s.%s', v_parent_schema, v_child_table)); END IF; IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN EXECUTE format('SELECT min(%s) FROM %I.%I', v_partition_expression, v_parent_schema, v_child_table) INTO v_child_min_time; ELSIF v_control_type = 'id' THEN EXECUTE format('SELECT min(%s) FROM %I.%I', v_partition_expression, v_parent_schema, v_child_table) INTO v_child_min_id; END IF; IF v_child_min_time IS NULL AND v_child_min_id IS NULL THEN -- No rows left in this child table. Remove from partition set. -- lockwait timeout for table drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_child_table); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; partitions_undone = -1; RETURN; END IF; END IF; -- END p_lock_wait IF v_lock_obtained := FALSE; -- reset for reuse later EXECUTE format('ALTER TABLE %I.%I DETACH PARTITION %I.%I' , v_parent_schema , v_parent_tablename , v_parent_schema , v_child_table); IF p_keep_table = false THEN EXECUTE format('DROP TABLE %I.%I', v_parent_schema, v_child_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table DROPPED. Moved %s rows to target table', v_child_loop_total)); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table DETACHED from parent, not DROPPED. Moved %s rows to target table', v_child_loop_total)); END IF; END IF; v_undo_count := v_undo_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached CONTINUE outer_child_loop; -- skip data moving steps below END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM %I.%I WHERE %I <= %L FOR UPDATE NOWAIT' , v_parent_schema , v_child_table , v_control , v_child_min_time + (v_batch_interval_time * v_inner_loop_count)); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; partitions_undone = -1; RETURN; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval EXECUTE format('WITH move_data AS ( DELETE FROM %I.%I WHERE %s <= %L RETURNING *) INSERT INTO %I.%I SELECT * FROM move_data' , v_parent_schema , v_child_table , v_partition_expression , v_child_min_time + (v_batch_interval_time * v_inner_loop_count) , v_target_schema , v_target_tablename); GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Moved %s rows to target table.', v_child_loop_total)); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; -- Check again if table is empty and go to outer loop again to drop it if so EXECUTE format('SELECT min(%s) FROM %I.%I', v_partition_expression, v_parent_schema, v_child_table) INTO v_child_min_time; CONTINUE outer_child_loop WHEN v_child_min_time IS NULL; ELSIF v_control_type = 'id' THEN IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM %I.%I WHERE %I <= %L FOR UPDATE NOWAIT' , v_parent_schema , v_child_table , v_control , v_child_min_id + (v_batch_interval_id * v_inner_loop_count)); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; partitions_undone = -1; RETURN; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval EXECUTE format('WITH move_data AS ( DELETE FROM %I.%I WHERE %s <= %L RETURNING *) INSERT INTO %I.%I SELECT * FROM move_data' , v_parent_schema , v_child_table , v_partition_expression , v_child_min_id + (v_batch_interval_id * v_inner_loop_count) , v_target_schema , v_target_tablename); GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Moved %s rows to target table.', v_child_loop_total)); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; -- Check again if table is empty and go to outer loop again to drop it if so EXECUTE format('SELECT min(%s) FROM %I.%I', v_partition_expression, v_parent_schema, v_child_table) INTO v_child_min_id; CONTINUE outer_child_loop WHEN v_child_min_id IS NULL; END IF; -- end v_control_type check EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC') LIMIT 1; IF v_child_table IS NULL THEN DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; -- Check if any other config entries still have this template table and don't remove if so -- Allows other sibling/parent tables to still keep using in case entire partition set isn't being undone SELECT count(*) INTO v_template_siblings FROM @extschema@.part_config WHERE template_table = v_template_table; 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(v_template_table, '.', 1)::name AND c.relname = split_part(v_template_table, '.', 2)::name; IF v_template_siblings = 0 AND v_template_tablename IS NOT NULL THEN EXECUTE format('DROP TABLE IF EXISTS %I.%I', v_template_schema, v_template_tablename); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Moved % row(s) to the target table. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', format('Moved %s row(s) to the target table. Removed %s partitions.', v_total, v_undo_count)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); partitions_undone := v_undo_count; rows_undone := v_total; 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 UNDO PARTITIONING: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; -- Restore dropped object privileges DO $$ DECLARE v_row record; BEGIN FOR v_row IN SELECT statement FROM partman_preserve_privs_temp LOOP IF v_row.statement IS NOT NULL THEN EXECUTE v_row.statement; END IF; END LOOP; END $$; DROP TABLE IF EXISTS partman_preserve_privs_temp; pg_partman-4.6.0/updates/pg_partman--3.1.0--3.1.1.sql000066400000000000000000001141051412761706100214230ustar00rootroot00000000000000-- Fixed custom time intervals to work properly with native partitioning. (Github Issue #200) -- Fixed template table owner to be the same as the partitioned table owner when the template table is not manually set. Note this will not change any existing template table ownerships, it only affects newly created native partitions sets. (Github Issue #203) CREATE OR REPLACE FUNCTION 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_trigger_return_null boolean DEFAULT true , p_template_table text DEFAULT NULL , p_jobmon boolean DEFAULT true , p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER 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_higher_control_type text; v_higher_parent_control 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_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_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_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); 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 INTO v_parent_schema, v_parent_tablename 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_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 (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 ... PARITIONED 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 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; 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 ) 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 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) 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); 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) 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); 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 INTO v_higher_parent_schema, v_higher_parent_table, v_higher_parent_control 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' 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) 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); 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' 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 $$; CREATE OR REPLACE FUNCTION show_partition_name(p_parent_table text, p_value text, OUT partition_schema text, OUT partition_table text, OUT suffix_timestamp timestamptz, OUT suffix_id bigint, OUT table_exists boolean) RETURNS record LANGUAGE plpgsql STABLE AS $$ DECLARE v_child_end_time timestamptz; v_child_exists text; v_child_larger boolean := false; v_child_smaller boolean := false; v_child_start_time timestamptz; v_control text; v_control_type text; v_datetime_string text; v_epoch text; v_given_timestamp timestamptz; v_max_range timestamptz; v_min_range timestamptz; v_parent_schema text; v_parent_tablename text; v_partition_interval text; v_row record; v_type text; BEGIN /* * Given a parent table and partition value, return the name of the child partition it would go in. * If using epoch time partitioning, give the text representation of the timestamp NOT the epoch integer value (use to_timestamp() to convert epoch values). * Also returns just the suffix value and true if the child table exists or false if it does not */ SELECT partition_type , control , partition_interval , datetime_string , epoch INTO v_type , v_control , v_partition_interval , v_datetime_string , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_type IS NULL THEN RAISE EXCEPTION 'Parent table given is not managed by pg_partman (%)', p_parent_table; END IF; SELECT n.nspname, c.relname INTO v_parent_schema, v_parent_tablename 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_tablename IS NULL THEN RAISE EXCEPTION 'Parent table given does not exist (%)', p_parent_table; END IF; partition_schema := v_parent_schema; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF ( (v_control_type = 'time') OR (v_control_type = 'id' AND v_epoch <> 'none') ) THEN v_given_timestamp := p_value::timestamptz; FOR v_row IN SELECT partition_schemaname ||'.'|| partition_tablename AS child_table FROM @extschema@.show_partitions(p_parent_table, 'DESC') LOOP SELECT child_start_time INTO v_child_start_time FROM @extschema@.show_partition_info(v_row.child_table, v_partition_interval, p_parent_table); -- Don't use child_end_time from above function to avoid edge cases around user supplied timestamps v_child_end_time := v_child_start_time + v_partition_interval::interval; IF v_given_timestamp >= v_child_end_time THEN -- given value is higher than any existing child table. handled below. v_child_larger := true; EXIT; END IF; IF v_given_timestamp >= v_child_start_time THEN -- found target child table v_child_smaller := false; suffix_timestamp := v_child_start_time; EXIT; END IF; -- Should only get here if no matching child table was found. handled below. v_child_smaller := true; END LOOP; IF v_child_start_time IS NULL OR v_child_end_time IS NULL THEN -- This should never happen since there should never be a partition set without children. -- Handling just in case so issues can be reported with context RAISE EXCEPTION 'Unexpected code path encountered in show_partition_name(). Please report this issue to author with relevant partition config info.'; END IF; IF v_child_larger THEN LOOP -- keep adding interval until found v_child_start_time := v_child_start_time + v_partition_interval::interval; v_child_end_time := v_child_end_time + v_partition_interval::interval; IF v_given_timestamp >= v_child_start_time AND v_given_timestamp < v_child_end_time THEN suffix_timestamp := v_child_start_time; EXIT; END IF; END LOOP; ELSIF v_child_smaller THEN LOOP -- keep subtracting interval until found v_child_start_time := v_child_start_time - v_partition_interval::interval; v_child_end_time := v_child_end_time - v_partition_interval::interval; IF v_given_timestamp >= v_child_start_time AND v_given_timestamp < v_child_end_time THEN suffix_timestamp := v_child_start_time; EXIT; END IF; END LOOP; END IF; partition_table := @extschema@.check_name_length(v_parent_tablename, to_char(suffix_timestamp, v_datetime_string), TRUE); ELSIF v_control_type = 'id' AND v_type <> 'time-custom' THEN suffix_id := (p_value::bigint - (p_value::bigint % v_partition_interval::bigint)); partition_table := @extschema@.check_name_length(v_parent_tablename, suffix_id::text, TRUE); ELSIF v_type = 'time-custom' THEN SELECT child_table, lower(partition_range) INTO partition_table, suffix_timestamp FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND partition_range @> p_value::timestamptz; IF partition_table IS NULL THEN SELECT max(upper(partition_range)) INTO v_max_range FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table; SELECT min(lower(partition_range)) INTO v_min_range FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table; IF p_value::timestamptz >= v_max_range THEN suffix_timestamp := v_max_range; LOOP -- Keep incrementing higher until given value is below the upper range suffix_timestamp := suffix_timestamp + v_partition_interval::interval; IF p_value::timestamptz < suffix_timestamp THEN -- Have to subtract one interval because the value would actually be in the partition previous -- to this partition timestamp since the partition names contain the lower boundary suffix_timestamp := suffix_timestamp - v_partition_interval::interval; EXIT; END IF; END LOOP; ELSIF p_value::timestamptz < v_min_range THEN suffix_timestamp := v_min_range; LOOP -- Keep decrementing lower until given value is below or equal to the lower range suffix_timestamp := suffix_timestamp - v_partition_interval::interval; IF p_value::timestamptz >= suffix_timestamp THEN EXIT; END IF; END LOOP; ELSE RAISE EXCEPTION 'Unable to determine a valid child table for the given parent table and value'; END IF; partition_table := @extschema@.check_name_length(v_parent_tablename, to_char(suffix_timestamp, v_datetime_string), TRUE); END IF; END IF; SELECT tablename INTO v_child_exists FROM pg_catalog.pg_tables WHERE schemaname = partition_schema::name AND tablename = partition_table::name; IF v_child_exists IS NOT NULL THEN table_exists := true; ELSE table_exists := false; END IF; RETURN; END $$; pg_partman-4.6.0/updates/pg_partman--3.1.1--3.1.2.sql000066400000000000000000000015011412761706100214200ustar00rootroot00000000000000-- NOTE: No changes to the core extension code contained in this update. This file is only here for version upgrade continuity. -- Fixed reapply_indexes.py to work properly with native partitioning. Previously it would recreate all indexes on the template table. If you've run this script on a natively partitioned set already, please review the indexes that exist on your template table and ensure only the ones you need exist. You will likely have to re-run the script again to get the child indexes set properly and you will need to set the the --recreate_all option. If your primary key has changed, also set the --primary option. Thanks to Jawshua on Github for finding the bug and providing a fix. (Github Pull Request #206). -- Added note to documentation (pg_partman.md) about support for the new IDENTITY feature in PG10. pg_partman-4.6.0/updates/pg_partman--3.1.2--3.1.3.sql000066400000000000000000001145131412761706100214320ustar00rootroot00000000000000-- Allow tablespace inheritance from the template table in PostgreSQL 10+. Note that PG10 currently completely ignores the TABLESPACE flag to the CREATE TABLE command on a native partition parent table. So a natively partitioned parent can never live in a different tablespace other than the default. Tablespaces must be declared for each child table and pg_partman can now do that using the template table the same way it does for indexes and foreign keys (Github Issue #212). -- Obey the inherit_fk config option for natively partitioned tables. Previously foreign keys would always be inherited even if this was set to false. -- Do not allow empty string value for retention_schema in configuration tables. If this update causes an error related to this, please ensure your retention schema is actually set or leave the column value NULL (Github #214). -- Moved the pg_partman github repository to its own dedicated organization at http://github.com/pgpartman. Please update all links that previously referred to the keithf4 personal repository. ALTER TABLE @extschema@.part_config ADD CONSTRAINT retention_schema_not_empty_chk CHECK (retention_schema <> ''); ALTER TABLE @extschema@.part_config_sub ADD CONSTRAINT retention_schema_not_empty_chk CHECK (sub_retention_schema <> ''); CREATE OR REPLACE FUNCTION inherit_template_properties (p_parent_table text, p_child_schema text, p_child_tablename text) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_child_relkind char; v_child_schema text; v_child_tablename text; v_fk_list record; v_index_list record; v_inherit_fk boolean; v_parent_oid oid; v_parent_table text; v_sql text; v_template_oid oid; v_template_table text; v_template_tablespace text; 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 */ 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; 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 = split_part(v_template_table, '.', 1)::name AND c.relname = split_part(v_template_table, '.', 2)::name; 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 (only required in 10) IF current_setting('server_version_num')::int > 100000 AND current_setting('server_version_num')::int < 110000 THEN FOR v_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 , c.relname AS index_name FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid WHERE i.indrelid = v_template_oid AND i.indisvalid ORDER BY 1 LOOP 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, '","') || '"'); RAISE DEBUG '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 INDEX ON %I.%I %s', v_child_schema, v_child_tablename, v_index_list.statement); RAISE DEBUG 'Create index: %', v_sql; EXECUTE v_sql; END IF; END LOOP; END IF; -- End index creation -- Foreign key creation 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 'Create FK: %', v_sql; EXECUTE v_sql; END LOOP; END IF; -- End foreign key creation -- Tablespace inheritance IF 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); EXECUTE v_sql; RAISE DEBUG 'Alter tablespace: %', v_sql; END IF; RETURN true; END $$; CREATE OR REPLACE FUNCTION create_partition_id(p_parent_table text, p_partition_ids bigint[], p_analyze boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_control_type text; v_exists text; v_grantees text[]; v_hasoids boolean; v_id bigint; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_grant record; v_parent_schema text; v_parent_tablename text; v_parent_tablespace text; v_partition_interval bigint; v_partition_created boolean := false; v_partition_name text; v_partition_type text; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_sub_control text; v_sub_partition_type text; v_sub_id_max bigint; v_sub_id_min bigint; v_template_table text; v_unlogged char; BEGIN /* * Function to create id partitions */ SELECT control , partition_type , partition_interval , inherit_fk , jobmon , template_table INTO v_control , v_partition_type , v_partition_interval , v_inherit_fk , v_jobmon , v_template_table FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT n.nspname, c.relname, t.spcname INTO v_parent_schema, v_parent_tablename, v_parent_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 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; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'id' THEN RAISE EXCEPTION 'ERROR: Given parent table is not set up for id/serial partitioning'; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'id'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; FOREACH v_id IN ARRAY p_partition_ids LOOP -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_id_min IS NOT NULL THEN IF v_id < v_sub_id_min OR v_id > v_sub_id_max THEN CONTINUE; END IF; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_id::text, TRUE); -- If child table already exists, skip creation -- Have to check pg_class because if subpartitioned, table will not be in pg_tables SELECT c.relname INTO v_exists 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_partition_name::name; IF v_exists IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_id||' to '||(v_id + v_partition_interval)-1); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c 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; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; -- Close parentheses on LIKE are below due to differing requirements of native subpartitioning v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING STORAGE INCLUDING COMMENTS ' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); SELECT sub_partition_type, sub_control INTO v_sub_partition_type, v_sub_control FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; IF v_sub_partition_type = 'native' THEN -- NOTE: Need to handle this differently when index inheritance is supported natively -- Cannot include indexes since they cannot exist on native parents. v_sql := v_sql || format(') PARTITION BY RANGE (%I) ', v_sub_control); ELSE v_sql := v_sql || format(' INCLUDING INDEXES) ', v_sub_control); END IF; SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c 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; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; IF v_partition_type = 'native' THEN IF v_template_table IS NOT NULL THEN PERFORM @extschema@.inherit_template_properties(p_parent_table, v_parent_schema, v_partition_name); END IF; EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , v_id , v_id + v_partition_interval); ELSE -- Handled in inherit_template_properties for native because CREATE TABLE ignores TABLESPACE flag for native partition parents IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %s AND %I < %s )' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_control , v_id , v_control , v_id + v_partition_interval); EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I', v_parent_schema, v_partition_name, v_parent_schema, v_parent_tablename); -- Indexes cannot be created on the parent, so clustering cannot be used for native yet. PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); -- Foreign keys to other tables not supported on native parent tables IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; END IF; -- NOTE: Privileges currently not automatically inherited for native PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_automatic_maintenance , sub_infinite_time_partitions , sub_jobmon , sub_trigger_exception_handling , sub_template_table FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Subpartitioning '||v_partition_name); END IF; 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_inherit_fk := %L , p_epoch := %L , p_template_table := %L , p_jobmon := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_automatic_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_template_table , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions , trigger_exception_handling = v_row.sub_trigger_exception_handling WHERE parent_table = v_parent_schema||'.'||v_partition_name; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; -- end sub partitioning LOOP -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table, p_job_id := v_job_id, p_debug := p_debug); v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s', p_parent_table)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE OR REPLACE FUNCTION create_partition_time(p_parent_table text, p_partition_times timestamptz[], p_analyze boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_control_type text; v_datetime_string text; v_epoch text; v_exists smallint; v_grantees text[]; v_hasoids boolean; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_grant record; v_parent_schema text; v_parent_tablename text; v_part_col text; v_partition_created boolean := false; v_partition_name text; v_partition_suffix text; v_parent_tablespace text; v_partition_expression text; v_partition_interval interval; v_partition_timestamp_end timestamptz; v_partition_timestamp_start timestamptz; v_quarter text; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_sub_control text; v_sub_parent text; v_sub_partition_type text; v_sub_timestamp_max timestamptz; v_sub_timestamp_min timestamptz; v_template_table text; v_trunc_value text; v_time timestamptz; v_partition_type text; v_unlogged char; v_year text; BEGIN /* * Function to create a child table in a time-based partition set */ SELECT partition_type , control , partition_interval , epoch , inherit_fk , jobmon , datetime_string , template_table INTO v_partition_type , v_control , v_partition_interval , v_epoch , v_inherit_fk , v_jobmon , v_datetime_string , v_template_table FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT n.nspname, c.relname, t.spcname INTO v_parent_schema, v_parent_tablename, v_parent_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 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; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'time' THEN IF (v_control_type = 'id' AND v_epoch = 'none') OR v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run on partition set without time based control column or epoch flag set with an id column. Found control: %, epoch: %', v_control_type, v_epoch; END IF; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::timestamptz, sub_max::timestamptz INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'time'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) END; IF p_debug THEN RAISE NOTICE 'create_partition_time: v_partition_expression: %', v_partition_expression; END IF; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_timestamp_start := v_time; BEGIN v_partition_timestamp_end := v_time + v_partition_interval; 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_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_time||' skipped'); CONTINUE; END; -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_timestamp_min IS NOT NULL THEN IF v_time < v_sub_timestamp_min OR v_time > v_sub_timestamp_max THEN CONTINUE; END IF; END IF; -- This suffix generation code is in partition_data_time() as well v_partition_suffix := to_char(v_time, v_datetime_string); v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); -- Check if child exists. SELECT count(*) INTO v_exists 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_partition_name::name; IF v_exists > 0 THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Creating new partition %s.%s with interval from %s to %s' , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end-'1sec'::interval)); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c 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; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; -- Close parentheses on LIKE are below due to differing requirements of native subpartitioning v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING STORAGE INCLUDING COMMENTS ' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); SELECT sub_partition_type, sub_control INTO v_sub_partition_type, v_sub_control FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; IF v_sub_partition_type = 'native' THEN -- NOTE: Need to handle this differently when index inheritance is supported natively -- Cannot include indexes since they cannot exist on native parents v_sql := v_sql || format(') PARTITION BY RANGE (%I) ', v_sub_control); ELSE v_sql := v_sql || format(' INCLUDING INDEXES) ', v_sub_control); END IF; SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c 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; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; IF p_debug THEN RAISE NOTICE 'create_partition_time v_sql: %', v_sql; END IF; EXECUTE v_sql; IF v_partition_type = 'native' THEN IF v_template_table IS NOT NULL THEN PERFORM @extschema@.inherit_template_properties(p_parent_table, v_parent_schema, v_partition_name); END IF; IF v_epoch = 'none' THEN -- Attach with normal, time-based values for native constraint EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end); ELSE -- Must attach with integer based values for native constraint and epoch IF v_epoch = 'seconds' THEN EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , EXTRACT('epoch' FROM v_partition_timestamp_start) , EXTRACT('epoch' FROM v_partition_timestamp_end)); ELSIF v_epoch = 'milliseconds' THEN EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , EXTRACT('epoch' FROM v_partition_timestamp_start) * 1000 , EXTRACT('epoch' FROM v_partition_timestamp_end) * 1000); END IF; -- Create secondary, time-based constraint since native's constraint is already integer based EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%s >= %L AND %4$s < %6$L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_partition_expression , v_partition_timestamp_start , v_partition_timestamp_end); END IF; ELSE -- Handled in inherit_template_properties for native because CREATE TABLE ignores TABLESPACE flag for native partition parents IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; -- Non-native always gets time-based constraint EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%s >= %L AND %4$s < %6$L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_partition_expression , v_partition_timestamp_start , v_partition_timestamp_end); IF v_epoch = 'seconds' THEN -- Non-native needs secondary, integer based constraint for epoch EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_int_check' , v_control , EXTRACT('epoch' from v_partition_timestamp_start) , v_control , EXTRACT('epoch' from v_partition_timestamp_end) ); ELSIF v_epoch = 'milliseconds' THEN EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_int_check' , v_control , EXTRACT('epoch' from v_partition_timestamp_start) * 1000 , v_control , EXTRACT('epoch' from v_partition_timestamp_end) * 1000); END IF; EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); -- If custom time, set extra config options. IF v_partition_type = 'time-custom' THEN INSERT INTO @extschema@.custom_time_partitions (parent_table, child_table, partition_range) VALUES ( p_parent_table, v_parent_schema||'.'||v_partition_name, tstzrange(v_partition_timestamp_start, v_partition_timestamp_end, '[)') ); END IF; -- Indexes cannot be created on the parent, so clustering cannot be used for native yet. PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); -- Foreign keys to other tables not supported in native IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; END IF; -- end native check -- NOTE: Privileges currently not automatically inherited for native PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_automatic_maintenance , sub_infinite_time_partitions , sub_jobmon , sub_trigger_exception_handling , sub_template_table FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Subpartitioning %s.%s', v_parent_schema, v_partition_name)); END IF; 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_inherit_fk := %L , p_epoch := %L , p_template_table := %L , p_jobmon := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_automatic_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_template_table , v_row.sub_jobmon); IF p_debug THEN RAISE NOTICE 'create_partition_time (create_parent loop): %', v_sql; END IF; EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions , trigger_exception_handling = v_row.sub_trigger_exception_handling WHERE parent_table = v_parent_schema||'.'||v_partition_name; END LOOP; -- end sub partitioning LOOP -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table, p_job_id := v_job_id, p_debug := p_debug); v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s. Attempted intervals: %s', p_parent_table, p_partition_times)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; 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_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; pg_partman-4.6.0/updates/pg_partman--3.1.3--3.2.0.sql000066400000000000000000002202551412761706100214320ustar00rootroot00000000000000-- Added option to include newly created child tables in a subscription for use in logical replication. PG10+ Only. (Github PR #218). -- Signature of create_parent() function has changed, so ensure calls are accounting for new argument. -- Does not currently work for subpartition tables if they are natively partitioned. -- Fix so that unique indexes that exist on the the template table are created on child tables. PG10+ native partitioning only. (GitHub PR #220, Issue #225). -- Fix so that indexes inherited from template table are put into the same tablespace as defined on the template table. PG10+ native partitioning only. (Github Issue #223). CREATE TEMP TABLE partman_preserve_privs_temp (statement text); INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_parent(text, text, text, text, text[], int, text, text, boolean, text, text, text[], boolean, text, boolean, boolean ) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_parent'; DROP FUNCTION @extschema@.create_parent(text, text, text, text, text[], int, text, text, boolean, text, text, boolean, text, boolean, boolean); ALTER TABLE @extschema@.part_config ADD COLUMN publications text[]; ALTER TABLE @extschema@.part_config ADD CONSTRAINT publications_no_empty_set_chk CHECK (publications <> '{}'); CREATE OR REPLACE FUNCTION inherit_template_properties (p_parent_table text, p_child_schema text, p_child_tablename text) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_child_relkind char; v_child_schema text; v_child_tablename text; v_fk_list record; v_index_list record; v_inherit_fk boolean; v_parent_oid oid; v_parent_table text; v_sql text; v_template_oid oid; v_template_table text; v_template_tablespace text; 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 */ 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; 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 = split_part(v_template_table, '.', 1)::name AND c.relname = split_part(v_template_table, '.', 2)::name; 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) -- TODO Add check here for PG11 to only allow unique, non-partition key indexes on template IF current_setting('server_version_num')::int > 100000 AND current_setting('server_version_num')::int < 110000 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 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 %', v_index_list.tablespace_name); END IF; RAISE DEBUG '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 %', v_index_list.tablespace_name); END IF; RAISE DEBUG 'Create index: %', v_sql; EXECUTE v_sql; END IF; END LOOP; END IF; -- End index creation -- Foreign key creation (PG10 only) -- TODO Add check in for PG11 to not allow FK inheritance from template 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 'Create FK: %', v_sql; EXECUTE v_sql; END LOOP; END IF; -- End foreign key creation -- Tablespace inheritance IF 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); EXECUTE v_sql; RAISE DEBUG 'Alter tablespace: %', v_sql; END IF; RETURN true; END $$; CREATE FUNCTION apply_publications(p_parent_table text, p_child_schema text, p_child_tablename text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_publications text[]; v_row record; v_sql text; BEGIN /* * Function to ATLER PUBLICATION ... ADD TABLE to support logical replication */ SELECT c.publications INTO v_publications FROM @extschema@.part_config c WHERE c.parent_table = p_parent_table; -- Loop over all publicaions which the table needs to be added to FOR v_row IN SELECT pubname FROM unnest(v_publications) AS pubname LOOP v_sql = format('ALTER PUBLICATION %I ADD TABLE %I.%I', v_row.pubname, p_child_schema, p_child_tablename); RAISE DEBUG '%', v_sql; EXECUTE v_sql; END LOOP; END; $$; CREATE FUNCTION 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 , p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER 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_higher_control_type text; v_higher_parent_control 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_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_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); 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 INTO v_parent_schema, v_parent_tablename 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_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 (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 ... PARITIONED 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 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; 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; 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 ) 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 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) 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); 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) 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_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 INTO v_higher_parent_schema, v_higher_parent_table, v_higher_parent_control 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' 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) 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_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' 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 $$; CREATE OR REPLACE FUNCTION create_partition_id(p_parent_table text, p_partition_ids bigint[], p_analyze boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_control_type text; v_exists text; v_grantees text[]; v_hasoids boolean; v_id bigint; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_grant record; v_parent_schema text; v_parent_tablename text; v_parent_tablespace text; v_partition_interval bigint; v_partition_created boolean := false; v_partition_name text; v_partition_type text; v_publications text[]; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_sub_control text; v_sub_partition_type text; v_sub_id_max bigint; v_sub_id_min bigint; v_template_table text; v_unlogged char; BEGIN /* * Function to create id partitions */ SELECT control , partition_type , partition_interval , inherit_fk , jobmon , template_table , publications INTO v_control , v_partition_type , v_partition_interval , v_inherit_fk , v_jobmon , v_template_table , v_publications FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT n.nspname, c.relname, t.spcname INTO v_parent_schema, v_parent_tablename, v_parent_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 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; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'id' THEN RAISE EXCEPTION 'ERROR: Given parent table is not set up for id/serial partitioning'; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'id'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; FOREACH v_id IN ARRAY p_partition_ids LOOP -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_id_min IS NOT NULL THEN IF v_id < v_sub_id_min OR v_id > v_sub_id_max THEN CONTINUE; END IF; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_id::text, TRUE); -- If child table already exists, skip creation -- Have to check pg_class because if subpartitioned, table will not be in pg_tables SELECT c.relname INTO v_exists 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_partition_name::name; IF v_exists IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_id||' to '||(v_id + v_partition_interval)-1); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c 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; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; -- Close parentheses on LIKE are below due to differing requirements of native subpartitioning v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING STORAGE INCLUDING COMMENTS ' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); SELECT sub_partition_type, sub_control INTO v_sub_partition_type, v_sub_control FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; IF v_sub_partition_type = 'native' THEN -- NOTE: Need to handle this differently when index inheritance is supported natively -- Cannot include indexes since they cannot exist on native parents. v_sql := v_sql || format(') PARTITION BY RANGE (%I) ', v_sub_control); ELSE v_sql := v_sql || format(' INCLUDING INDEXES) ', v_sub_control); END IF; SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c 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; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; IF v_partition_type = 'native' THEN IF v_template_table IS NOT NULL THEN PERFORM @extschema@.inherit_template_properties(p_parent_table, v_parent_schema, v_partition_name); END IF; EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , v_id , v_id + v_partition_interval); ELSE -- Handled in inherit_template_properties for native because CREATE TABLE ignores TABLESPACE flag for native partition parents IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %s AND %I < %s )' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_control , v_id , v_control , v_id + v_partition_interval); EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I', v_parent_schema, v_partition_name, v_parent_schema, v_parent_tablename); -- Indexes cannot be created on the parent, so clustering cannot be used for native yet. PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); -- Foreign keys to other tables not supported on native parent tables IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; END IF; -- NOTE: Privileges currently not automatically inherited for native PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_automatic_maintenance , sub_infinite_time_partitions , sub_jobmon , sub_trigger_exception_handling , sub_template_table FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Subpartitioning '||v_partition_name); END IF; 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_inherit_fk := %L , p_epoch := %L , p_template_table := %L , p_jobmon := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_automatic_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_template_table , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions , trigger_exception_handling = v_row.sub_trigger_exception_handling WHERE parent_table = v_parent_schema||'.'||v_partition_name; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; -- end sub partitioning LOOP -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table, p_job_id := v_job_id, p_debug := p_debug); IF v_publications IS NOT NULL THEN -- NOTE: Publications currently not supported on parent table, but are supported on the table partitions if individually assigned. PERFORM @extschema@.apply_publications(p_parent_table, v_parent_schema, v_partition_name); END IF; v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s', p_parent_table)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE OR REPLACE FUNCTION create_partition_time(p_parent_table text, p_partition_times timestamptz[], p_analyze boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_control_type text; v_datetime_string text; v_epoch text; v_exists smallint; v_grantees text[]; v_hasoids boolean; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_grant record; v_parent_schema text; v_parent_tablename text; v_part_col text; v_partition_created boolean := false; v_partition_name text; v_partition_suffix text; v_parent_tablespace text; v_partition_expression text; v_partition_interval interval; v_partition_timestamp_end timestamptz; v_partition_timestamp_start timestamptz; v_publications text[]; v_quarter text; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_sub_control text; v_sub_parent text; v_sub_partition_type text; v_sub_timestamp_max timestamptz; v_sub_timestamp_min timestamptz; v_template_table text; v_trunc_value text; v_time timestamptz; v_partition_type text; v_unlogged char; v_year text; BEGIN /* * Function to create a child table in a time-based partition set */ SELECT partition_type , control , partition_interval , epoch , inherit_fk , jobmon , datetime_string , template_table , publications INTO v_partition_type , v_control , v_partition_interval , v_epoch , v_inherit_fk , v_jobmon , v_datetime_string , v_template_table , v_publications FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT n.nspname, c.relname, t.spcname INTO v_parent_schema, v_parent_tablename, v_parent_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 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; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'time' THEN IF (v_control_type = 'id' AND v_epoch = 'none') OR v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run on partition set without time based control column or epoch flag set with an id column. Found control: %, epoch: %', v_control_type, v_epoch; END IF; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::timestamptz, sub_max::timestamptz INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'time'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) END; IF p_debug THEN RAISE NOTICE 'create_partition_time: v_partition_expression: %', v_partition_expression; END IF; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_timestamp_start := v_time; BEGIN v_partition_timestamp_end := v_time + v_partition_interval; 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_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_time||' skipped'); CONTINUE; END; -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_timestamp_min IS NOT NULL THEN IF v_time < v_sub_timestamp_min OR v_time > v_sub_timestamp_max THEN CONTINUE; END IF; END IF; -- This suffix generation code is in partition_data_time() as well v_partition_suffix := to_char(v_time, v_datetime_string); v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); -- Check if child exists. SELECT count(*) INTO v_exists 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_partition_name::name; IF v_exists > 0 THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Creating new partition %s.%s with interval from %s to %s' , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end-'1sec'::interval)); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c 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; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; -- Close parentheses on LIKE are below due to differing requirements of native subpartitioning v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING STORAGE INCLUDING COMMENTS ' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); SELECT sub_partition_type, sub_control INTO v_sub_partition_type, v_sub_control FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; IF v_sub_partition_type = 'native' THEN -- NOTE: Need to handle this differently when index inheritance is supported natively -- Cannot include indexes since they cannot exist on native parents v_sql := v_sql || format(') PARTITION BY RANGE (%I) ', v_sub_control); ELSE v_sql := v_sql || format(' INCLUDING INDEXES) ', v_sub_control); END IF; SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c 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; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; IF p_debug THEN RAISE NOTICE 'create_partition_time v_sql: %', v_sql; END IF; EXECUTE v_sql; IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; IF v_partition_type = 'native' THEN IF v_template_table IS NOT NULL THEN PERFORM @extschema@.inherit_template_properties(p_parent_table, v_parent_schema, v_partition_name); END IF; IF v_epoch = 'none' THEN -- Attach with normal, time-based values for native constraint EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end); ELSE -- Must attach with integer based values for native constraint and epoch IF v_epoch = 'seconds' THEN EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , EXTRACT('epoch' FROM v_partition_timestamp_start) , EXTRACT('epoch' FROM v_partition_timestamp_end)); ELSIF v_epoch = 'milliseconds' THEN EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , EXTRACT('epoch' FROM v_partition_timestamp_start) * 1000 , EXTRACT('epoch' FROM v_partition_timestamp_end) * 1000); END IF; -- Create secondary, time-based constraint since native's constraint is already integer based EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%s >= %L AND %4$s < %6$L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_partition_expression , v_partition_timestamp_start , v_partition_timestamp_end); END IF; ELSE -- Non-native always gets time-based constraint EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%s >= %L AND %4$s < %6$L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_partition_expression , v_partition_timestamp_start , v_partition_timestamp_end); IF v_epoch = 'seconds' THEN -- Non-native needs secondary, integer based constraint for epoch EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_int_check' , v_control , EXTRACT('epoch' from v_partition_timestamp_start) , v_control , EXTRACT('epoch' from v_partition_timestamp_end) ); ELSIF v_epoch = 'milliseconds' THEN EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_int_check' , v_control , EXTRACT('epoch' from v_partition_timestamp_start) * 1000 , v_control , EXTRACT('epoch' from v_partition_timestamp_end) * 1000); END IF; EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); -- If custom time, set extra config options. IF v_partition_type = 'time-custom' THEN INSERT INTO @extschema@.custom_time_partitions (parent_table, child_table, partition_range) VALUES ( p_parent_table, v_parent_schema||'.'||v_partition_name, tstzrange(v_partition_timestamp_start, v_partition_timestamp_end, '[)') ); END IF; -- Indexes cannot be created on the parent, so clustering cannot be used for native yet. PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); -- Foreign keys to other tables not supported in native IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; END IF; -- end native check -- NOTE: Privileges currently not automatically inherited for native PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_automatic_maintenance , sub_infinite_time_partitions , sub_jobmon , sub_trigger_exception_handling , sub_template_table FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Subpartitioning %s.%s', v_parent_schema, v_partition_name)); END IF; 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_inherit_fk := %L , p_epoch := %L , p_template_table := %L , p_jobmon := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_automatic_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_template_table , v_row.sub_jobmon); IF p_debug THEN RAISE NOTICE 'create_partition_time (create_parent loop): %', v_sql; END IF; EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions , trigger_exception_handling = v_row.sub_trigger_exception_handling WHERE parent_table = v_parent_schema||'.'||v_partition_name; END LOOP; -- end sub partitioning LOOP -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table, p_job_id := v_job_id, p_debug := p_debug); IF v_publications IS NOT NULL THEN -- NOTE: Publications currently not supported on parent table, but are supported on the table partitions if individually assigned. PERFORM @extschema@.apply_publications(p_parent_table, v_parent_schema, v_partition_name); END IF; v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s. Attempted intervals: %s', p_parent_table, p_partition_times)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; 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_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; -- Restore dropped object privileges DO $$ DECLARE v_row record; BEGIN FOR v_row IN SELECT statement FROM partman_preserve_privs_temp LOOP IF v_row.statement IS NOT NULL THEN EXECUTE v_row.statement; END IF; END LOOP; END $$; DROP TABLE IF EXISTS partman_preserve_privs_temp; pg_partman-4.6.0/updates/pg_partman--3.2.0--3.2.1.sql000066400000000000000000000140611412761706100214250ustar00rootroot00000000000000-- Fix bug with inheriting indexes from template tables with native partitioning. Was throwing syntax error related to format() function. Thanks to iwarford on github for reporting the issue (Github Issue #223). CREATE OR REPLACE FUNCTION inherit_template_properties (p_parent_table text, p_child_schema text, p_child_tablename text) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_child_relkind char; v_child_schema text; v_child_tablename text; v_fk_list record; v_index_list record; v_inherit_fk boolean; v_parent_oid oid; v_parent_table text; v_sql text; v_template_oid oid; v_template_table text; v_template_tablespace text; 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 */ 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; 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 = split_part(v_template_table, '.', 1)::name AND c.relname = split_part(v_template_table, '.', 2)::name; 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) -- TODO Add check here for PG11 to only allow unique, non-partition key indexes on template IF current_setting('server_version_num')::int > 100000 AND current_setting('server_version_num')::int < 110000 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 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 '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 'Create index: %', v_sql; EXECUTE v_sql; END IF; END LOOP; END IF; -- End index creation -- Foreign key creation (PG10 only) -- TODO Add check in for PG11 to not allow FK inheritance from template 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 'Create FK: %', v_sql; EXECUTE v_sql; END LOOP; END IF; -- End foreign key creation -- Tablespace inheritance IF 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); EXECUTE v_sql; RAISE DEBUG 'Alter tablespace: %', v_sql; END IF; RETURN true; END $$; pg_partman-4.6.0/updates/pg_partman--3.2.1--4.0.0.sql000066400000000000000000007720221412761706100214340ustar00rootroot00000000000000-- Initial release to provide support PostgreSQL 11. All PG11 functionality is subject to change before its final stable release. -- IMPORTANT POSTGRESQL 11 UPGRADE STEPS -- All foreign keys, any non-unique indexes, and any unique indexes that include the partition key must be created on the actual parent table and removed from the template table for those property inheritances to continue working. Foreign key inheritance from the template table no longer works at all. -- Any unique indexes or primary keys that do not include the partition column must still use the template table in order to be inherited to child tables. -- Indexes defined on the parent will take precidence over those defined on the template table. Duplicate indexes should not happen. If they do, please report this as a bug. -- Note that if this version is installed on PG10 and older, the procedures will not be created. A migration script to update pg_partman to include those will be made available once PG11 stable has been released. -- Major change in privilege/security of this extension. SECURITY DEFINER is no longer in use to cause most of pg_partman's functions to run as the owner of the called function. This is mostly due to the requirements to support PROCEDUREs in PG11, but it's also generally a better idea for security in the long run. -- It is recommended to create a dedicated role for pg_partman maintenance runs and give it the privileges outlined in the setup section of the top level README. -- The role running maintenance must now have permissions to create tables in the target schema. If you start seeing permission errors after upgrading, this is most likely what needs to be fixed. -- The role running maintenance must now always be the owner of the tables it is managing. This shouldn't be too drastic of a change since previously it was making the maintenace function owner the owner of the partitioned tables. However, if you've changed partition table ownership after initial creation, you may run into issues until you set the pg_partman role as the owner. -- If you are using logical replication, the partman maintenance role must be the owner of the publication in use. -- As a bonus, the ALTER DEFAULT PRIVILEGES feature should now be much easier to use since you have a dedicated role for maintaining partitions. Check the documentation for this feature so that you can set any read/write privileges you want new partition sets to have at the time they are initially created. -- Two functions retain their SECURITY DEFINER status: -- check_name_length() - Used inside the trigger function and I don't want to break existing installations. It's an IMMUTABLE function so the security implications aren't that great. -- apply_privileges() - Requires access to pg_authid & also potentially changes object ownership. -- If you're using pg_jobmon, privileges for that extension are managed independently of pg_partman. See that extension's documentation for privileges it requires. -- For PostgreSQL 11+, started conversion of some python scripts into PROCEDUREs that are now installed within the database. Scripts are still available for pre-11 versions, but will be phased out as soon as PG10 is no longer under support. -- Note that the user that calls these procedures must have permissions to write to the partition set and create/drop partitions in the set. -- partition_data.py script is now the partition_data_proc() PROCEDURE. -- undo_partition.py script is now the undo_partition_proc() PROCEDURE. -- reapply_constraints.py is now the reapply_constraints_proc() PROCEDURE. -- reapply_foreign_keys.py not converted since it's not needed for PG11+. Foreign keys can be applied to the parent and will automatically be inherited. -- reapply_indexes.py was not converted yet because concurrent index creation is not yet possible inside a PROCEDURE. -- Unable to turn autovacuum off/on reliably in PROCEDURE as was previously done with python scripts. If autovac is an issue when (un)partitioning large tables, recommend manually disabling it. -- Consolidated undo_partition_time(), undo_partition_id(), and undo_partition_native() functions into a single undo_partition() function. -- The old undo_partition() function that just copied data from child tables to the parent has been removed. The only utility this really had was a possible way to support undoing partition sets not maintained by pg_partman. With native partitioning now in place, its continued usability is now questionable and it was also causing confusion since people were using it and not the previous ones specialized for pg_partman. Can incorporate back in if someone can provide a further beneficial use case. -- For PG11+, a DEFAULT partition is automatically created as part of the partition set to handle any data that does not have a matching child table. -- It's just given a _default suffix on the existing parent table name. -- Support within pg_partman for moving data out of the default partition is not included in 4.0.0, but will be included in a future release. -- New parameter to show_partitions() can include the DEFAULT in its output, but it's not output unless specifically requested since it's not commonly needed in internal code where it's mostly used. -- For native partitioning, added option to give a "source" table to the partition_data*() functions that is different from the parent table of the partition set. This assists with partitioning an existing table to a natively partitioned table. -- For PostgreSQL 11+, run_maintenance_proc() has been added and should cause much less contention when running against many large partition sets in a single call. -- For PG11+, this is the preferred maintenance method to run over the old run_maintenance() function. The old one will continue to work for the next few major releases, but please update all pg_partman maintenence calls in your environment to use the new procedure. -- Simplified --type argument for undo_partition.py script for PG versions 10 and below. Similar to create_parent(), values are now 'partman' or 'native'. -- Maintenance that creates new partitions on PG11+ and is using native partitioning will no longer analyze the partition sets by default. This should greatly cut down on maintenance run time and contention issues. If you're noticing any odd query plans, especially if you're using additionally configured constraints, you may need to schedule some manual analyzes or you can set the p_analyze parameter in either the function or procedure call to "true". -- Fixed bug in run_maintenance() with subpartitioning where it would throw an error if retention dropped one of the parent tables undergoing maintenance. -- Fixed template table inheritance for PG10 not working on minor version PostgreSQL 10.0 (but you really shouldn't be running this version anymore). -- Fixed compilation errors for background worker against PostgreSQL 11. -- ######################## START POSTGRESQL 11 ONLY SECTION ############################## DO $pg11only$ DECLARE v_partition_data_sql text; v_reapply_constraints_sql text; v_reapply_foreign_keys_sql text; v_run_maintenance_sql text; v_undo_partition_sql text; BEGIN IF current_setting('server_version_num')::int >= 110000 THEN -- ######################## START POSTGRESQL 11 ONLY SECTION ############################## -- Syntax check during extension updating doesn't even allow the CREATE PROCEDURE statement to exist. Using dollar quoting w/ EXECUTE to get around it in this conditional IF block. v_partition_data_sql := $partition_data$ CREATE PROCEDURE @extschema@.partition_data_proc (p_parent_table text, p_interval text DEFAULT NULL, p_batch int DEFAULT NULL, p_wait int DEFAULT 1, p_source_table text DEFAULT NULL, p_order text DEFAULT 'ASC', p_lock_wait int DEFAULT 0, p_lock_wait_tries int DEFAULT 10, p_quiet boolean DEFAULT false) LANGUAGE plpgsql AS $$ DECLARE v_adv_lock boolean; v_batch_count int := 0; v_control text; v_control_type text; v_is_autovac_off boolean := false; v_lockwait_count int := 0; v_parent_schema text; v_parent_tablename text; v_row record; v_rows_moved bigint; v_source_schema text; v_source_tablename text; v_sql text; v_total bigint := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman partition_data_proc'), hashtext(p_parent_table)); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman partition_data_proc already running for given parent table: %.', p_parent_table; RETURN; END IF; SELECT control INTO v_control FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: No entry in part_config found for given table: %', p_parent_table; END IF; SELECT n.nspname, c.relname INTO v_parent_schema, v_parent_tablename 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_tablename IS NULL THEN RAISE EXCEPTION 'Unable to find given parent table in system catalogs. Ensure it is schema qualified: %', p_parent_table; END IF; IF p_source_table IS NOT NULL THEN SELECT n.nspname, c.relname INTO v_source_schema, v_source_tablename FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = split_part(p_source_table, '.', 1)::name AND c.relname = split_part(p_source_table, '.', 2)::name; IF v_source_tablename IS NULL THEN RAISE EXCEPTION 'Unable to find given source table in system catalogs. Ensure it is schema qualified: %', p_source_table; END IF; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); /* -- Currently no way to catch exception and reset autovac settings back to normal. Until I can do that, leaving this feature out for now -- Leaving the functions to turn off/reset in to let people do that manually if desired IF p_autovacuum_on = false THEN -- Add this parameter back to definition when this is working -- Turn off autovac for parent, source table if set, and all child tables v_is_autovac_off := @extschema@.autovacuum_off(v_parent_schema, v_parent_tablename, v_source_schema, v_source_tablename); COMMIT; END IF; */ v_sql := format('SELECT %I.partition_data_%s (%L, p_lock_wait := %L, p_order := %L, p_analyze := false' , '@extschema@', v_control_type, p_parent_table, p_lock_wait, p_order); IF p_interval IS NOT NULL THEN v_sql := v_sql || format(', p_batch_interval := %L', p_interval); END IF; IF p_source_table IS NOT NULL THEN v_sql := v_sql || format(', p_source_table := %L', p_source_table); END IF; v_sql := v_sql || ')'; RAISE DEBUG 'partition_data sql: %', v_sql; LOOP EXECUTE v_sql INTO v_rows_moved; -- If lock wait timeout, do not increment the counter IF v_rows_moved != -1 THEN v_batch_count := v_batch_count + 1; v_total := v_total + v_rows_moved; v_lockwait_count := 0; ELSE v_lockwait_count := v_lockwait_count + 1; IF v_lockwait_count > p_lock_wait_tries THEN RAISE EXCEPTION 'Quitting due to inability to get lock on next batch of rows to be moved'; END IF; END IF; IF p_quiet = false THEN IF v_rows_moved > 0 THEN RAISE NOTICE 'Batch: %, Rows moved: %', v_batch_count, v_rows_moved; ELSIF v_rows_moved = -1 THEN RAISE NOTICE 'Unable to obtain row locks for data to be moved. Trying again...'; END IF; END IF; -- If no rows left or given batch argument limit is reached IF v_rows_moved = 0 OR (p_batch > 0 AND v_batch_count >= p_batch) THEN EXIT; END IF; COMMIT; PERFORM pg_sleep(p_wait); RAISE DEBUG 'v_rows_moved: %, v_batch_count: %, v_total: %, v_lockwait_count: %, p_wait: %', p_wait, v_rows_moved, v_batch_count, v_total, v_lockwait_count; END LOOP; /* IF v_is_autovac_off = true THEN -- Reset autovac back to default if it was turned off by this procedure PERFORM @extschema@.autovacuum_reset(v_parent_schema, v_parent_tablename, v_source_schema, v_source_tablename); COMMIT; END IF; */ IF p_quiet = false THEN RAISE NOTICE 'Total rows moved: %', v_total; END IF; RAISE NOTICE 'Ensure to VACUUM ANALYZE the parent (and source table if used) after partitioning data'; /* Leaving here until I can figure out what's wrong with procedures and exception handling EXCEPTION WHEN QUERY_CANCELED THEN ROLLBACK; -- Reset autovac back to default if it was turned off by this procedure IF v_is_autovac_off = true THEN PERFORM @extschema@.autovacuum_reset(v_parent_schema, v_parent_tablename, v_source_schema, v_source_tablename); END IF; RAISE EXCEPTION '%', SQLERRM; WHEN OTHERS THEN ROLLBACK; -- Reset autovac back to default if it was turned off by this procedure IF v_is_autovac_off = true THEN PERFORM @extschema@.autovacuum_reset(v_parent_schema, v_parent_tablename, v_source_schema, v_source_tablename); END IF; RAISE EXCEPTION '%', SQLERRM; */ END; $$; $partition_data$; EXECUTE v_partition_data_sql; v_undo_partition_sql := $undo_partition$ CREATE PROCEDURE @extschema@.undo_partition_proc(p_parent_table text, p_interval text DEFAULT NULL, p_batch int DEFAULT NULL, p_wait int DEFAULT 1, p_target_table text DEFAULT NULL, p_keep_table boolean DEFAULT true, p_lock_wait int DEFAULT 0, p_lock_wait_tries int DEFAULT 10, p_quiet boolean DEFAULT false) LANGUAGE plpgsql AS $$ DECLARE v_adv_lock boolean; v_batch_count int := 0; v_is_autovac_off boolean := false; v_lockwait_count int := 0; v_parent_schema text; v_parent_tablename text; v_partition_type text; v_partitions_undone int; v_partitions_undone_total int := 0; v_row record; v_rows_undone bigint; v_target_schema text; v_target_tablename text; v_sql text; v_total bigint := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition_proc'), hashtext(p_parent_table)); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman partition_data_proc already running for given parent table: %.', p_parent_table; RETURN; END IF; SELECT partition_type INTO v_partition_type FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: No entry in part_config found for given table: %', p_parent_table; END IF; IF v_partition_type = 'native' AND p_target_table IS NULL THEN RAISE EXCEPTION 'Natively partitioned table sets require setting the p_target_table parameter to undo partitioning.'; END IF; SELECT n.nspname, c.relname INTO v_parent_schema, v_parent_tablename 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_tablename IS NULL THEN RAISE EXCEPTION 'Unable to find given parent table in system catalogs. Ensure it is schema qualified: %', p_parent_table; END IF; IF p_target_table IS NOT NULL THEN SELECT n.nspname, c.relname INTO v_target_schema, v_target_tablename FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = split_part(p_target_table, '.', 1)::name AND c.relname = split_part(p_target_table, '.', 2)::name; IF v_target_tablename IS NULL THEN RAISE EXCEPTION 'Unable to find given target table in system catalogs. Ensure it is schema qualified: %', p_source_table; END IF; END IF; /* -- Currently no way to catch exception and reset autovac settings back to normal. Until I can do that, leaving this feature out for now -- Leaving the functions to turn off/reset in to let people do that manually if desired IF p_autovacuum_on = false THEN -- Add this parameter back to definition when this is working -- Turn off autovac for parent, source table if set, and all child tables v_is_autovac_off := @extschema@.autovacuum_off(v_parent_schema, v_parent_tablename, v_source_schema, v_source_tablename); COMMIT; END IF; */ v_sql := format('SELECT partitions_undone, rows_undone FROM %I.undo_partition (%L, p_keep_table := %L, p_lock_wait := %L' , '@extschema@', p_parent_table, p_keep_table, p_lock_wait); IF p_interval IS NOT NULL THEN v_sql := v_sql || format(', p_batch_interval := %L', p_interval); END IF; IF p_target_table IS NOT NULL THEN v_sql := v_sql || format(', p_target_table := %L', p_target_table); END IF; v_sql := v_sql || ')'; RAISE DEBUG 'partition_data sql: %', v_sql; LOOP EXECUTE v_sql INTO v_partitions_undone, v_rows_undone; -- If lock wait timeout, do not increment the counter IF v_rows_undone != -1 THEN v_batch_count := v_batch_count + 1; v_partitions_undone_total := v_partitions_undone_total + v_partitions_undone; v_total := v_total + v_rows_undone; v_lockwait_count := 0; ELSE v_lockwait_count := v_lockwait_count + 1; IF v_lockwait_count > p_lock_wait_tries THEN RAISE EXCEPTION 'Quitting due to inability to get lock on next batch of rows to be moved'; END IF; END IF; IF p_quiet = false THEN IF v_rows_undone > 0 THEN RAISE NOTICE 'Batch: %, Partitions undone this batch: %, Rows undone this batch: %', v_batch_count, v_partitions_undone, v_rows_undone; ELSIF v_rows_undone = -1 THEN RAISE NOTICE 'Unable to obtain row locks for data to be moved. Trying again...'; END IF; END IF; COMMIT; -- If no rows left or given batch argument limit is reached IF v_rows_undone = 0 OR (p_batch > 0 AND v_batch_count >= p_batch) THEN EXIT; END IF; -- undo_partition functions will remove config entry once last child is dropped -- Added here to handle edge-case SELECT partition_type INTO v_partition_type FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN EXIT; END IF; PERFORM pg_sleep(p_wait); RAISE DEBUG 'v_partitions_undone: %, v_rows_undone: %, v_batch_count: %, v_total: %, v_lockwait_count: %, p_wait: %', v_partitions_undone, p_wait, v_rows_undone, v_batch_count, v_total, v_lockwait_count; END LOOP; /* IF v_is_autovac_off = true THEN -- Reset autovac back to default if it was turned off by this procedure PERFORM @extschema@.autovacuum_reset(v_parent_schema, v_parent_tablename, v_source_schema, v_source_tablename); COMMIT; END IF; */ IF p_quiet = false THEN RAISE NOTICE 'Total partitions undone: %, Total rows moved: %', v_partitions_undone_total, v_total; END IF; RAISE NOTICE 'Ensure to VACUUM ANALYZE the old parent & target table after undo has finished'; END $$; $undo_partition$; EXECUTE v_undo_partition_sql; v_run_maintenance_sql := $run_maintenance$ CREATE PROCEDURE @extschema@.run_maintenance_proc(p_wait int DEFAULT 0, p_analyze boolean DEFAULT NULL, p_jobmon boolean DEFAULT true, p_debug boolean DEFAULT false) LANGUAGE plpgsql AS $$ DECLARE v_adv_lock boolean; v_row record; v_sql text; v_tables_list_sql text; BEGIN v_adv_lock := pg_try_advisory_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = false THEN RAISE NOTICE 'Partman maintenance already running or another session has not released its advisory lock.'; RETURN; END IF; v_tables_list_sql := 'SELECT parent_table FROM @extschema@.part_config WHERE undo_in_progress = false AND automatic_maintenance = ''on'''; FOR v_row IN EXECUTE v_tables_list_sql LOOP /* * Run maintenance with a commit between each partition set * TODO - Once PG11 is more mainstream, see about more full conversion of run_maintenance function as well as turning * create_partition* functions into procedures to commit after every child table is made. May need to wait * for more PROCEDURE features as well (return values, search_path, etc). * - Also see about swapping names so this is the main object to call for maintenance instead of a function. */ v_sql := format('SELECT %I.run_maintenance(%L, p_jobmon := %L, p_debug := %L', '@extschema@', v_row.parent_table, p_jobmon, p_debug); IF p_analyze IS NOT NULL THEN v_sql := v_sql || format(', p_analyze := %L', p_analyze); END IF; v_sql := v_sql || ')'; IF p_debug THEN RAISE NOTICE 'v_sql run_maintenance_proc: %', v_sql; END IF; EXECUTE v_sql; COMMIT; PERFORM pg_sleep(p_wait); END LOOP; PERFORM pg_advisory_unlock(hashtext('pg_partman run_maintenance')); END $$; $run_maintenance$; EXECUTE v_run_maintenance_sql; v_reapply_constraints_sql := $reapply_constraints$ CREATE PROCEDURE @extschema@.reapply_constraints_proc(p_parent_table text, p_drop_constraints boolean DEFAULT false, p_apply_constraints boolean DEFAULT false, p_wait int DEFAULT 0, p_dryrun boolean DEFAULT false) LANGUAGE plpgsql AS $$ DECLARE v_adv_lock boolean; v_child_stop text; v_control text; v_control_type text; v_datetime_string text; v_epoch text; v_last_partition text; v_last_partition_id bigint; v_last_partition_timestamp timestamptz; v_optimize_constraint int; v_parent_schema text; v_parent_tablename text; v_partition_interval text; v_partition_suffix text; v_premake int; v_row record; v_sql text; BEGIN /* * Procedure for reapplying additional constraints managed by pg_partman on child tables. See docs for additional info on this special constraint management. * Procedure can run in two distinct modes: 1) Drop all constraints 2) Apply all constraints. * If both modes are run in a single call, drop is run before apply. * Typical usage would be to run the drop mode, edit the data, then run apply mode to re-create all constraints on a partition set." */ v_adv_lock := pg_try_advisory_lock(hashtext('pg_partman reapply_constraints')); IF v_adv_lock = false THEN RAISE NOTICE 'Partman reapply_constraints_proc already running or another session has not released its advisory lock.'; RETURN; END IF; SELECT control, premake, optimize_constraint, datetime_string, epoch, partition_interval INTO v_control, v_premake, v_optimize_constraint, v_datetime_string, v_epoch, v_partition_interval FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_premake IS NULL THEN RAISE EXCEPTION 'Unable to find given parent in pg_partman config: %. This procedure is only meant to be called on pg_partman managed partition sets.', p_parent_table; END IF; SELECT n.nspname, c.relname INTO v_parent_schema, v_parent_tablename 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_tablename IS NULL THEN RAISE EXCEPTION 'Unable to find given parent table in system catalogs. Ensure it is schema qualified: %', p_parent_table; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); -- Determine child table to stop creating constraints on based on optimize_constraint value -- Same code in apply_constraints.sql SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN SELECT child_start_time INTO v_last_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_partition_interval, p_parent_table); v_partition_suffix := to_char(v_last_partition_timestamp - (v_partition_interval::interval * (v_optimize_constraint + v_premake + 1) ), v_datetime_string); ELSIF v_control_type = 'id' THEN SELECT child_start_id INTO v_last_partition_id FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_partition_interval, p_parent_table); v_partition_suffix := (v_last_partition_id - (v_partition_interval::int * (v_optimize_constraint + v_premake + 1) ))::text; END IF; v_child_stop := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); v_sql := format('SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(%L, %L)', p_parent_table, 'ASC'); RAISE DEBUG 'reapply_constraint: v_parent_tablename: % , v_partition_suffix: %, v_child_stop: %, v_sql: %', v_parent_tablename, v_partition_suffix, v_child_stop, v_sql; v_row := NULL; FOR v_row IN EXECUTE v_sql LOOP IF p_drop_constraints THEN IF p_dryrun THEN RAISE NOTICE 'DRYRUN NOTICE: Dropping constraints on child table: %.%', v_row.partition_schemaname, v_row.partition_tablename; ELSE RAISE DEBUG 'reapply_constraint drop: %.%', v_row.partition_schemaname, v_row.partition_tablename; PERFORM @extschema@.drop_constraints(p_parent_table, format('%s.%s', v_row.partition_schemaname, v_row.partition_tablename)::text); END IF; END IF; -- end drop COMMIT; IF p_apply_constraints THEN IF p_dryrun THEN RAISE NOTICE 'DRYRUN NOTICE: Applying constraints on child table: %.%', v_row.partition_schemaname, v_row.partition_tablename; ELSE RAISE DEBUG 'reapply_constraint apply: %.%', v_row.partition_schemaname, v_row.partition_tablename; PERFORM @extschema@.apply_constraints(p_parent_table, format('%s.%s', v_row.partition_schemaname, v_row.partition_tablename)::text); END IF; END IF; -- end apply IF v_row.partition_tablename = v_child_stop THEN RAISE DEBUG 'reapply_constraint: Reached stop at %.%', v_row.partition_schemaname, v_row.partition_tablename; EXIT; -- stop creating constraints after optimize target is reached END IF; COMMIT; PERFORM pg_sleep(p_wait); END LOOP; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); PERFORM pg_advisory_unlock(hashtext('pg_partman reapply_constraints')); END $$; $reapply_constraints$; EXECUTE v_reapply_constraints_sql; -- ######################## END POSTGRESQL 11 ONLY SECTION ############################## END IF; END $pg11only$; -- ######################## END POSTGRESQL 11 ONLY SECTION ############################## CREATE TEMP TABLE partman_preserve_privs_temp (statement text); INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.partition_data_id(text, int, bigint, numeric, text, boolean, text) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'partition_data_time'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.partition_data_time(text, int, interval, numeric, text, boolean, text) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'partition_data_time'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.undo_partition(text, int, text, boolean, numeric, text) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'undo_partition_native'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.show_partitions(text, text, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = ''; DROP FUNCTION @extschema@.partition_data_id(text, int, bigint, numeric, text, boolean); DROP FUNCTION @extschema@.partition_data_time(text, int, interval, numeric, text, boolean); DROP FUNCTION @extschema@.undo_partition_time(text, int, interval, boolean, numeric); DROP FUNCTION @extschema@.undo_partition_id(text, int, bigint, boolean, numeric); DROP FUNCTION @extschema@.undo_partition_native(text, text, int, text, boolean, numeric); DROP FUNCTION @extschema@.undo_partition(text, int, boolean, boolean, numeric); DROP FUNCTION @extschema@.show_partitions(text, text); CREATE FUNCTION @extschema@.autovacuum_off(p_parent_schema text, p_parent_tablename text, p_source_schema text DEFAULT NULL, p_source_tablename text DEFAULT NULL) RETURNS boolean LANGUAGE plpgsql AS $$ DECLARE v_row record; v_sql text; BEGIN v_sql = format('ALTER TABLE %I.%I SET (autovacuum_enabled = false, toast.autovacuum_enabled = false)', p_parent_schema, p_parent_tablename); RAISE DEBUG 'partition_data sql: %', v_sql; EXECUTE v_sql; IF p_source_tablename IS NOT NULL THEN v_sql = format('ALTER TABLE %I.%I SET (autovacuum_enabled = false, toast.autovacuum_enabled = false)', p_source_schema, p_source_tablename); RAISE DEBUG 'partition_data sql: %', v_sql; EXECUTE v_sql; END IF; FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_schema||'.'||p_parent_tablename, 'ASC') LOOP v_sql = format('ALTER TABLE %I.%I SET (autovacuum_enabled = false, toast.autovacuum_enabled = false)', v_row.partition_schemaname, v_row.partition_tablename); RAISE DEBUG 'partition_data sql: %', v_sql; EXECUTE v_sql; END LOOP; RETURN true; END $$; CREATE FUNCTION @extschema@.autovacuum_reset(p_parent_schema text, p_parent_tablename text, p_source_schema text DEFAULT NULL, p_source_tablename text DEFAULT NULL) RETURNS boolean LANGUAGE plpgsql AS $$ DECLARE v_row record; v_sql text; BEGIN v_sql = format('ALTER TABLE %I.%I RESET (autovacuum_enabled, toast.autovacuum_enabled)', p_parent_schema, p_parent_tablename); RAISE DEBUG 'partition_data sql: %', v_sql; EXECUTE v_sql; IF p_source_tablename IS NOT NULL THEN v_sql = format('ALTER TABLE %I.%I RESET (autovacuum_enabled, toast.autovacuum_enabled)', p_source_schema, p_source_tablename); RAISE DEBUG 'partition_data sql: %', v_sql; EXECUTE v_sql; END IF; FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_schema||'.'||p_parent_tablename, 'ASC') LOOP v_sql = format('ALTER TABLE %I.%I RESET (autovacuum_enabled, toast.autovacuum_enabled)', v_row.partition_schemaname, v_row.partition_tablename); RAISE DEBUG 'partition_data sql: %', v_sql; EXECUTE v_sql; END LOOP; RETURN true; END $$; CREATE OR REPLACE FUNCTION @extschema@.apply_cluster(p_parent_schema text, p_parent_tablename text, p_child_schema text, p_child_tablename text) RETURNS void LANGUAGE plpgsql AS $$ DECLARE v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_indexdef text; v_relkind char; v_row record; v_sql text; BEGIN /* * Function to apply cluster from parent to child table * Adapted from code fork by https://github.com/dturon/pg_partman */ 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'); 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 = p_parent_schema AND c.relname = p_parent_tablename; IF v_relkind = 'p' THEN RAISE EXCEPTION 'This function cannot run on natively partitioned tables'; ELSIF v_relkind IS NULL THEN RAISE EXCEPTION 'Unable to find given table in system catalogs: %.%', p_parent_schema, p_parent_tablename; END IF; WITH parent_info AS ( SELECT c.oid AS parent_oid FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = p_parent_schema::name AND c.relname = p_parent_tablename::name ) SELECT substring(pg_get_indexdef(i.indexrelid) from ' USING .*$') AS index_def INTO v_parent_indexdef FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid JOIN parent_info p ON p.parent_oid = indrelid WHERE i.indisclustered = true; -- Loop over all existing indexes in child table to find one with matching definition FOR v_row IN WITH child_info AS ( SELECT c.oid AS child_oid 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 ) SELECT substring(pg_get_indexdef(i.indexrelid) from ' USING .*$') AS child_indexdef , c.relname AS child_indexname FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid JOIN child_info p ON p.child_oid = indrelid LOOP IF v_row.child_indexdef = v_parent_indexdef THEN v_sql = format('ALTER TABLE %I.%I CLUSTER ON %I', p_child_schema, p_child_tablename, v_row.child_indexname); RAISE DEBUG '%', v_sql; EXECUTE v_sql; END IF; END LOOP; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END; $$; CREATE OR REPLACE FUNCTION @extschema@.apply_publications(p_parent_table text, p_child_schema text, p_child_tablename text) RETURNS void LANGUAGE plpgsql AS $$ DECLARE v_publications text[]; v_row record; v_sql text; BEGIN /* * Function to ATLER PUBLICATION ... ADD TABLE to support logical replication */ SELECT c.publications INTO v_publications FROM @extschema@.part_config c WHERE c.parent_table = p_parent_table; -- Loop over all publicaions which the table needs to be added to FOR v_row IN SELECT pubname FROM unnest(v_publications) AS pubname LOOP v_sql = format('ALTER PUBLICATION %I ADD TABLE %I.%I', v_row.pubname, p_child_schema, p_child_tablename); RAISE DEBUG '%', v_sql; EXECUTE v_sql; END LOOP; END; $$; CREATE OR REPLACE FUNCTION @extschema@.check_control_type(p_parent_schema text, p_parent_tablename text, p_control text) RETURNS TABLE (general_type text, exact_type text) LANGUAGE sql STABLE AS $$ /* * Return column type for given table & column in that table * Returns NULL of objects don't exist */ SELECT CASE WHEN typname IN ('timestamptz', 'timestamp', 'date') THEN 'time' WHEN typname IN ('int2', 'int4', 'int8') THEN 'id' END , typname::text FROM pg_catalog.pg_type t JOIN pg_catalog.pg_attribute a ON t.oid = a.atttypid JOIN pg_catalog.pg_class c ON a.attrelid = c.oid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = p_parent_schema::name AND c.relname = p_parent_tablename::name AND a.attname = p_control::name $$; CREATE OR REPLACE FUNCTION @extschema@.check_parent(p_exact_count boolean DEFAULT true) RETURNS SETOF @extschema@.check_parent_table LANGUAGE plpgsql STABLE SET search_path = @extschema@,pg_temp AS $$ DECLARE v_count bigint = 0; v_row record; v_schemaname text; v_tablename text; v_sql text; v_trouble @extschema@.check_parent_table%rowtype; BEGIN /* * Function to monitor for data getting inserted into parent tables managed by extension */ FOR v_row IN SELECT parent_table FROM @extschema@.part_config LOOP SELECT schemaname, tablename INTO v_schemaname, v_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(v_row.parent_table, '.', 1)::name AND tablename = split_part(v_row.parent_table, '.', 2)::name; IF p_exact_count THEN v_sql := format('SELECT count(1) AS n FROM ONLY %I.%I', v_schemaname, v_tablename); ELSE v_sql := format('SELECT count(1) AS n FROM (SELECT 1 FROM ONLY %I.%I LIMIT 1) x', v_schemaname, v_tablename); END IF; EXECUTE v_sql INTO v_count; IF v_count > 0 THEN v_trouble.parent_table := v_schemaname ||'.'|| v_tablename; v_trouble.count := v_count; RETURN NEXT v_trouble; END IF; v_count := 0; END LOOP; RETURN; END $$; CREATE OR REPLACE FUNCTION @extschema@.check_subpart_sameconfig(p_parent_table text) RETURNS TABLE (sub_partition_type text , sub_control text , sub_partition_interval text , sub_constraint_cols text[] , sub_premake int , sub_inherit_fk boolean , sub_retention text , sub_retention_schema text , sub_retention_keep_table boolean , sub_retention_keep_index boolean , sub_automatic_maintenance text , sub_epoch text , sub_optimize_trigger int , sub_optimize_constraint int , sub_infinite_time_partitions boolean , sub_jobmon boolean , sub_trigger_exception_handling boolean , sub_upsert text , sub_trigger_return_null boolean) LANGUAGE sql STABLE SET search_path = @extschema@,pg_temp AS $$ /* * Check for consistent data in part_config_sub table. Was unable to get this working properly as either a constraint or trigger. * Would either delay raising an error until the next write (which I cannot predict) or disallow future edits to update a sub-partition set's configuration. * This is called by run_maintainance() and at least provides a consistent way to check that I know will run. * If anyone can get a working constraint/trigger, please help! */ WITH parent_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE n1.nspname = split_part(p_parent_table, '.', 1)::name AND c1.relname = split_part(p_parent_table, '.', 2)::name ) , child_tables AS ( SELECT n.nspname||'.'||c.relname AS tablename FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN parent_info pi ON h.inhparent = pi.oid ) -- Column order here must match the RETURNS TABLE definition SELECT DISTINCT a.sub_partition_type , a.sub_control , a.sub_partition_interval , a.sub_constraint_cols , a.sub_premake , a.sub_inherit_fk , a.sub_retention , a.sub_retention_schema , a.sub_retention_keep_table , a.sub_retention_keep_index , a.sub_automatic_maintenance , a.sub_epoch , a.sub_optimize_trigger , a.sub_optimize_constraint , a.sub_infinite_time_partitions , a.sub_jobmon , a.sub_trigger_exception_handling , a.sub_upsert , a.sub_trigger_return_null FROM @extschema@.part_config_sub a JOIN child_tables b on a.sub_parent = b.tablename; $$; CREATE OR REPLACE FUNCTION @extschema@.create_function_id(p_parent_table text, p_job_id bigint DEFAULT NULL) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_control text; v_control_type text; v_count int; v_current_partition_name text; v_current_partition_id bigint; v_datetime_string text; v_final_partition_id bigint; v_function_name 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_position int; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_max bigint; v_next_partition_id bigint; v_next_partition_name text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_optimize_trigger int; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_premake int; v_prev_partition_id bigint; v_prev_partition_name text; v_relkind char; v_row_max_id record; v_run_maint text; v_step_id bigint; v_top_parent text := p_parent_table; v_trig_func text; v_trigger_exception_handling boolean; v_trigger_return_null boolean; v_upsert text; BEGIN /* * Create the trigger function for the parent table of an id-based partition set */ SELECT partition_interval::bigint , control , premake , optimize_trigger , automatic_maintenance , jobmon , trigger_exception_handling , upsert , trigger_return_null INTO v_partition_interval , v_control , v_premake , v_optimize_trigger , v_run_maint , v_jobmon , v_trigger_exception_handling , v_upsert , v_trigger_return_null FROM @extschema@.part_config WHERE parent_table = p_parent_table AND partition_type = 'partman'; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no non-native pg_partman config found for %', p_parent_table; END IF; SELECT n.nspname, c.relname, c.relkind INTO v_parent_schema, v_parent_tablename, v_relkind 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_relkind = 'p' THEN RAISE EXCEPTION 'This function cannot run on natively partitioned tables'; ELSIF v_relkind IS NULL THEN RAISE EXCEPTION 'Unable to find given table in system catalogs: %.%', v_parent_schema, v_parent_tablename; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run create_function_id on partition set without id based control column. Found: %', v_control_type; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE FUNCTION: %s', p_parent_table)); ELSE v_job_id = p_job_id; END IF; v_step_id := add_step(v_job_id, format('Creating partition function for table %s', p_parent_table)); END IF; -- Get the highest level top parent if multi-level partitioned in order to get proper max() value below 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 INTO v_higher_parent_schema, v_higher_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 WHERE p.partition_type = 'id'; IF v_higher_parent_table IS NOT NULL THEN -- initially set in DECLARE v_top_parent := v_higher_parent_schema||'.'||v_higher_parent_table; END IF; END LOOP; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_top_parent, 'DESC') LOOP EXECUTE format('SELECT max(%I) FROM %I.%I', v_control, v_row_max_id.partition_schemaname, v_row_max_id.partition_tablename) INTO v_max; IF v_max IS NOT NULL THEN EXIT; END IF; END LOOP; IF v_max IS NULL THEN v_max := 0; END IF; v_current_partition_id = v_max - (v_max % v_partition_interval); v_next_partition_id := v_current_partition_id + v_partition_interval; v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_current_partition_id::text, TRUE); v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_current_partition_id bigint; v_current_partition_name text; v_id_position int; v_last_partition text := %L; v_next_partition_id bigint; v_next_partition_name text; v_partition_created boolean; BEGIN IF TG_OP = ''INSERT'' THEN IF NEW.%I >= %s AND NEW.%I < %s THEN ' , v_parent_schema , v_function_name , v_last_partition , v_control , v_current_partition_id , v_control , v_next_partition_id ); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_current_partition_name::name; IF v_count > 0 THEN v_trig_func := v_trig_func || format(' INSERT INTO %I.%I VALUES (NEW.*) %s; ', v_parent_schema, v_current_partition_name, v_upsert); ELSE v_trig_func := v_trig_func || ' -- Child table for current values does not exist in this partition set, so write to parent RETURN NEW;'; END IF; FOR i IN 1..v_optimize_trigger LOOP v_prev_partition_id := v_current_partition_id - (v_partition_interval * i); v_next_partition_id := v_current_partition_id + (v_partition_interval * i); v_final_partition_id := v_next_partition_id + v_partition_interval; v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, v_prev_partition_id::text, TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, v_next_partition_id::text, TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles optimize_trigger being larger than premake (to go back in time further) and edge case of changing optimize_trigger immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_prev_partition_name::name; IF v_count > 0 THEN -- Only handle previous partitions if they're starting above zero IF v_prev_partition_id >= 0 THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %s AND NEW.%I < %s THEN INSERT INTO %I.%I VALUES (NEW.*) %s; ' , v_control , v_prev_partition_id , v_control , v_prev_partition_id + v_partition_interval , v_parent_schema , v_prev_partition_name , v_upsert ); END IF; END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_next_partition_name::name; IF v_count > 0 THEN v_trig_func := v_trig_func ||format(' ELSIF NEW.%I >= %s AND NEW.%I < %s THEN INSERT INTO %I.%I VALUES (NEW.*) %s;' , v_control , v_next_partition_id , v_control , v_final_partition_id , v_parent_schema , v_next_partition_name , v_upsert ); END IF; END LOOP; v_trig_func := v_trig_func ||format(' ELSE v_current_partition_id := NEW.%I - (NEW.%I %% %s); v_current_partition_name := @extschema@.check_name_length(%L, v_current_partition_id::text, TRUE); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = %L::name AND tablename = v_current_partition_name::name; IF v_count > 0 THEN EXECUTE format(''INSERT INTO %%I.%%I VALUES($1.*) %s'', %L, v_current_partition_name) USING NEW; ELSE RETURN NEW; END IF; END IF;' , v_control , v_control , v_partition_interval , v_parent_tablename , v_parent_schema , v_upsert , v_parent_schema ); v_trig_func := v_trig_func ||' END IF;'; IF v_trigger_return_null IS TRUE THEN v_trig_func := v_trig_func ||' RETURN NULL;'; ELSE v_trig_func := v_trig_func ||' RETURN NEW;'; END IF; IF v_trigger_exception_handling THEN v_trig_func := v_trig_func ||' EXCEPTION WHEN OTHERS THEN RAISE WARNING ''pg_partman insert into child table failed, row inserted into parent (%.%). ERROR: %'', TG_TABLE_SCHEMA, TG_TABLE_NAME, COALESCE(SQLERRM, ''unknown''); RETURN NEW;'; END IF; v_trig_func := v_trig_func ||' END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for current id interval: %s to %s', v_current_partition_id, v_final_partition_id-1)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); 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 FUNCTION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''Partition function maintenance for table %s 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 $$; CREATE OR REPLACE FUNCTION @extschema@.create_function_time(p_parent_table text, p_job_id bigint DEFAULT NULL) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_control text; v_control_type text; v_count int; v_current_partition_name text; v_current_partition_timestamp timestamptz; v_datetime_string text; v_epoch text; v_final_partition_timestamp timestamptz; v_function_name text; v_infinite_time_partitions boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_new_length int; v_next_partition_name text; v_next_partition_timestamp timestamptz; v_parent_schema text; v_parent_tablename text; v_partition_expression text; v_partition_interval interval; v_prev_partition_name text; v_prev_partition_timestamp timestamptz; v_relkind char; v_row_max_time record; v_step_id bigint; v_trig_func text; v_optimize_trigger int; v_table_exists boolean; v_trigger_exception_handling boolean; v_trigger_return_null boolean; v_type text; v_upsert text; BEGIN /* * Create the trigger function for the parent table of a time-based partition set */ SELECT partition_type , partition_interval::interval , epoch , control , optimize_trigger , datetime_string , jobmon , trigger_exception_handling , upsert , trigger_return_null , infinite_time_partitions INTO v_type , v_partition_interval , v_epoch , v_control , v_optimize_trigger , v_datetime_string , v_jobmon , v_trigger_exception_handling , v_upsert , v_trigger_return_null , v_infinite_time_partitions FROM @extschema@.part_config WHERE parent_table = p_parent_table AND (partition_type = 'partman' OR partition_type = 'time-custom'); IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no non-native pg_partman config found for %', p_parent_table; END IF; SELECT n.nspname, c.relname, c.relkind INTO v_parent_schema, v_parent_tablename, v_relkind 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_relkind = 'p' THEN RAISE EXCEPTION 'This function cannot run on natively partitioned tables'; ELSIF v_relkind IS NULL THEN RAISE EXCEPTION 'Unable to find given table in system catalogs: %.%', v_parent_schema, v_parent_tablename; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'time' THEN IF (v_control_type = 'id' AND v_epoch = 'none') OR v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run on partition set without time based control column or epoch flag set with an id column. Found control: %, epoch: %', v_control_type, v_epoch; END IF; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE FUNCTION: %s', p_parent_table)); ELSE v_job_id = p_job_id; END IF; v_step_id := add_step(v_job_id, format('Creating partition function for table %s', p_parent_table)); END IF; IF v_infinite_time_partitions IS TRUE THEN -- Set it to "now" to line up with maintenance always making new partitions despite no new data -- Also, don't need to bother getting the max value in the partitions v_current_partition_timestamp := CURRENT_TIMESTAMP; ELSE v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) END; FOR v_row_max_time IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%s)::text FROM %I.%I' , v_partition_expression , v_row_max_time.partition_schemaname , v_row_max_time.partition_tablename ) INTO v_current_partition_timestamp; IF v_current_partition_timestamp IS NOT NULL THEN EXIT; END IF; END LOOP; IF v_current_partition_timestamp IS NULL THEN v_current_partition_timestamp := CURRENT_TIMESTAMP; END IF; END IF; -- end infinite time check -- Reset for use in trigger function v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(NEW.%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((NEW.%I/1000)::float)', v_control) ELSE format('NEW.%I', v_control) END; IF v_type = 'partman' THEN v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_count int; v_partition_name text; v_partition_timestamp timestamptz; BEGIN IF TG_OP = ''INSERT'' THEN ' , v_parent_schema , v_function_name); SELECT suffix_timestamp, partition_table, table_exists INTO v_current_partition_timestamp, v_current_partition_name, v_table_exists FROM @extschema@.show_partition_name(p_parent_table, v_current_partition_timestamp::text); CASE WHEN v_partition_interval = '15 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', %s) + ''15min''::interval * floor(date_part(''minute'', %1$s) / 15.0);' , v_partition_expression); WHEN v_partition_interval = '30 mins' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', %s) + ''30min''::interval * floor(date_part(''minute'', %1$s) / 30.0);' , v_partition_expression); WHEN v_partition_interval = '1 hour' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''hour'', %s);', v_partition_expression); WHEN v_partition_interval = '1 day' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''day'', %s);', v_partition_expression); WHEN v_partition_interval = '1 week' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''week'', %s);', v_partition_expression); WHEN v_partition_interval = '1 month' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''month'', %s);', v_partition_expression); WHEN v_partition_interval = '3 months' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''quarter'', %s);', v_partition_expression); WHEN v_partition_interval = '1 year' THEN v_trig_func := v_trig_func||format('v_partition_timestamp := date_trunc(''year'', %s);', v_partition_expression); END CASE; v_next_partition_timestamp := v_current_partition_timestamp + v_partition_interval::interval; v_trig_func := v_trig_func ||format(' IF %s >= %L AND %1$s < %3$L THEN ' , v_partition_expression , v_current_partition_timestamp , v_next_partition_timestamp); IF v_table_exists THEN v_trig_func := v_trig_func || format(' INSERT INTO %I.%I VALUES (NEW.*) %s; ', v_parent_schema, v_current_partition_name, v_upsert); ELSE v_trig_func := v_trig_func || ' -- Child table for current values does not exist in this partition set, so write to parent RETURN NEW;'; END IF; FOR i IN 1..v_optimize_trigger LOOP v_prev_partition_timestamp := v_current_partition_timestamp - (v_partition_interval::interval * i); v_next_partition_timestamp := v_current_partition_timestamp + (v_partition_interval::interval * i); v_final_partition_timestamp := v_next_partition_timestamp + (v_partition_interval::interval); v_prev_partition_name := @extschema@.check_name_length(v_parent_tablename, to_char(v_prev_partition_timestamp, v_datetime_string), TRUE); v_next_partition_name := @extschema@.check_name_length(v_parent_tablename, to_char(v_next_partition_timestamp, v_datetime_string), TRUE); -- Check that child table exist before making a rule to insert to them. -- Handles optimize_trigger being larger than premake (to go back in time further) and edge case of changing optimize_trigger immediately after running create_parent(). SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_prev_partition_name::name; IF v_count > 0 THEN v_trig_func := v_trig_func ||format(' ELSIF %s >= %L AND %1$s < %3$L THEN INSERT INTO %I.%I VALUES (NEW.*) %s;' , v_partition_expression , v_prev_partition_timestamp , v_prev_partition_timestamp + v_partition_interval::interval , v_parent_schema , v_prev_partition_name , v_upsert); END IF; SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = v_parent_schema::name AND tablename = v_next_partition_name::name; IF v_count > 0 THEN v_trig_func := v_trig_func ||format(' ELSIF %s >= %L AND %1$s < %3$L THEN INSERT INTO %I.%I VALUES (NEW.*) %s;' , v_partition_expression , v_next_partition_timestamp , v_final_partition_timestamp , v_parent_schema , v_next_partition_name , v_upsert); END IF; END LOOP; v_trig_func := v_trig_func||format(' ELSE v_partition_name := @extschema@.check_name_length(%L, to_char(v_partition_timestamp, %L), TRUE); SELECT count(*) INTO v_count FROM pg_catalog.pg_tables WHERE schemaname = %L::name AND tablename = v_partition_name::name; IF v_count > 0 THEN EXECUTE format(''INSERT INTO %%I.%%I VALUES($1.*) %s'', %L, v_partition_name) USING NEW; ELSE RETURN NEW; END IF; END IF;' , v_parent_tablename , v_datetime_string , v_parent_schema , v_upsert , v_parent_schema); v_trig_func := v_trig_func ||' END IF;'; IF v_trigger_return_null IS TRUE THEN v_trig_func := v_trig_func ||' RETURN NULL;'; ELSE v_trig_func := v_trig_func ||' RETURN NEW;'; END IF; IF v_trigger_exception_handling THEN v_trig_func := v_trig_func ||' EXCEPTION WHEN OTHERS THEN RAISE WARNING ''pg_partman insert into child table failed, row inserted into parent (%.%). ERROR: %'', TG_TABLE_SCHEMA, TG_TABLE_NAME, COALESCE(SQLERRM, ''unknown''); RETURN NEW;'; END IF; v_trig_func := v_trig_func ||' END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for current time interval: %s to %s' , v_current_partition_timestamp , v_final_partition_timestamp-'1sec'::interval)); END IF; ELSIF v_type = 'time-custom' THEN v_trig_func := format('CREATE OR REPLACE FUNCTION %I.%I() RETURNS trigger LANGUAGE plpgsql AS $t$ DECLARE v_child_schemaname text; v_child_table text; v_child_tablename text; v_upsert text; BEGIN ' , v_parent_schema , v_function_name); v_trig_func := v_trig_func || format(' SELECT c.child_table, p.upsert INTO v_child_table, v_upsert FROM @extschema@.custom_time_partitions c JOIN @extschema@.part_config p ON c.parent_table = p.parent_table WHERE c.partition_range @> %s AND c.parent_table = %L;' , v_partition_expression , v_parent_schema||'.'||v_parent_tablename); v_trig_func := v_trig_func || ' SELECT schemaname, tablename INTO v_child_schemaname, v_child_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(v_child_table, ''.'', 1)::name AND tablename = split_part(v_child_table, ''.'', 2)::name; IF v_child_schemaname IS NOT NULL AND v_child_tablename IS NOT NULL THEN EXECUTE format(''INSERT INTO %I.%I VALUES ($1.*) %s'', v_child_schemaname, v_child_tablename, v_upsert) USING NEW; ELSE RETURN NEW; END IF;'; IF v_trigger_return_null IS TRUE THEN v_trig_func := v_trig_func ||' RETURN NULL;'; ELSE v_trig_func := v_trig_func ||' RETURN NEW;'; END IF; IF v_trigger_exception_handling THEN v_trig_func := v_trig_func ||' EXCEPTION WHEN OTHERS THEN RAISE WARNING ''pg_partman insert into child table failed, row inserted into parent (%.%). ERROR: %'', TG_TABLE_SCHEMA, TG_TABLE_NAME, COALESCE(SQLERRM, ''unknown''); RETURN NEW;'; END IF; v_trig_func := v_trig_func ||' END $t$;'; EXECUTE v_trig_func; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Added function for custom time table: %s', p_parent_table)); END IF; ELSE RAISE EXCEPTION 'ERROR: Invalid time partitioning type given: %', v_type; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); 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 FUNCTION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''Partition function maintenance for table %s 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 $$; 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 , p_debug boolean DEFAULT false) 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_schema text := split_part(p_parent_table, '.', 1); v_higher_parent_table text := split_part(p_parent_table, '.', 2); v_id_interval bigint; 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 (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 ... PARITIONED 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 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; 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; 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 ) 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 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) 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); 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) 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); 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 INTO v_higher_parent_schema, v_higher_parent_table, v_higher_parent_control 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' 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) 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_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'; IF v_unlogged = 'u' THEN v_sql := v_sql ||' UNLOGGED'; END IF; -- Same INCLUDING list is used in create_partition_*() 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); 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 v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_default_partition, v_parent_tablespace); END IF; -- NOTE: Privileges currently not automatically inherited for native PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_default_partition, v_job_id); 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 $$; CREATE OR REPLACE FUNCTION @extschema@.create_partition_id(p_parent_table text, p_partition_ids bigint[], p_analyze boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_control_type text; v_exists text; v_grantees text[]; v_hasoids boolean; v_id bigint; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_grant record; v_parent_schema text; v_parent_tablename text; v_parent_tablespace text; v_partition_interval bigint; v_partition_created boolean := false; v_partition_name text; v_partition_type text; v_publications text[]; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_sub_control text; v_sub_partition_type text; v_sub_id_max bigint; v_sub_id_min bigint; v_template_table text; v_unlogged char; BEGIN /* * Function to create id partitions */ SELECT control , partition_type , partition_interval , inherit_fk , jobmon , template_table , publications INTO v_control , v_partition_type , v_partition_interval , v_inherit_fk , v_jobmon , v_template_table , v_publications FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT n.nspname, c.relname, t.spcname INTO v_parent_schema, v_parent_tablename, v_parent_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 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; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'id' THEN RAISE EXCEPTION 'ERROR: Given parent table is not set up for id/serial partitioning'; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'id'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; FOREACH v_id IN ARRAY p_partition_ids LOOP -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_id_min IS NOT NULL THEN IF v_id < v_sub_id_min OR v_id > v_sub_id_max THEN CONTINUE; END IF; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_id::text, TRUE); -- If child table already exists, skip creation -- Have to check pg_class because if subpartitioned, table will not be in pg_tables SELECT c.relname INTO v_exists 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_partition_name::name; IF v_exists IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_id||' to '||(v_id + v_partition_interval)-1); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c 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; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; -- Close parentheses on LIKE are below due to differing requirements of native subpartitioning -- Same INCLUDING list is used in create_parent() v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING STORAGE INCLUDING COMMENTS ' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); SELECT sub_partition_type, sub_control INTO v_sub_partition_type, v_sub_control FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; IF v_sub_partition_type = 'native' THEN -- NOTE: Need to handle this differently when index inheritance is supported natively -- Cannot include indexes since they cannot exist on native parents. v_sql := v_sql || format(') PARTITION BY RANGE (%I) ', v_sub_control); ELSE v_sql := v_sql || format(' INCLUDING INDEXES) ', v_sub_control); END IF; SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c 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; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; IF v_partition_type = 'native' THEN IF v_template_table IS NOT NULL THEN PERFORM @extschema@.inherit_template_properties(p_parent_table, v_parent_schema, v_partition_name); END IF; EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , v_id , v_id + v_partition_interval); ELSE -- Handled in inherit_template_properties for native because CREATE TABLE ignores TABLESPACE flag for native partition parents IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %s AND %I < %s )' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_control , v_id , v_control , v_id + v_partition_interval); EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I', v_parent_schema, v_partition_name, v_parent_schema, v_parent_tablename); -- Indexes cannot be created on the parent, so clustering cannot be used for native yet. PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); -- Foreign keys to other tables not supported on native parent tables IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; END IF; -- NOTE: Privileges currently not automatically inherited for native PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_automatic_maintenance , sub_infinite_time_partitions , sub_jobmon , sub_trigger_exception_handling , sub_template_table FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Subpartitioning '||v_partition_name); END IF; 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_inherit_fk := %L , p_epoch := %L , p_template_table := %L , p_jobmon := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_automatic_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_template_table , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions , trigger_exception_handling = v_row.sub_trigger_exception_handling WHERE parent_table = v_parent_schema||'.'||v_partition_name; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; -- end sub partitioning LOOP -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table, p_job_id := v_job_id, p_debug := p_debug); IF v_publications IS NOT NULL THEN -- NOTE: Publications currently not supported on parent table, but are supported on the table partitions if individually assigned. PERFORM @extschema@.apply_publications(p_parent_table, v_parent_schema, v_partition_name); END IF; v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s', p_parent_table)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE OR REPLACE FUNCTION @extschema@.create_partition_time(p_parent_table text, p_partition_times timestamptz[], p_analyze boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_control_type text; v_datetime_string text; v_epoch text; v_exists smallint; v_grantees text[]; v_hasoids boolean; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_grant record; v_parent_schema text; v_parent_tablename text; v_part_col text; v_partition_created boolean := false; v_partition_name text; v_partition_suffix text; v_parent_tablespace text; v_partition_expression text; v_partition_interval interval; v_partition_timestamp_end timestamptz; v_partition_timestamp_start timestamptz; v_publications text[]; v_quarter text; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_sub_control text; v_sub_parent text; v_sub_partition_type text; v_sub_timestamp_max timestamptz; v_sub_timestamp_min timestamptz; v_template_table text; v_trunc_value text; v_time timestamptz; v_partition_type text; v_unlogged char; v_year text; BEGIN /* * Function to create a child table in a time-based partition set */ SELECT partition_type , control , partition_interval , epoch , inherit_fk , jobmon , datetime_string , template_table , publications INTO v_partition_type , v_control , v_partition_interval , v_epoch , v_inherit_fk , v_jobmon , v_datetime_string , v_template_table , v_publications FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT n.nspname, c.relname, t.spcname INTO v_parent_schema, v_parent_tablename, v_parent_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 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; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'time' THEN IF (v_control_type = 'id' AND v_epoch = 'none') OR v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run on partition set without time based control column or epoch flag set with an id column. Found control: %, epoch: %', v_control_type, v_epoch; END IF; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::timestamptz, sub_max::timestamptz INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'time'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) END; IF p_debug THEN RAISE NOTICE 'create_partition_time: v_partition_expression: %', v_partition_expression; END IF; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_timestamp_start := v_time; BEGIN v_partition_timestamp_end := v_time + v_partition_interval; 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_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_time||' skipped'); CONTINUE; END; -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_timestamp_min IS NOT NULL THEN IF v_time < v_sub_timestamp_min OR v_time > v_sub_timestamp_max THEN CONTINUE; END IF; END IF; -- This suffix generation code is in partition_data_time() as well v_partition_suffix := to_char(v_time, v_datetime_string); v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); -- Check if child exists. SELECT count(*) INTO v_exists 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_partition_name::name; IF v_exists > 0 THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Creating new partition %s.%s with interval from %s to %s' , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end-'1sec'::interval)); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c 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; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; -- Close parentheses on LIKE are below due to differing requirements of native subpartitioning -- Same INCLUDING list is used in create_parent() v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING STORAGE INCLUDING COMMENTS ' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); SELECT sub_partition_type, sub_control INTO v_sub_partition_type, v_sub_control FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; IF v_sub_partition_type = 'native' THEN -- NOTE: Need to handle this differently when index inheritance is supported natively -- Cannot include indexes since they cannot exist on native parents v_sql := v_sql || format(') PARTITION BY RANGE (%I) ', v_sub_control); ELSE v_sql := v_sql || format(' INCLUDING INDEXES) ', v_sub_control); END IF; SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c 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; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; IF p_debug THEN RAISE NOTICE 'create_partition_time v_sql: %', v_sql; END IF; EXECUTE v_sql; IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; IF v_partition_type = 'native' THEN IF v_template_table IS NOT NULL THEN PERFORM @extschema@.inherit_template_properties(p_parent_table, v_parent_schema, v_partition_name); END IF; IF v_epoch = 'none' THEN -- Attach with normal, time-based values for native constraint EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end); ELSE -- Must attach with integer based values for native constraint and epoch IF v_epoch = 'seconds' THEN EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , EXTRACT('epoch' FROM v_partition_timestamp_start) , EXTRACT('epoch' FROM v_partition_timestamp_end)); ELSIF v_epoch = 'milliseconds' THEN EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , EXTRACT('epoch' FROM v_partition_timestamp_start) * 1000 , EXTRACT('epoch' FROM v_partition_timestamp_end) * 1000); END IF; -- Create secondary, time-based constraint since native's constraint is already integer based EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%s >= %L AND %4$s < %6$L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_partition_expression , v_partition_timestamp_start , v_partition_timestamp_end); END IF; ELSE -- Non-native always gets time-based constraint EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%s >= %L AND %4$s < %6$L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_partition_expression , v_partition_timestamp_start , v_partition_timestamp_end); IF v_epoch = 'seconds' THEN -- Non-native needs secondary, integer based constraint for epoch EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_int_check' , v_control , EXTRACT('epoch' from v_partition_timestamp_start) , v_control , EXTRACT('epoch' from v_partition_timestamp_end) ); ELSIF v_epoch = 'milliseconds' THEN EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_int_check' , v_control , EXTRACT('epoch' from v_partition_timestamp_start) * 1000 , v_control , EXTRACT('epoch' from v_partition_timestamp_end) * 1000); END IF; EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); -- If custom time, set extra config options. IF v_partition_type = 'time-custom' THEN INSERT INTO @extschema@.custom_time_partitions (parent_table, child_table, partition_range) VALUES ( p_parent_table, v_parent_schema||'.'||v_partition_name, tstzrange(v_partition_timestamp_start, v_partition_timestamp_end, '[)') ); END IF; -- Indexes cannot be created on the parent, so clustering cannot be used for native yet. PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); -- Foreign keys to other tables not supported in native IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; END IF; -- end native check -- NOTE: Privileges currently not automatically inherited for native PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_automatic_maintenance , sub_infinite_time_partitions , sub_jobmon , sub_trigger_exception_handling , sub_template_table FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Subpartitioning %s.%s', v_parent_schema, v_partition_name)); END IF; 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_inherit_fk := %L , p_epoch := %L , p_template_table := %L , p_jobmon := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_automatic_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_template_table , v_row.sub_jobmon); IF p_debug THEN RAISE NOTICE 'create_partition_time (create_parent loop): %', v_sql; END IF; EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions , trigger_exception_handling = v_row.sub_trigger_exception_handling WHERE parent_table = v_parent_schema||'.'||v_partition_name; END LOOP; -- end sub partitioning LOOP -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table, p_job_id := v_job_id, p_debug := p_debug); IF v_publications IS NOT NULL THEN -- NOTE: Publications currently not supported on parent table, but are supported on the table partitions if individually assigned. PERFORM @extschema@.apply_publications(p_parent_table, v_parent_schema, v_partition_name); END IF; v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s. Attempted intervals: %s', p_parent_table, p_partition_times)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; 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_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; 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 , p_debug boolean DEFAULT false) 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' 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); 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); 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 , p_debug := %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 , p_debug); 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@.create_trigger(p_parent_table text) RETURNS void LANGUAGE plpgsql AS $$ DECLARE v_function_name text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_relkind char; v_trig_name text; v_trig_sql text; BEGIN /* * Function to create partitioning trigger on parent table */ 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'); SELECT n.nspname, c.relname, c.relkind INTO v_parent_schema, v_parent_tablename, v_relkind 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_relkind = 'p' THEN RAISE EXCEPTION 'This function cannot run on natively partitioned tables'; ELSIF v_relkind IS NULL THEN RAISE EXCEPTION 'Unable to find given table in system catalogs: %.%', v_parent_schema, v_parent_tablename; END IF; v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); -- Ensure function name matches the naming pattern v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); v_trig_sql := format('CREATE TRIGGER %I BEFORE INSERT ON %I.%I FOR EACH ROW EXECUTE PROCEDURE %I.%I()' , v_trig_name , v_parent_schema , v_parent_tablename , v_parent_schema , v_function_name); EXECUTE v_trig_sql; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); END $$; CREATE OR REPLACE FUNCTION @extschema@.drop_partition_column(p_parent_table text, p_column text) RETURNS void LANGUAGE plpgsql AS $$ DECLARE v_parent_oid oid; v_parent_schema text; v_parent_tablename text; v_row record; BEGIN /* * Function to ensure a column is dropped in all child tables, no matter when it was created */ SELECT c.oid, n.nspname, c.relname INTO v_parent_oid, v_parent_schema, v_parent_tablename 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 'Given parent table does not exist: %', p_parent_table; END IF; EXECUTE format('ALTER TABLE %I.%I DROP COLUMN IF EXISTS %I', v_parent_schema, v_parent_tablename, p_column); FOR v_row IN SELECT n.nspname AS child_schema, c.relname AS child_table 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 inhparent = v_parent_oid LOOP EXECUTE format('ALTER TABLE %I.%I DROP COLUMN IF EXISTS %I', v_row.child_schema, v_row.child_table, p_column); END LOOP; END $$; CREATE OR REPLACE FUNCTION @extschema@.drop_partition_id(p_parent_table text, p_retention bigint DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_control text; v_control_type text; v_count int; v_drop_count int := 0; v_index record; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_max bigint; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_partition_id bigint; v_partition_type text; v_retention bigint; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_row record; v_row_max_id record; v_step_id bigint; BEGIN /* * Function to drop child tables from an id-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman drop_partition_id')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_id already running.'; RETURN 0; END IF; IF p_retention IS NULL THEN SELECT partition_interval::bigint , partition_type , control , retention::bigint , retention_keep_table , retention_keep_index , retention_schema , jobmon INTO v_partition_interval , v_partition_type , v_control , v_retention , v_retention_keep_table , v_retention_keep_index , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND retention IS NOT NULL; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE -- Allow override of configuration options SELECT partition_interval::bigint , partition_type , control , retention_keep_table , retention_keep_index , retention_schema , jobmon INTO v_partition_interval , v_partition_type , v_control , v_retention_keep_table , v_retention_keep_index , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; v_retention := p_retention; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'id' THEN RAISE EXCEPTION 'Data type of control column in given partition set is not an integer type'; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%I) FROM %I.%I', v_control, v_row_max_id.partition_schemaname, v_row_max_id.partition_tablename) INTO v_max; IF v_max IS NOT NULL THEN EXIT; END IF; END LOOP; -- Loop through child tables of the given parent FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'ASC') LOOP SELECT child_start_id INTO v_partition_id FROM @extschema@.show_partition_info(v_row.partition_schemaname||'.'||v_row.partition_tablename , v_partition_interval::text , p_parent_table); -- Add one interval since partition names contain the start of the constraint period IF v_retention <= (v_max - (v_partition_id + v_partition_interval)) THEN -- Do not allow final partition to be dropped SELECT count(*) INTO v_count FROM @extschema@.show_partitions(p_parent_table); IF v_count = 1 THEN RAISE WARNING 'Attempt to drop final partition in partition set % as part of retention policy. Advise reviewing retention policy and/or data entry into the partition set.', p_parent_table; CONTINUE; END IF; -- Only create a jobmon entry if there's actual retention work done IF v_jobmon_schema IS NOT NULL AND v_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN DROP ID PARTITION: %s', p_parent_table)); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Detach/Uninherit table %s.%s from %s', v_row.partition_schemaname, v_row.partition_tablename, p_parent_table)); END IF; IF v_partition_type = 'native' THEN EXECUTE format('ALTER TABLE %I.%I DETACH PARTITION %I.%I' , v_parent_schema , v_parent_tablename , v_row.partition_schemaname , v_row.partition_tablename); ELSE EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_row.partition_schemaname , v_row.partition_tablename , v_parent_schema , v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop table %s.%s', v_row.partition_schemaname, v_row.partition_tablename)); END IF; EXECUTE format('DROP TABLE %I.%I CASCADE', v_row.partition_schemaname, v_row.partition_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN FOR v_index IN WITH child_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE c1.relname = v_row.partition_tablename::name AND n1.nspname = v_row.partition_schema::name ) SELECT c.relname as name , con.conname FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid LEFT JOIN pg_catalog.pg_constraint con ON i.indexrelid = con.conindid JOIN child_info ON i.indrelid = child_info.oid LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop index %s from %s.%s' , v_index.name , v_row.partition_schemaname , v_row.partition_tablename)); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I DROP CONSTRAINT %I', v_row.partition_schemaname, v_row.partition_tablename, v_index.conname); ELSE EXECUTE format('DROP INDEX %I.%I', v_row.partition_schemaname, v_index.name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Moving table %s.%s to schema %s' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema)); END IF; EXECUTE format('ALTER TABLE %I.%I SET SCHEMA %I' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if -- If child table is a subpartition, remove it from part_config & part_config_sub (should cascade due to FK) DELETE FROM @extschema@.part_config WHERE parent_table = v_row.partition_schemaname ||'.'||v_row.partition_tablename; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', format('%s partitions dropped.', v_drop_count)); 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_drop_count; 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 DROP ID PARTITION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE OR REPLACE FUNCTION @extschema@.drop_partition_time(p_parent_table text, p_retention interval DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_control text; v_control_type text; v_count int; v_datetime_string text; v_drop_count int := 0; v_epoch text; v_index record; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_partition_timestamp timestamptz; v_partition_type text; v_retention interval; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_row record; v_step_id bigint; BEGIN /* * Function to drop child tables from a time-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman drop_partition_time')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_time already running.'; RETURN 0; END IF; -- Allow override of configuration options IF p_retention IS NULL THEN SELECT partition_type , control , partition_interval::interval , epoch , retention::interval , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_partition_type , v_control , v_partition_interval , v_epoch , v_retention , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND retention IS NOT NULL; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE SELECT partition_type , partition_interval::interval , epoch , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_partition_type , v_partition_interval , v_epoch , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; v_retention := p_retention; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'time' THEN IF (v_control_type = 'id' AND v_epoch = 'none') OR v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run on partition set without time based control column or epoch flag set with an id column. Found control: %, epoch: %', v_control_type, v_epoch; END IF; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- Loop through child tables of the given parent FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'DESC') LOOP -- pull out datetime portion of partition's tablename to make the next one SELECT child_start_time INTO v_partition_timestamp FROM @extschema@.show_partition_info(v_row.partition_schemaname||'.'||v_row.partition_tablename , v_partition_interval::text , p_parent_table); -- Add one interval since partition names contain the start of the constraint period IF v_retention < (CURRENT_TIMESTAMP - (v_partition_timestamp + v_partition_interval)) THEN -- Do not allow final partition to be dropped SELECT count(*) INTO v_count FROM @extschema@.show_partitions(p_parent_table); IF v_count = 1 THEN RAISE WARNING 'Attempt to drop final partition in partition set % as part of retention policy. If you see this message multiple times for the same table, advise reviewing retention policy and/or data entry into the partition set. Also consider setting "infinite_time_partitions = true" if there are large gaps in data insertion.).', p_parent_table; CONTINUE; END IF; -- Only create a jobmon entry if there's actual retention work done IF v_jobmon_schema IS NOT NULL AND v_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN DROP TIME PARTITION: %s', p_parent_table)); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Detach/Uninherit table %s.%s from %s' , v_row.partition_schemaname , v_row.partition_tablename , p_parent_table)); END IF; IF v_partition_type = 'native' THEN EXECUTE format('ALTER TABLE %I.%I DETACH PARTITION %I.%I' , v_parent_schema , v_parent_tablename , v_row.partition_schemaname , v_row.partition_tablename); ELSE EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_row.partition_schemaname , v_row.partition_tablename , v_parent_schema , v_parent_tablename); END IF; IF v_partition_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_row.partition_schemaname||'.'||v_row.partition_tablename; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop table %s.%s', v_row.partition_schemaname, v_row.partition_tablename)); END IF; EXECUTE format('DROP TABLE %I.%I CASCADE', v_row.partition_schemaname, v_row.partition_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN FOR v_index IN WITH child_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE c1.relname = v_row.partition_tablename::name AND n1.nspname = v_row.partition_schemaname::name ) SELECT c.relname as name , con.conname FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid LEFT JOIN pg_catalog.pg_constraint con ON i.indexrelid = con.conindid JOIN child_info ON i.indrelid = child_info.oid LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop index %s from %s.%s' , v_index.name , v_row.partition_schemaname , v_row.partition_tablename)); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I DROP CONSTRAINT %I' , v_row.partition_schemaname , v_row.partition_tablename , v_index.conname); ELSE EXECUTE format('DROP INDEX %I.%I', v_parent_schema, v_index.name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Moving table %s.%s to schema %s' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema)); END IF; EXECUTE format('ALTER TABLE %I.%I SET SCHEMA %I', v_row.partition_schemaname, v_row.partition_tablename, v_retention_schema); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if -- If child table is a subpartition, remove it from part_config & part_config_sub (should cascade due to FK) DELETE FROM @extschema@.part_config WHERE parent_table = v_row.partition_schemaname||'.'||v_row.partition_tablename; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', format('%s partitions dropped.', v_drop_count)); 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_drop_count; 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 DROP TIME PARTITION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; 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_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_sql text; v_template_oid oid; v_template_table text; v_template_tablespace text; 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; 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 = split_part(v_template_table, '.', 1)::name AND c.relname = split_part(v_template_table, '.', 2)::name; 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 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 '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; 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 '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 '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 'Create FK: %', v_sql; EXECUTE v_sql; END LOOP; END IF; END IF; -- End foreign key creation -- Tablespace inheritance IF 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); EXECUTE v_sql; RAISE DEBUG 'Alter tablespace: %', v_sql; END IF; RETURN true; END $$; CREATE FUNCTION @extschema@.partition_data_id(p_parent_table text , p_batch_count int DEFAULT 1 , p_batch_interval bigint DEFAULT NULL , p_lock_wait numeric DEFAULT 0 , p_order text DEFAULT 'ASC' , p_analyze boolean DEFAULT true , p_source_table text DEFAULT NULL) RETURNS bigint LANGUAGE plpgsql AS $$ DECLARE v_control text; v_control_type text; v_current_partition_name text; v_epoch text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_id bigint; v_min_partition_id bigint; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_parent_tablename_real text; v_partition_interval bigint; v_partition_id bigint[]; v_partition_type text; v_rowcount bigint; v_sql text; v_start_control bigint; v_total_rows bigint := 0; BEGIN /* * Populate the child table(s) of an id-based partition set with old data from the original parent */ SELECT partition_interval::bigint , partition_type , control , epoch INTO v_partition_interval , v_partition_type , v_control , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: No entry in part_config found for given table: %', p_parent_table; END IF; IF v_partition_type = 'native' AND p_source_table IS NULL THEN RAISE EXCEPTION 'Partitioning data for a native partition set requires the p_source_table parameter to be set.'; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'id' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN RAISE EXCEPTION 'Control column for given partition set is not id/serial based or epoch flag is set for time-based partitioning.'; END IF; -- Replace the parent variables with the source variables if using source table for child table data IF p_source_table IS NOT NULL THEN v_parent_tablename_real := v_parent_tablename; -- Preserve for use later v_parent_schema := NULL; v_parent_tablename := NULL; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_source_table, '.', 1)::name AND tablename = split_part(p_source_table, '.', 2)::name; IF v_parent_tablename IS NULL THEN RAISE EXCEPTION 'Given source table does not exist in system catalogs: %', p_source_table; END IF; END IF; 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'); IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; FOR i IN 1..p_batch_count LOOP IF p_order = 'ASC' THEN EXECUTE format('SELECT min(%I) FROM ONLY %I.%I', v_control, v_parent_schema, v_parent_tablename) INTO v_start_control; IF v_start_control IS NULL THEN EXIT; END IF; v_min_partition_id = v_start_control - (v_start_control % v_partition_interval); v_partition_id := ARRAY[v_min_partition_id]; -- Check if custom batch interval overflows current partition maximum IF (v_start_control + p_batch_interval) >= (v_min_partition_id + v_partition_interval) THEN v_max_partition_id := v_min_partition_id + v_partition_interval; ELSE v_max_partition_id := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN EXECUTE 'SELECT max('||v_control||') FROM ONLY '||p_parent_table INTO v_start_control; IF v_start_control IS NULL THEN EXIT; END IF; v_min_partition_id = v_start_control - (v_start_control % v_partition_interval); -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_id := v_min_partition_id + v_partition_interval; v_partition_id := ARRAY[v_min_partition_id]; -- Make sure minimum doesn't underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_id THEN v_min_partition_id = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN v_sql := format('SELECT * FROM ONLY %I.%I WHERE %I >= %s AND %I < %s FOR UPDATE NOWAIT' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_id , v_control , v_max_partition_id); EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; PERFORM @extschema@.create_partition_id(p_parent_table, v_partition_id, p_analyze); v_current_partition_name := @extschema@.check_name_length(COALESCE(v_parent_tablename_real, v_parent_tablename), v_min_partition_id::text, TRUE); EXECUTE format('WITH partition_data AS ( DELETE FROM ONLY %I.%I WHERE %I >= %s AND %I < %s RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_parent_schema , v_parent_tablename , v_control , v_min_partition_id , v_control , v_max_partition_id , v_parent_schema , v_current_partition_name); GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; IF v_partition_type = 'partman' THEN PERFORM @extschema@.create_function_id(p_parent_table); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_total_rows; END $$; CREATE FUNCTION @extschema@.partition_data_time( p_parent_table text , p_batch_count int DEFAULT 1 , p_batch_interval interval DEFAULT NULL , p_lock_wait numeric DEFAULT 0 , p_order text DEFAULT 'ASC' , p_analyze boolean DEFAULT true , p_source_table text DEFAULT NULL) RETURNS bigint LANGUAGE plpgsql AS $$ DECLARE v_control text; v_control_type text; v_datetime_string text; v_current_partition_name text; v_epoch text; v_last_partition text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_timestamp timestamptz; v_min_partition_timestamp timestamptz; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_parent_tablename_real text; v_partition_expression text; v_partition_interval interval; v_partition_suffix text; v_partition_timestamp timestamptz[]; v_partition_type text; v_rowcount bigint; v_start_control timestamptz; v_total_rows bigint := 0; BEGIN /* * Populate the child table(s) of a time-based partition set with old data from the original parent */ SELECT partition_type , partition_interval::interval , control , datetime_string , epoch INTO v_partition_type , v_partition_interval , v_control , v_datetime_string , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: No entry in part_config found for given table: %', p_parent_table; END IF; IF v_partition_type = 'native' AND p_source_table IS NULL THEN RAISE EXCEPTION 'Partitioning data for a native partition set requires the p_source_table parameter to be set.'; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'time' THEN IF (v_control_type = 'id' AND v_epoch = 'none') OR v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run on partition set without time based control column or epoch flag set with an id column. Found control: %, epoch: %', v_control_type, v_epoch; END IF; END IF; -- Replace the parent variables with the source variables if using source table for child table data IF p_source_table IS NOT NULL THEN v_parent_tablename_real := v_parent_tablename; -- Preserve for use later v_parent_schema := NULL; v_parent_tablename := NULL; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_source_table, '.', 1)::name AND tablename = split_part(p_source_table, '.', 2)::name; IF v_parent_tablename IS NULL THEN RAISE EXCEPTION 'Given source table does not exist in system catalogs: %', p_source_table; END IF; END IF; 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'); IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) END; FOR i IN 1..p_batch_count LOOP IF p_order = 'ASC' THEN EXECUTE format('SELECT min(%s) FROM ONLY %I.%I', v_partition_expression, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSIF p_order = 'DESC' THEN EXECUTE format('SELECT max(%s) FROM ONLY %I.%I', v_partition_expression, v_parent_schema, v_parent_tablename) INTO v_start_control; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; IF v_start_control IS NULL THEN EXIT; END IF; IF v_partition_type = 'partman' THEN CASE WHEN v_partition_interval = '15 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '15min'::interval * floor(date_part('minute', v_start_control) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '30min'::interval * floor(date_part('minute', v_start_control) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control); WHEN v_partition_interval = '1 day' THEN v_min_partition_timestamp := date_trunc('day', v_start_control); WHEN v_partition_interval = '1 week' THEN v_min_partition_timestamp := date_trunc('week', v_start_control); WHEN v_partition_interval = '1 month' THEN v_min_partition_timestamp := date_trunc('month', v_start_control); WHEN v_partition_interval = '3 months' THEN v_min_partition_timestamp := date_trunc('quarter', v_start_control); WHEN v_partition_interval = '1 year' THEN v_min_partition_timestamp := date_trunc('year', v_start_control); END CASE; ELSIF v_partition_type IN ('time-custom', 'native') THEN SELECT child_start_time INTO v_min_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition , v_partition_interval::text , p_parent_table); v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; LOOP IF v_start_control >= v_min_partition_timestamp AND v_start_control < v_max_partition_timestamp THEN EXIT; ELSE BEGIN IF v_start_control > v_max_partition_timestamp THEN -- Keep going forward in time, checking if child partition time interval encompasses the current v_start_control value v_min_partition_timestamp := v_max_partition_timestamp; v_max_partition_timestamp := v_max_partition_timestamp + v_partition_interval; ELSE -- Keep going backwards in time, checking if child partition time interval encompasses the current v_start_control value v_max_partition_timestamp := v_min_partition_timestamp; v_min_partition_timestamp := v_min_partition_timestamp - v_partition_interval; END IF; EXCEPTION WHEN datetime_field_overflow THEN RAISE EXCEPTION 'Attempted partition time interval is outside PostgreSQL''s supported time range. Unable to create partition with interval before timestamp % ', v_min_partition_timestamp; END; END IF; END LOOP; END IF; v_partition_timestamp := ARRAY[v_min_partition_timestamp]; IF p_order = 'ASC' THEN -- Ensure batch interval given as parameter doesn't cause maximum to overflow the current partition maximum IF (v_start_control + p_batch_interval) >= (v_min_partition_timestamp + v_partition_interval) THEN v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; ELSE v_max_partition_timestamp := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; -- Ensure batch interval given as parameter doesn't cause minimum to underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_timestamp THEN v_min_partition_timestamp = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM ONLY %I.%I WHERE %s >= %L AND %3$s < %5$L FOR UPDATE NOWAIT' , v_parent_schema , v_parent_tablename , v_partition_expression , v_min_partition_timestamp , v_max_partition_timestamp); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; PERFORM @extschema@.create_partition_time(p_parent_table, v_partition_timestamp, p_analyze); -- This suffix generation code is in create_partition_time() as well v_partition_suffix := to_char(v_min_partition_timestamp, v_datetime_string); v_current_partition_name := @extschema@.check_name_length(COALESCE(v_parent_tablename_real, v_parent_tablename), v_partition_suffix, TRUE); EXECUTE format('WITH partition_data AS ( DELETE FROM ONLY %I.%I WHERE %s >= %L AND %3$s < %5$L RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_parent_schema , v_parent_tablename , v_partition_expression , v_min_partition_timestamp , v_max_partition_timestamp , v_parent_schema , v_current_partition_name); GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; IF v_partition_type IN ('partman', 'time-custom') THEN PERFORM @extschema@.create_function_time(p_parent_table); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_total_rows; END $$; CREATE OR REPLACE FUNCTION @extschema@.reapply_privileges(p_parent_table text) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_row record; v_step_id bigint; BEGIN /* * Function to re-apply ownership & privileges on all child tables in a partition set using parent table as reference */ SELECT jobmon INTO v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon IS NULL THEN RAISE EXCEPTION 'Given table is not managed by this extention: %', p_parent_table; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; IF v_parent_tablename IS NULL THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RAISE EXCEPTION 'Given parent table does not exist: %', p_parent_table; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN RE-APPLYING PRIVILEGES TO ALL CHILD TABLES OF: %s', p_parent_table)); END IF; FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'ASC') LOOP PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_row.partition_schemaname, v_row.partition_tablename, v_job_id); END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); 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 RE-APPLYING PRIVILEGES TO ALL CHILD TABLES OF: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE OR REPLACE FUNCTION @extschema@.run_maintenance(p_parent_table text DEFAULT NULL, p_analyze boolean DEFAULT NULL, p_jobmon boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_analyze boolean; v_check_subpart int; v_control_type text; v_create_count int := 0; v_current_partition text; v_current_partition_id bigint; v_current_partition_timestamp timestamptz; v_default_tablename text; v_drop_count int := 0; v_is_default text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_created boolean; v_last_partition_id bigint; v_last_partition_timestamp timestamptz; v_max_id_parent bigint; v_max_time_parent timestamptz; v_new_search_path text := '@extschema@,pg_temp'; v_next_partition_id bigint; v_next_partition_timestamp timestamptz; v_old_search_path text; v_parent_exists text; v_parent_schema text; v_parent_tablename text; v_partition_expression text; v_premade_count int; v_premake_id_max bigint; v_premake_id_min bigint; v_premake_timestamp_min timestamptz; v_premake_timestamp_max timestamptz; v_row record; v_row_max_id record; v_row_max_time record; v_row_sub record; v_step_id bigint; v_step_overflow_id bigint; v_sub_id_max bigint; v_sub_id_max_suffix bigint; v_sub_id_min bigint; v_sub_parent text; v_sub_timestamp_max timestamptz; v_sub_timestamp_max_suffix timestamptz; v_sub_timestamp_min timestamptz; v_tablename text; v_tables_list_sql text; BEGIN /* * Function to manage pre-creation of the next partitions in a set. * Also manages dropping old partitions if the retention option is set. * If p_parent_table is passed, will only run run_maintenance() on that one table (no matter what the configuration table may have set for it) * Otherwise, will run on all tables in the config table with p_automatic_maintenance() set to true. * For large partition sets, running analyze can cause maintenance to take longer than expected. Can set p_analyze to false to avoid a forced analyze run on PG versions before 11. 11+ does not analyze by default anymore. * Be aware that constraint exclusion may not work properly until an analyze on the partition set is run. */ v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman maintenance already running.'; RETURN; 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'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN RUN MAINTENANCE'); v_step_id := add_step(v_job_id, 'Running maintenance loop'); END IF; v_row := NULL; -- Ensure it's reset v_tables_list_sql := 'SELECT parent_table , partition_type , partition_interval , control , premake , undo_in_progress , sub_partition_set_full , epoch , infinite_time_partitions , retention FROM @extschema@.part_config WHERE undo_in_progress = false'; IF p_parent_table IS NULL THEN v_tables_list_sql := v_tables_list_sql || ' AND automatic_maintenance = ''on'''; ELSE v_tables_list_sql := v_tables_list_sql || format(' AND parent_table = %L', p_parent_table); END IF; FOR v_row IN EXECUTE v_tables_list_sql LOOP CONTINUE WHEN v_row.undo_in_progress; -- When sub-partitioning, retention may drop tables that were already put into the query loop values. -- Check if they still exist in part_config before continuing v_parent_exists := NULL; SELECT parent_table INTO v_parent_exists FROM @extschema@.part_config WHERE parent_table = v_row.parent_table; RAISE DEBUG 'Parent table possibly removed from part_config by retenion'; CONTINUE WHEN v_parent_exists IS NULL; -- Check for consistent data in part_config_sub table. Was unable to get this working properly as either a constraint or trigger. -- Would either delay raising an error until the next write (which I cannot predict) or disallow future edits to update a sub-partition set's configuration. -- This way at least provides a consistent way to check that I know will run. If anyone can get a working constraint/trigger, please help! SELECT sub_parent INTO v_sub_parent FROM @extschema@.part_config_sub WHERE sub_parent = v_row.parent_table; IF v_sub_parent IS NOT NULL THEN SELECT count(*) INTO v_check_subpart FROM @extschema@.check_subpart_sameconfig(v_row.parent_table); IF v_check_subpart > 1 THEN RAISE EXCEPTION 'Inconsistent data in part_config_sub table. Sub-partition tables that are themselves sub-partitions cannot have differing configuration values among their siblings. Run this query: "SELECT * FROM @extschema@.check_subpart_sameconfig(''%'');" This should only return a single row or nothing. If multiple rows are returned, results are all children of the given parent. Update the differing values to be consistent for your desired values.', v_row.sub_parent; END IF; END IF; -- Shouldn't need to analyze tables for most statistics for native sets on PG11+ by default anymore IF p_analyze IS NULL THEN IF v_row.partition_type = 'native' AND current_setting('server_version_num')::int >= 110000 THEN v_analyze := false; ELSE v_analyze := true; END IF; END IF; SELECT n.nspname, c.relname INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = split_part(v_row.parent_table, '.', 1)::name AND c.relname = split_part(v_row.parent_table, '.', 2)::name; -- Used below to see if there's any data in the parent (<=PG10) or default (PG11+) child table. IF v_row.partition_type = 'native' AND current_setting('server_version_num')::int >= 110000 THEN -- Always returns the default partition first if it exists SELECT partition_tablename INTO v_default_tablename FROM @extschema@.show_partitions(v_row.parent_table, p_include_default := true) LIMIT 1; SELECT pg_get_expr(relpartbound, v_row.parent_table::regclass) INTO v_is_default FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n on c.relnamespace = n.oid WHERE n.nspname = v_parent_schema AND c.relname = v_default_tablename; IF v_is_default != 'DEFAULT' THEN v_default_tablename := v_parent_tablename; END IF; ELSE v_default_tablename := v_parent_tablename; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_row.control); v_partition_expression := CASE WHEN v_row.epoch = 'seconds' THEN format('to_timestamp(%I)', v_row.control) WHEN v_row.epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_row.control) ELSE format('%I', v_row.control) END; IF p_debug THEN RAISE NOTICE 'run_maint: v_partition_expression: %', v_partition_expression; END IF; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LIMIT 1; IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_last_partition: %', v_row.parent_table, v_last_partition; END IF; IF v_control_type = 'time' OR (v_control_type = 'id' AND v_row.epoch <> 'none') THEN -- Run retention if needed IF v_row.retention IS NOT NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); IF v_drop_count > 0 AND v_row.partition_type <> 'native' THEN PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); END IF; END IF; IF v_row.sub_partition_set_full THEN CONTINUE; END IF; SELECT child_start_time INTO v_last_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_row.partition_interval, v_row.parent_table); -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. IF v_row.infinite_time_partitions IS TRUE THEN -- Set it to "now" so new partitions continue to be created -- For infinite_time_partitions, don't bother getting the max value in the partitions v_current_partition_timestamp = CURRENT_TIMESTAMP; ELSE FOR v_row_max_time IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%s)::text FROM %I.%I' , v_partition_expression , v_row_max_time.partition_schemaname , v_row_max_time.partition_tablename ) INTO v_current_partition_timestamp; IF v_current_partition_timestamp IS NOT NULL THEN SELECT suffix_timestamp INTO v_current_partition_timestamp FROM @extschema@.show_partition_name(v_row.parent_table, v_current_partition_timestamp::text); EXIT; END IF; END LOOP; END IF; -- end infinite time check -- Check for values in the parent/default table. If they are there and greater than all child values, use that instead -- This allows maintenance to continue working properly if there is a large gap in data insertion. Data will remain in parent, but new tables will be created EXECUTE format('SELECT max(%s) FROM ONLY %I.%I', v_partition_expression, v_parent_schema, v_default_tablename) INTO v_max_time_parent; IF p_debug THEN RAISE NOTICE 'run_maint: v_current_partition_timestamp: %, v_max_time_parent: %', v_current_partition_timestamp, v_max_time_parent; END IF; IF v_max_time_parent > v_current_partition_timestamp THEN SELECT suffix_timestamp INTO v_current_partition_timestamp FROM @extschema@.show_partition_name(v_row.parent_table, v_max_time_parent::text); END IF; IF v_current_partition_timestamp IS NULL THEN -- Partition set is completely empty and infinite time partitions not set -- Nothing to do CONTINUE; END IF; -- If this is a subpartition, determine if the last child table has been made. If so, mark it as full so future maintenance runs can skip it SELECT sub_min::timestamptz, sub_max::timestamptz INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'time'); IF v_sub_timestamp_max IS NOT NULL THEN SELECT suffix_timestamp INTO v_sub_timestamp_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_timestamp_max::text); IF v_sub_timestamp_max_suffix = v_last_partition_timestamp THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; -- Check and see how many premade partitions there are. v_premade_count = round(EXTRACT('epoch' FROM age(v_last_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); v_next_partition_timestamp := v_last_partition_timestamp; IF p_debug THEN RAISE NOTICE 'run_maint before loop: current_partition_timestamp: %, v_premade_count: %, v_sub_timestamp_min: %, v_sub_timestamp_max: %' , v_current_partition_timestamp , v_premade_count , v_sub_timestamp_min , v_sub_timestamp_max; END IF; -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed WHILE (v_premade_count < v_row.premake) LOOP IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_premade_count: %, v_next_partition_timestamp: %', v_row.parent_table, v_premade_count, v_next_partition_timestamp; END IF; IF v_next_partition_timestamp < v_sub_timestamp_min OR v_next_partition_timestamp > v_sub_timestamp_max THEN -- With subpartitioning, no need to run if the timestamp is not in the parent table's range EXIT; END IF; BEGIN v_next_partition_timestamp := v_next_partition_timestamp + v_row.partition_interval::interval; EXCEPTION WHEN datetime_field_overflow THEN v_premade_count := v_row.premake; -- do this so it can exit the premake check loop and continue in the outer for loop IF v_jobmon_schema IS NOT NULL THEN 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', format('Child partition creation skipped for parent table: %s', v_partition_time)); END IF; RAISE WARNING 'Attempted partition time interval is outside PostgreSQL''s supported time range. Child partition creation skipped for parent table %', v_row.parent_table; CONTINUE; END; v_last_partition_created := @extschema@.create_partition_time(v_row.parent_table , ARRAY[v_next_partition_timestamp] , v_analyze , p_debug := p_debug); IF v_last_partition_created THEN v_create_count := v_create_count + 1; IF v_row.partition_type <> 'native' THEN PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); END IF; END IF; v_premade_count = round(EXTRACT('epoch' FROM age(v_next_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); END LOOP; ELSIF v_control_type = 'id' THEN -- Run retention if needed IF v_row.retention IS NOT NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); IF v_drop_count > 0 AND v_row.partition_type <> 'native' THEN PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); END IF; END IF; IF v_row.sub_partition_set_full THEN CONTINUE; END IF; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%I)::text FROM %I.%I' , v_row.control , v_row_max_id.partition_schemaname , v_row_max_id.partition_tablename) INTO v_current_partition_id; IF v_current_partition_id IS NOT NULL THEN SELECT suffix_id INTO v_current_partition_id FROM @extschema@.show_partition_name(v_row.parent_table, v_current_partition_id::text); EXIT; END IF; END LOOP; -- Check for values in the parent/default table. If they are there and greater than all child values, use that instead -- This allows maintenance to continue working properly if there is a large gap in data insertion. Data will remain in parent, but new tables will be created EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_row.control, v_parent_schema, v_default_tablename) INTO v_max_id_parent; IF v_max_id_parent > v_current_partition_id THEN SELECT suffix_id INTO v_current_partition_id FROM @extschema@.show_partition_name(v_row.parent_table, v_max_id_parent::text); END IF; IF v_current_partition_id IS NULL THEN -- Partition set is completely empty. Nothing to do CONTINUE; END IF; SELECT child_start_id INTO v_last_partition_id FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_row.partition_interval, v_row.parent_table); -- Determine if this table is a child of a subpartition parent. If so, get limits to see if run_maintenance even needs to run for it. SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'id'); IF v_sub_id_max IS NOT NULL THEN SELECT suffix_id INTO v_sub_id_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_id_max::text); IF v_sub_id_max_suffix = v_last_partition_id THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; v_next_partition_id := v_last_partition_id; v_premade_count := ((v_last_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed. WHILE (v_premade_count < v_row.premake) LOOP IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_premade_count: %, v_next_partition_id: %', v_row.parent_table, v_premade_count, v_next_partition_id; END IF; IF v_next_partition_id < v_sub_id_min OR v_next_partition_id > v_sub_id_max THEN -- With subpartitioning, no need to run if the id is not in the parent table's range EXIT; END IF; v_next_partition_id := v_next_partition_id + v_row.partition_interval::bigint; v_last_partition_created := @extschema@.create_partition_id(v_row.parent_table, ARRAY[v_next_partition_id], v_analyze); IF v_last_partition_created THEN v_create_count := v_create_count + 1; IF v_row.partition_type <> 'native' THEN PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); END IF; END IF; v_premade_count := ((v_next_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); END LOOP; END IF; -- end main IF check for time or id END LOOP; -- end of creation loop IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Partition maintenance finished. %s partitions made. %s partitions dropped.', v_create_count, v_drop_count)); 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'); 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 RUN MAINTENANCE'')', v_jobmon_schema) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE OR REPLACE FUNCTION @extschema@.show_partition_info(p_child_table text , p_partition_interval text DEFAULT NULL , p_parent_table text DEFAULT NULL , OUT child_start_time timestamptz , OUT child_end_time timestamptz , OUT child_start_id bigint , OUT child_end_id bigint , OUT suffix text) RETURNS record LANGUAGE plpgsql STABLE AS $$ DECLARE v_child_schema text; v_child_tablename text; v_control text; v_control_type text; v_datetime_string text; v_epoch text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_table text; v_partition_interval text; v_partition_type text; v_quarter text; v_suffix text; v_suffix_position int; v_year text; BEGIN /* * Show the data boundries for a given child table as well as the suffix that will be used. * Passing the parent table argument improves performance by avoiding a catalog lookup. * Passing an interval lets you set one different than the default configured one if desired. */ 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'); SELECT n.nspname, c.relname INTO v_child_schema, v_child_tablename FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = split_part(p_child_table, '.', 1)::name AND c.relname = split_part(p_child_table, '.', 2)::name; IF v_child_tablename IS NULL THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RAISE EXCEPTION 'Child table given does not exist (%)', p_child_table; END IF; IF p_parent_table IS NULL THEN SELECT n.nspname||'.'|| c.relname INTO v_parent_table FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhparent JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhrelid::regclass = p_child_table::regclass; ELSE v_parent_table := p_parent_table; END IF; IF p_partition_interval IS NULL THEN SELECT control, partition_interval, partition_type, datetime_string, epoch INTO v_control, v_partition_interval, v_partition_type, v_datetime_string, v_epoch FROM @extschema@.part_config WHERE parent_table = v_parent_table; ELSE v_partition_interval := p_partition_interval; SELECT control, partition_type, datetime_string, epoch INTO v_control, v_partition_type, v_datetime_string, v_epoch FROM @extschema@.part_config WHERE parent_table = v_parent_table; END IF; IF v_control IS NULL THEN RAISE EXCEPTION 'Parent table of given child not managed by pg_partman: %', v_parent_table; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_child_schema, v_child_tablename, v_control); v_suffix_position := (length(v_child_tablename) - position('p_' in reverse(v_child_tablename))) + 2; v_suffix := substring(v_child_tablename from v_suffix_position); IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN IF v_partition_interval::interval <> '3 months' OR (v_partition_interval::interval = '3 months' AND v_partition_type = 'time-custom') THEN child_start_time := to_timestamp(v_suffix, v_datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(v_suffix, 'q', 1); v_quarter := split_part(v_suffix, 'q', 2); CASE WHEN v_quarter = '1' THEN child_start_time := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN child_start_time := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN child_start_time := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN child_start_time := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); END CASE; END IF; child_end_time := (child_start_time + v_partition_interval::interval) - '1 second'::interval; ELSIF v_control_type = 'id' THEN child_start_id := v_suffix::bigint; child_end_id := (child_start_id + v_partition_interval::bigint) - 1; ELSE RAISE EXCEPTION 'Invalid partition type encountered in show_partition_info()'; END IF; suffix = v_suffix; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN; END $$; CREATE FUNCTION @extschema@.show_partitions (p_parent_table text, p_order text DEFAULT 'ASC', p_include_default boolean DEFAULT false) RETURNS TABLE (partition_schemaname text, partition_tablename text) LANGUAGE plpgsql STABLE SET search_path = @extschema@,pg_temp AS $$ DECLARE v_control text; v_control_type text; v_datetime_string text; v_default_sql text; v_epoch text; v_parent_schema text; v_parent_tablename text; v_partition_interval text; v_partition_type text; v_sql text; BEGIN /* * Function to list all child partitions in a set in logical order. * Default partition is not listed by default since that's the common usage internally * If p_include_default is set true, default is always listed first. */ IF upper(p_order) NOT IN ('ASC', 'DESC') THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RAISE EXCEPTION 'p_order paramter must be one of the following values: ASC, DESC'; END IF; SELECT partition_type , partition_interval , datetime_string , control , epoch INTO v_partition_type , v_partition_interval , v_datetime_string , v_control , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_partition_type IS NULL THEN RAISE EXCEPTION 'Given parent table not managed by pg_partman: %', p_parent_table; END IF; SELECT n.nspname, c.relname INTO v_parent_schema, v_parent_tablename 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_tablename IS NULL THEN RAISE EXCEPTION 'Given parent table not found in system catalogs: %', p_parent_table; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); RAISE DEBUG 'show_partitions: v_parent_schema: %, v_parent_tablename: %, v_datetime_string: %', v_parent_schema, v_parent_tablename, v_datetime_string; v_sql := format('SELECT n.nspname::text AS partition_schemaname, c.relname::text AS partition_name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = ''%I.%I''::regclass' , v_parent_schema , v_parent_tablename); IF v_partition_type = 'native' AND current_setting('server_version_num')::int >= 110000 THEN IF p_include_default THEN -- Return the default partition immediately as first item in list v_default_sql := v_sql || format(' AND pg_get_expr(relpartbound, c.oid) = ''DEFAULT'''); RAISE DEBUG 'show_partitions: v_default_sql: %', v_default_sql; RETURN QUERY EXECUTE v_default_sql; END IF; v_sql := v_sql || format(' AND pg_get_expr(relpartbound, c.oid) != ''DEFAULT'''); END IF; IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN IF v_partition_interval::interval <> '3 months' OR (v_partition_interval::interval = '3 months' AND v_partition_type = 'time-custom') THEN v_sql := v_sql || format(' ORDER BY to_timestamp(substring(c.relname from ((length(c.relname) - position(''p_'' in reverse(c.relname))) + 2) ), %L) %s' , v_datetime_string , p_order); ELSE -- For quarterly, to_timestamp() doesn't recognize "Q" in datetime string. -- First order by just the year, then order by the quarter number (should be last character in table name) v_sql := v_sql || format(' ORDER BY to_timestamp(substring(c.relname from ((length(c.relname) - position(''p_'' in reverse(c.relname))) + 2) for 4), ''YYYY'') %s , substring(reverse(c.relname) from 1 for 1) %s' , p_order , p_order); END IF; ELSIF v_control_type = 'id' THEN v_sql := v_sql || format(' ORDER BY substring(c.relname from ((length(c.relname) - position(''p_'' in reverse(c.relname))) + 2) )::bigint %s' , p_order); END IF; RAISE DEBUG 'show_partitions: v_sql: %', v_sql; RETURN QUERY EXECUTE v_sql; END $$; CREATE OR REPLACE FUNCTION stop_sub_partition(p_parent_table text, p_jobmon boolean DEFAULT true) RETURNS boolean LANGUAGE plpgsql AS $$ DECLARE v_job_id bigint; v_jobmon_schema text; v_step_id bigint; BEGIN /* * Stop a given parent table from causing its children to be subpartitioned */ 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; END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.add_job(''PARTMAN STOP SUBPARTITIONING'')', v_jobmon_schema) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''Stopped subpartitioning for %s'')', v_jobmon_schema, v_job_id, p_parent_table) INTO v_step_id; END IF; DELETE FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.update_step(%s, %L, %L)', v_jobmon_schema, v_step_id, 'OK', 'Done'); EXECUTE format('SELECT %I.close_job(%s)', v_jobmon_schema, v_job_id); END IF; RETURN true; END $$; CREATE FUNCTION @extschema@.undo_partition(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval text DEFAULT NULL, p_keep_table boolean DEFAULT true, p_lock_wait numeric DEFAULT 0, p_target_table text DEFAULT NULL, OUT partitions_undone int, OUT rows_undone bigint) RETURNS record LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_batch_interval_id bigint; v_batch_interval_time interval; v_batch_loop_count int := 0; v_child_loop_total bigint := 0; v_child_table text; v_control text; v_control_type text; v_child_min_id bigint; v_child_min_time timestamptz; v_epoch text; v_function_name text; v_jobmon boolean; v_jobmon_schema text; v_job_id bigint; v_inner_loop_count int; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_new_search_path text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_expression text; v_partition_interval text; v_partition_type text; v_relkind char; v_row record; v_rowcount bigint; v_step_id bigint; v_sub_count int; v_target_schema text; v_target_tablename text; v_template_schema text; v_template_siblings int; v_template_table text; v_template_tablename text; v_total bigint := 0; v_trig_name text; v_undo_count int := 0; BEGIN /* * For native, moves data to new, target table since data cannot be moved to parent. * Leaves old parent table as is and does not change name of new table. * For trigger-based, moves data to parent */ v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition_native')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition_native already running.'; partitions_undone = -1; RETURN; END IF; IF p_parent_table = p_target_table THEN RAISE EXCEPTION 'Target table cannot be the same as the parent table'; END IF; SELECT partition_interval::text , partition_type , control , jobmon , epoch , template_table INTO v_partition_interval , v_partition_type , v_control , v_jobmon , v_epoch , v_template_table FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_control IS NULL THEN RAISE EXCEPTION 'No configuration found for pg_partman for given parent table: %', p_parent_table; END IF; IF v_partition_type = 'native' AND p_target_table IS NULL THEN RAISE EXCEPTION 'Natively partitioned tables require setting the p_target_table option'; END IF; SELECT n.nspname, c.relname, c.relkind INTO v_parent_schema, v_parent_tablename, v_relkind 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_tablename IS NULL THEN RAISE EXCEPTION 'Given parent table not found in system catalogs: %', p_parent_table; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN IF p_batch_interval IS NULL THEN v_batch_interval_time := v_partition_interval::interval; ELSE v_batch_interval_time := p_batch_interval::interval; END IF; ELSIF v_control_type = 'id' THEN IF p_batch_interval IS NULL THEN v_batch_interval_id := v_partition_interval::bigint; ELSE v_batch_interval_id := p_batch_interval::bigint; END IF; ELSE RAISE EXCEPTION 'Data type of control column in given partition set must be either data/time or integer.'; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Check if any child tables are themselves partitioned or part of an inheritance tree. Prevent undo at this level if so. -- Need to lock child tables at all levels before multi-level undo can be performed safely. FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table) LOOP SELECT count(*) INTO v_sub_count FROM pg_catalog.pg_inherits i JOIN pg_catalog.pg_class c ON i.inhparent = c.oid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_row.partition_tablename::name AND n.nspname = v_row.partition_schemaname::name; IF v_sub_count > 0 THEN RAISE EXCEPTION 'Child table for this parent has child table(s) itself (%). Run undo partitioning on this table or remove inheritance first to ensure all data is properly moved to parent', v_row.partition_schemaname||'.'||v_row.partition_tablename; END IF; END LOOP; IF p_target_table IS NOT NULL THEN SELECT n.nspname, c.relname INTO v_target_schema, v_target_tablename FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = split_part(p_target_table, '.', 1)::name AND c.relname = split_part(p_target_table, '.', 2)::name; ELSE v_target_schema := v_parent_schema; v_target_tablename := v_parent_tablename; END IF; IF v_target_tablename IS NULL THEN RAISE EXCEPTION 'Given target table not found in system catalogs: %', p_target_table; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN UNDO PARTITIONING: %s', p_parent_table)); v_step_id := add_step(v_job_id, format('Undoing partitioning for table %s', p_parent_table)); END IF; v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) END; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; IF v_partition_type != 'native' THEN -- Stop data going into child tables on non-native partition sets. v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); -- Double-check for proper object existence SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t JOIN pg_catalog.pg_class c ON t.tgrelid = c.oid WHERE tgname = v_trig_name::name AND c.relname = v_parent_tablename::name; SELECT proname INTO v_function_name FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid WHERE n.nspname = v_parent_schema::name AND proname = v_function_name::name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_parent_tablename); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; partitions_undone = -1; RETURN; END IF; END IF; -- END p_lock_wait IF EXECUTE format('DROP TRIGGER IF EXISTS %I ON %I.%I', v_trig_name, v_parent_schema, v_parent_tablename); END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later IF v_function_name IS NOT NULL THEN EXECUTE format('DROP FUNCTION IF EXISTS %I.%I()', v_parent_schema, v_function_name); END IF; END IF; -- end pg_partman trigger cleanup IF v_jobmon_schema IS NOT NULL THEN IF (v_trig_name IS NOT NULL OR v_function_name IS NOT NULL) THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); ELSE PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process.'); END IF; END IF; <> LOOP -- Get ordered list of child table in set. Store in variable one at a time per loop until none are left or batch count is reached. -- This easily allows it to loop over same child table until empty or move onto next child table after it's dropped -- Include the native default table to ensure all data there is removed as well (final parameter = true) SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC', TRUE) LIMIT 1; EXIT outer_child_loop WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Removing child partition: %s.%s', v_parent_schema, v_child_table)); END IF; IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN EXECUTE format('SELECT min(%s) FROM %I.%I', v_partition_expression, v_parent_schema, v_child_table) INTO v_child_min_time; ELSIF v_control_type = 'id' THEN EXECUTE format('SELECT min(%s) FROM %I.%I', v_partition_expression, v_parent_schema, v_child_table) INTO v_child_min_id; END IF; IF v_child_min_time IS NULL AND v_child_min_id IS NULL THEN -- No rows left in this child table. Remove from partition set. -- lockwait timeout for table drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_child_table); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; partitions_undone = -1; RETURN; END IF; END IF; -- END p_lock_wait IF v_lock_obtained := FALSE; -- reset for reuse later IF v_partition_type = 'native' THEN EXECUTE format('ALTER TABLE %I.%I DETACH PARTITION %I.%I' , v_parent_schema , v_parent_tablename , v_parent_schema , v_child_table); ELSE EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_parent_schema , v_child_table , v_parent_schema , v_parent_tablename); END IF; IF p_keep_table = false THEN EXECUTE format('DROP TABLE %I.%I', v_parent_schema, v_child_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table DROPPED. Moved %s rows to target table', v_child_loop_total)); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table DETACHED/UNINHERITED from parent, not DROPPED. Moved %s rows to target table', v_child_loop_total)); END IF; END IF; IF v_partition_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_parent_schema||'.'||v_child_table; END IF; v_undo_count := v_undo_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached CONTINUE outer_child_loop; -- skip data moving steps below END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM %I.%I WHERE %I <= %L FOR UPDATE NOWAIT' , v_parent_schema , v_child_table , v_control , v_child_min_time + (v_batch_interval_time * v_inner_loop_count)); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; partitions_undone = -1; RETURN; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval EXECUTE format('WITH move_data AS ( DELETE FROM %I.%I WHERE %s <= %L RETURNING *) INSERT INTO %I.%I SELECT * FROM move_data' , v_parent_schema , v_child_table , v_partition_expression , v_child_min_time + (v_batch_interval_time * v_inner_loop_count) , v_target_schema , v_target_tablename); GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Moved %s rows to target table.', v_child_loop_total)); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; -- Check again if table is empty and go to outer loop again to drop it if so EXECUTE format('SELECT min(%s) FROM %I.%I', v_partition_expression, v_parent_schema, v_child_table) INTO v_child_min_time; CONTINUE outer_child_loop WHEN v_child_min_time IS NULL; ELSIF v_control_type = 'id' THEN IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM %I.%I WHERE %I <= %L FOR UPDATE NOWAIT' , v_parent_schema , v_child_table , v_control , v_child_min_id + (v_batch_interval_id * v_inner_loop_count)); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; partitions_undone = -1; RETURN; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval EXECUTE format('WITH move_data AS ( DELETE FROM %I.%I WHERE %s <= %L RETURNING *) INSERT INTO %I.%I SELECT * FROM move_data' , v_parent_schema , v_child_table , v_partition_expression , v_child_min_id + (v_batch_interval_id * v_inner_loop_count) , v_target_schema , v_target_tablename); GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Moved %s rows to target table.', v_child_loop_total)); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; -- Check again if table is empty and go to outer loop again to drop it if so EXECUTE format('SELECT min(%s) FROM %I.%I', v_partition_expression, v_parent_schema, v_child_table) INTO v_child_min_id; CONTINUE outer_child_loop WHEN v_child_min_id IS NULL; END IF; -- end v_control_type check EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC', TRUE) LIMIT 1; IF v_child_table IS NULL THEN DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; -- Check if any other config entries still have this template table and don't remove if so -- Allows other sibling/parent tables to still keep using in case entire partition set isn't being undone SELECT count(*) INTO v_template_siblings FROM @extschema@.part_config WHERE template_table = v_template_table; 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(v_template_table, '.', 1)::name AND c.relname = split_part(v_template_table, '.', 2)::name; IF v_template_siblings = 0 AND v_template_tablename IS NOT NULL THEN EXECUTE format('DROP TABLE IF EXISTS %I.%I', v_template_schema, v_template_tablename); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Moved % row(s) to the target table. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', format('Moved %s row(s) to the target table. Removed %s partitions.', v_total, v_undo_count)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); partitions_undone := v_undo_count; rows_undone := v_total; 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 UNDO PARTITIONING: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; -- Restore dropped object privileges DO $$ DECLARE v_row record; BEGIN FOR v_row IN SELECT statement FROM partman_preserve_privs_temp LOOP IF v_row.statement IS NOT NULL THEN EXECUTE v_row.statement; END IF; END LOOP; END $$; DROP TABLE IF EXISTS partman_preserve_privs_temp; pg_partman-4.6.0/updates/pg_partman--4.0.0--4.1.0.sql000066400000000000000000004104751412761706100214340ustar00rootroot00000000000000-- For PostgreSQL 10+ and native partitioning, privilege inheritance is now optional and turned off by default. This now allows pg_partman to run fully as a non-superuser when using native partitioning. (Github Issue #231) -- This means that all interaction with these partition sets must be done through the parent table. -- A new column in the part_config table, inherit_privileges, is available if you require direct access to the child tables. Note that this will then require superuser at some level (superuser ownershipt of pg_partman functions for SECURITY DEFINER or running maintenance as a superuser). Just set this option to true and run the reapply_privileges() function to set all child privileges to match the parent. From then on, all new child tables will inherit the parent's privileges. -- Note that for non-native partitioning on all versions, privileges will continue to be inherited to avoid backward compatability issues and will therefore require a superuser as the above bullet notes. -- All existing partition sets have had their configuration values set to true so they will continue to act as they did before this update. -- Updated GRANTS that are required for a non-superuser are in the INSTALLATION section of the readme. -- The partition_data*() functions now work with the new DEFAULT table feature of PG11. Running these functions will now move data out of the default partition and into the proper child tables, creating them if necessary (Github Issue #230). -- Note that normal maintenance can fail if data goes into the default table since data that exists in the default cannot have a corresponding child created without taking extra steps. Those extra steps are now handled by partition_data*() and, once that is run, maintenance should be able to proceed as normal. -- Renamed check_parent() function to check_default(). This function now checks both the parent table in trigger-based partition sets as well as the default table introduced in PostgreSQL 11. -- The reapply_indexes.py script now only works on trigger-based partition sets or native partition sets in PG10. This script will likely never work with native partitioning in PG11+ without being extremely prone to unexpected behavior, so it is currently not supported at all. Be aware that non-unique index inheritance is built in for PG11+ and that should be used if possible (Github Issue #232). -- Fixed partition_data_proc() not working for epoch time partitioned sets (Github Pull Request #239). -- Use SplitIdentifierString() in BGW for more recent versions of PostgreSQL (10.5+) (Github Pull Request #241) -- Fixed show_partition_info() to account for quarterly/3month partitioning that may not use the "q" naming convention (Github Issue #252, PR #251). -- Set the drop indexes retention feature to only be usable with trigger-based partitioning or for native, only on PG10 and lower. Natively inherited indexes cannot be dropped from children. ALTER TABLE @extschema@.part_config ADD COLUMN inherit_privileges boolean DEFAULT false; ALTER TABLE @extschema@.part_config_sub ADD COLUMN sub_inherit_privileges boolean DEFAULT false; UPDATE @extschema@.part_config SET inherit_privileges = true; UPDATE @extschema@.part_config_sub SET sub_inherit_privileges = true; ALTER TYPE @extschema@.check_parent_table RENAME TO check_default_table; ALTER TYPE @extschema@.check_default_table RENAME ATTRIBUTE parent_table TO default_table; -- ######################## START POSTGRESQL 11 ONLY SECTION ############################## DO $pg11only$ DECLARE v_partition_data_sql text; BEGIN IF current_setting('server_version_num')::int >= 110000 THEN -- ######################## START POSTGRESQL 11 ONLY SECTION ############################## -- Syntax check during extension updating doesn't even allow the CREATE PROCEDURE statement to exist. Using dollar quoting w/ EXECUTE to get around it in this conditional IF block. v_partition_data_sql := $partition_data$ CREATE OR REPLACE PROCEDURE @extschema@.partition_data_proc (p_parent_table text, p_interval text DEFAULT NULL, p_batch int DEFAULT NULL, p_wait int DEFAULT 1, p_source_table text DEFAULT NULL, p_order text DEFAULT 'ASC', p_lock_wait int DEFAULT 0, p_lock_wait_tries int DEFAULT 10, p_quiet boolean DEFAULT false) LANGUAGE plpgsql AS $$ DECLARE v_adv_lock boolean; v_batch_count int := 0; v_control text; v_control_type text; v_epoch text; v_is_autovac_off boolean := false; v_lockwait_count int := 0; v_parent_schema text; v_parent_tablename text; v_row record; v_rows_moved bigint; v_source_schema text; v_source_tablename text; v_sql text; v_total bigint := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman partition_data_proc'), hashtext(p_parent_table)); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman partition_data_proc already running for given parent table: %.', p_parent_table; RETURN; END IF; SELECT control, epoch INTO v_control, v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: No entry in part_config found for given table: %', p_parent_table; END IF; SELECT n.nspname, c.relname INTO v_parent_schema, v_parent_tablename 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_tablename IS NULL THEN RAISE EXCEPTION 'Unable to find given parent table in system catalogs. Ensure it is schema qualified: %', p_parent_table; END IF; IF p_source_table IS NOT NULL THEN SELECT n.nspname, c.relname INTO v_source_schema, v_source_tablename FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = split_part(p_source_table, '.', 1)::name AND c.relname = split_part(p_source_table, '.', 2)::name; IF v_source_tablename IS NULL THEN RAISE EXCEPTION 'Unable to find given source table in system catalogs. Ensure it is schema qualified: %', p_source_table; END IF; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type = 'id' AND v_epoch <> 'none' THEN v_control_type := 'time'; END IF; /* -- Currently no way to catch exception and reset autovac settings back to normal. Until I can do that, leaving this feature out for now -- Leaving the functions to turn off/reset in to let people do that manually if desired IF p_autovacuum_on = false THEN -- Add this parameter back to definition when this is working -- Turn off autovac for parent, source table if set, and all child tables v_is_autovac_off := @extschema@.autovacuum_off(v_parent_schema, v_parent_tablename, v_source_schema, v_source_tablename); COMMIT; END IF; */ v_sql := format('SELECT %I.partition_data_%s (%L, p_lock_wait := %L, p_order := %L, p_analyze := false' , '@extschema@', v_control_type, p_parent_table, p_lock_wait, p_order); IF p_interval IS NOT NULL THEN v_sql := v_sql || format(', p_batch_interval := %L', p_interval); END IF; IF p_source_table IS NOT NULL THEN v_sql := v_sql || format(', p_source_table := %L', p_source_table); END IF; v_sql := v_sql || ')'; RAISE DEBUG 'partition_data sql: %', v_sql; LOOP EXECUTE v_sql INTO v_rows_moved; -- If lock wait timeout, do not increment the counter IF v_rows_moved != -1 THEN v_batch_count := v_batch_count + 1; v_total := v_total + v_rows_moved; v_lockwait_count := 0; ELSE v_lockwait_count := v_lockwait_count + 1; IF v_lockwait_count > p_lock_wait_tries THEN RAISE EXCEPTION 'Quitting due to inability to get lock on next batch of rows to be moved'; END IF; END IF; IF p_quiet = false THEN IF v_rows_moved > 0 THEN RAISE NOTICE 'Batch: %, Rows moved: %', v_batch_count, v_rows_moved; ELSIF v_rows_moved = -1 THEN RAISE NOTICE 'Unable to obtain row locks for data to be moved. Trying again...'; END IF; END IF; -- If no rows left or given batch argument limit is reached IF v_rows_moved = 0 OR (p_batch > 0 AND v_batch_count >= p_batch) THEN EXIT; END IF; COMMIT; PERFORM pg_sleep(p_wait); RAISE DEBUG 'v_rows_moved: %, v_batch_count: %, v_total: %, v_lockwait_count: %, p_wait: %', p_wait, v_rows_moved, v_batch_count, v_total, v_lockwait_count; END LOOP; /* IF v_is_autovac_off = true THEN -- Reset autovac back to default if it was turned off by this procedure PERFORM @extschema@.autovacuum_reset(v_parent_schema, v_parent_tablename, v_source_schema, v_source_tablename); COMMIT; END IF; */ IF p_quiet = false THEN RAISE NOTICE 'Total rows moved: %', v_total; END IF; RAISE NOTICE 'Ensure to VACUUM ANALYZE the parent (and source table if used) after partitioning data'; /* Leaving here until I can figure out what's wrong with procedures and exception handling EXCEPTION WHEN QUERY_CANCELED THEN ROLLBACK; -- Reset autovac back to default if it was turned off by this procedure IF v_is_autovac_off = true THEN PERFORM @extschema@.autovacuum_reset(v_parent_schema, v_parent_tablename, v_source_schema, v_source_tablename); END IF; RAISE EXCEPTION '%', SQLERRM; WHEN OTHERS THEN ROLLBACK; -- Reset autovac back to default if it was turned off by this procedure IF v_is_autovac_off = true THEN PERFORM @extschema@.autovacuum_reset(v_parent_schema, v_parent_tablename, v_source_schema, v_source_tablename); END IF; RAISE EXCEPTION '%', SQLERRM; */ END; $$; $partition_data$; EXECUTE v_partition_data_sql; -- ######################## END POSTGRESQL 11 ONLY SECTION ############################## END IF; END $pg11only$; -- ######################## END POSTGRESQL 11 ONLY SECTION ############################## CREATE OR REPLACE FUNCTION @extschema@.drop_partition_id(p_parent_table text, p_retention bigint DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_control text; v_control_type text; v_count int; v_drop_count int := 0; v_index record; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_max bigint; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_partition_id bigint; v_partition_type text; v_retention bigint; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_row record; v_row_max_id record; v_step_id bigint; BEGIN /* * Function to drop child tables from an id-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman drop_partition_id')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_id already running.'; RETURN 0; END IF; IF p_retention IS NULL THEN SELECT partition_interval::bigint , partition_type , control , retention::bigint , retention_keep_table , retention_keep_index , retention_schema , jobmon INTO v_partition_interval , v_partition_type , v_control , v_retention , v_retention_keep_table , v_retention_keep_index , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND retention IS NOT NULL; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE -- Allow override of configuration options SELECT partition_interval::bigint , partition_type , control , retention_keep_table , retention_keep_index , retention_schema , jobmon INTO v_partition_interval , v_partition_type , v_control , v_retention_keep_table , v_retention_keep_index , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; v_retention := p_retention; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'id' THEN RAISE EXCEPTION 'Data type of control column in given partition set is not an integer type'; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%I) FROM %I.%I', v_control, v_row_max_id.partition_schemaname, v_row_max_id.partition_tablename) INTO v_max; IF v_max IS NOT NULL THEN EXIT; END IF; END LOOP; -- Loop through child tables of the given parent FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'ASC') LOOP SELECT child_start_id INTO v_partition_id FROM @extschema@.show_partition_info(v_row.partition_schemaname||'.'||v_row.partition_tablename , v_partition_interval::text , p_parent_table); -- Add one interval since partition names contain the start of the constraint period IF v_retention <= (v_max - (v_partition_id + v_partition_interval)) THEN -- Do not allow final partition to be dropped SELECT count(*) INTO v_count FROM @extschema@.show_partitions(p_parent_table); IF v_count = 1 THEN RAISE WARNING 'Attempt to drop final partition in partition set % as part of retention policy. Advise reviewing retention policy and/or data entry into the partition set.', p_parent_table; CONTINUE; END IF; -- Only create a jobmon entry if there's actual retention work done IF v_jobmon_schema IS NOT NULL AND v_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN DROP ID PARTITION: %s', p_parent_table)); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Detach/Uninherit table %s.%s from %s', v_row.partition_schemaname, v_row.partition_tablename, p_parent_table)); END IF; IF v_partition_type = 'native' THEN EXECUTE format('ALTER TABLE %I.%I DETACH PARTITION %I.%I' , v_parent_schema , v_parent_tablename , v_row.partition_schemaname , v_row.partition_tablename); ELSE EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_row.partition_schemaname , v_row.partition_tablename , v_parent_schema , v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop table %s.%s', v_row.partition_schemaname, v_row.partition_tablename)); END IF; EXECUTE format('DROP TABLE %I.%I CASCADE', v_row.partition_schemaname, v_row.partition_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN IF v_partition_type = 'partman' OR ( v_partition_type = 'native' AND current_setting('server_version_num')::int < 110000) THEN -- Cannot drop child indexes on native partition sets in PG11+ FOR v_index IN WITH child_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE c1.relname = v_row.partition_tablename::name AND n1.nspname = v_row.partition_schema::name ) SELECT c.relname as name , con.conname FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid LEFT JOIN pg_catalog.pg_constraint con ON i.indexrelid = con.conindid JOIN child_info ON i.indrelid = child_info.oid LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop index %s from %s.%s' , v_index.name , v_row.partition_schemaname , v_row.partition_tablename)); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I DROP CONSTRAINT %I', v_row.partition_schemaname, v_row.partition_tablename, v_index.conname); ELSE EXECUTE format('DROP INDEX %I.%I', v_row.partition_schemaname, v_index.name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; -- end native/11 check END IF; -- end v_retention_keep_index IF ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Moving table %s.%s to schema %s' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema)); END IF; EXECUTE format('ALTER TABLE %I.%I SET SCHEMA %I' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if -- If child table is a subpartition, remove it from part_config & part_config_sub (should cascade due to FK) DELETE FROM @extschema@.part_config WHERE parent_table = v_row.partition_schemaname ||'.'||v_row.partition_tablename; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', format('%s partitions dropped.', v_drop_count)); 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_drop_count; 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 DROP ID PARTITION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE OR REPLACE FUNCTION @extschema@.drop_partition_time(p_parent_table text, p_retention interval DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_control text; v_control_type text; v_count int; v_datetime_string text; v_drop_count int := 0; v_epoch text; v_index record; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_partition_timestamp timestamptz; v_partition_type text; v_retention interval; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_row record; v_step_id bigint; BEGIN /* * Function to drop child tables from a time-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman drop_partition_time')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_time already running.'; RETURN 0; END IF; -- Allow override of configuration options IF p_retention IS NULL THEN SELECT partition_type , control , partition_interval::interval , epoch , retention::interval , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_partition_type , v_control , v_partition_interval , v_epoch , v_retention , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND retention IS NOT NULL; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE SELECT partition_type , partition_interval::interval , epoch , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_partition_type , v_partition_interval , v_epoch , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; v_retention := p_retention; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'time' THEN IF (v_control_type = 'id' AND v_epoch = 'none') OR v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run on partition set without time based control column or epoch flag set with an id column. Found control: %, epoch: %', v_control_type, v_epoch; END IF; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- Loop through child tables of the given parent FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'DESC') LOOP -- pull out datetime portion of partition's tablename to make the next one SELECT child_start_time INTO v_partition_timestamp FROM @extschema@.show_partition_info(v_row.partition_schemaname||'.'||v_row.partition_tablename , v_partition_interval::text , p_parent_table); -- Add one interval since partition names contain the start of the constraint period IF v_retention < (CURRENT_TIMESTAMP - (v_partition_timestamp + v_partition_interval)) THEN -- Do not allow final partition to be dropped SELECT count(*) INTO v_count FROM @extschema@.show_partitions(p_parent_table); IF v_count = 1 THEN RAISE WARNING 'Attempt to drop final partition in partition set % as part of retention policy. If you see this message multiple times for the same table, advise reviewing retention policy and/or data entry into the partition set. Also consider setting "infinite_time_partitions = true" if there are large gaps in data insertion.).', p_parent_table; CONTINUE; END IF; -- Only create a jobmon entry if there's actual retention work done IF v_jobmon_schema IS NOT NULL AND v_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN DROP TIME PARTITION: %s', p_parent_table)); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Detach/Uninherit table %s.%s from %s' , v_row.partition_schemaname , v_row.partition_tablename , p_parent_table)); END IF; IF v_partition_type = 'native' THEN EXECUTE format('ALTER TABLE %I.%I DETACH PARTITION %I.%I' , v_parent_schema , v_parent_tablename , v_row.partition_schemaname , v_row.partition_tablename); ELSE EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_row.partition_schemaname , v_row.partition_tablename , v_parent_schema , v_parent_tablename); END IF; IF v_partition_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_row.partition_schemaname||'.'||v_row.partition_tablename; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop table %s.%s', v_row.partition_schemaname, v_row.partition_tablename)); END IF; EXECUTE format('DROP TABLE %I.%I CASCADE', v_row.partition_schemaname, v_row.partition_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN IF v_partition_type = 'partman' OR ( v_partition_type = 'native' AND current_setting('server_version_num')::int < 110000) THEN -- Cannot drop child indexes on native partition sets in PG11+ FOR v_index IN WITH child_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE c1.relname = v_row.partition_tablename::name AND n1.nspname = v_row.partition_schemaname::name ) SELECT c.relname as name , con.conname FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid LEFT JOIN pg_catalog.pg_constraint con ON i.indexrelid = con.conindid JOIN child_info ON i.indrelid = child_info.oid LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop index %s from %s.%s' , v_index.name , v_row.partition_schemaname , v_row.partition_tablename)); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I DROP CONSTRAINT %I' , v_row.partition_schemaname , v_row.partition_tablename , v_index.conname); ELSE EXECUTE format('DROP INDEX %I.%I', v_parent_schema, v_index.name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; -- end native/11 check END IF; -- end v_retention_keep_index IF ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Moving table %s.%s to schema %s' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema)); END IF; EXECUTE format('ALTER TABLE %I.%I SET SCHEMA %I', v_row.partition_schemaname, v_row.partition_tablename, v_retention_schema); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if -- If child table is a subpartition, remove it from part_config & part_config_sub (should cascade due to FK) DELETE FROM @extschema@.part_config WHERE parent_table = v_row.partition_schemaname||'.'||v_row.partition_tablename; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', format('%s partitions dropped.', v_drop_count)); 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_drop_count; 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 DROP TIME PARTITION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE OR REPLACE FUNCTION @extschema@.create_partition_id(p_parent_table text, p_partition_ids bigint[], p_analyze boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_control_type text; v_exists text; v_grantees text[]; v_hasoids boolean; v_id bigint; v_inherit_fk boolean; v_inherit_privileges boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_grant record; v_parent_schema text; v_parent_tablename text; v_parent_tablespace text; v_partition_interval bigint; v_partition_created boolean := false; v_partition_name text; v_partition_type text; v_publications text[]; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_sub_control text; v_sub_partition_type text; v_sub_id_max bigint; v_sub_id_min bigint; v_template_table text; v_unlogged char; BEGIN /* * Function to create id partitions */ SELECT control , partition_type , partition_interval , inherit_fk , jobmon , template_table , publications , inherit_privileges INTO v_control , v_partition_type , v_partition_interval , v_inherit_fk , v_jobmon , v_template_table , v_publications , v_inherit_privileges FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT n.nspname, c.relname, t.spcname INTO v_parent_schema, v_parent_tablename, v_parent_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 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; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'id' THEN RAISE EXCEPTION 'ERROR: Given parent table is not set up for id/serial partitioning'; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'id'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; FOREACH v_id IN ARRAY p_partition_ids LOOP -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_id_min IS NOT NULL THEN IF v_id < v_sub_id_min OR v_id > v_sub_id_max THEN CONTINUE; END IF; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_id::text, TRUE); -- If child table already exists, skip creation -- Have to check pg_class because if subpartitioned, table will not be in pg_tables SELECT c.relname INTO v_exists 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_partition_name::name; IF v_exists IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_id||' to '||(v_id + v_partition_interval)-1); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c 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; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; -- Close parentheses on LIKE are below due to differing requirements of native subpartitioning -- Same INCLUDING list is used in create_parent() v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING STORAGE INCLUDING COMMENTS ' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); SELECT sub_partition_type, sub_control INTO v_sub_partition_type, v_sub_control FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; IF v_sub_partition_type = 'native' THEN -- NOTE: Need to handle this differently when index inheritance is supported natively -- Cannot include indexes since they cannot exist on native parents. v_sql := v_sql || format(') PARTITION BY RANGE (%I) ', v_sub_control); ELSE v_sql := v_sql || format(' INCLUDING INDEXES) ', v_sub_control); END IF; SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c 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; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; EXECUTE v_sql; IF v_partition_type = 'native' THEN IF v_template_table IS NOT NULL THEN PERFORM @extschema@.inherit_template_properties(p_parent_table, v_parent_schema, v_partition_name); END IF; EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , v_id , v_id + v_partition_interval); ELSE -- Handled in inherit_template_properties for native because CREATE TABLE ignores TABLESPACE flag for native partition parents IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %s AND %I < %s )' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_control , v_id , v_control , v_id + v_partition_interval); EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I', v_parent_schema, v_partition_name, v_parent_schema, v_parent_tablename); -- Indexes cannot be created on the parent, so clustering cannot be used for native yet. PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); -- Foreign keys to other tables not supported on native parent tables IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; END IF; -- NOTE: Privileges not automatically inherited for native. Only do so if config flag is set IF v_partition_type != 'native' OR (v_partition_type = 'native' AND v_inherit_privileges = TRUE) THEN PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_automatic_maintenance , sub_infinite_time_partitions , sub_jobmon , sub_trigger_exception_handling , sub_template_table , sub_inherit_privileges FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Subpartitioning '||v_partition_name); END IF; 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_inherit_fk := %L , p_epoch := %L , p_template_table := %L , p_jobmon := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_automatic_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_template_table , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions , trigger_exception_handling = v_row.sub_trigger_exception_handling , inherit_privileges = v_row.sub_inherit_privileges WHERE parent_table = v_parent_schema||'.'||v_partition_name; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; -- end sub partitioning LOOP -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table, p_job_id := v_job_id, p_debug := p_debug); IF v_publications IS NOT NULL THEN -- NOTE: Publications currently not supported on parent table, but are supported on the table partitions if individually assigned. PERFORM @extschema@.apply_publications(p_parent_table, v_parent_schema, v_partition_name); END IF; v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s', p_parent_table)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE OR REPLACE FUNCTION @extschema@.create_partition_time(p_parent_table text, p_partition_times timestamptz[], p_analyze boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_control_type text; v_datetime_string text; v_epoch text; v_exists smallint; v_grantees text[]; v_hasoids boolean; v_inherit_privileges boolean; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_grant record; v_parent_schema text; v_parent_tablename text; v_part_col text; v_partition_created boolean := false; v_partition_name text; v_partition_suffix text; v_parent_tablespace text; v_partition_expression text; v_partition_interval interval; v_partition_timestamp_end timestamptz; v_partition_timestamp_start timestamptz; v_publications text[]; v_quarter text; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_sub_control text; v_sub_parent text; v_sub_partition_type text; v_sub_timestamp_max timestamptz; v_sub_timestamp_min timestamptz; v_template_table text; v_trunc_value text; v_time timestamptz; v_partition_type text; v_unlogged char; v_year text; BEGIN /* * Function to create a child table in a time-based partition set */ SELECT partition_type , control , partition_interval , epoch , inherit_fk , jobmon , datetime_string , template_table , publications , inherit_privileges INTO v_partition_type , v_control , v_partition_interval , v_epoch , v_inherit_fk , v_jobmon , v_datetime_string , v_template_table , v_publications , v_inherit_privileges FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT n.nspname, c.relname, t.spcname INTO v_parent_schema, v_parent_tablename, v_parent_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 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; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'time' THEN IF (v_control_type = 'id' AND v_epoch = 'none') OR v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run on partition set without time based control column or epoch flag set with an id column. Found control: %, epoch: %', v_control_type, v_epoch; END IF; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::timestamptz, sub_max::timestamptz INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'time'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) END; IF p_debug THEN RAISE NOTICE 'create_partition_time: v_partition_expression: %', v_partition_expression; END IF; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_timestamp_start := v_time; BEGIN v_partition_timestamp_end := v_time + v_partition_interval; 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_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_time||' skipped'); CONTINUE; END; -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_timestamp_min IS NOT NULL THEN IF v_time < v_sub_timestamp_min OR v_time > v_sub_timestamp_max THEN CONTINUE; END IF; END IF; -- This suffix generation code is in partition_data_time() as well v_partition_suffix := to_char(v_time, v_datetime_string); v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); -- Check if child exists. SELECT count(*) INTO v_exists 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_partition_name::name; IF v_exists > 0 THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Creating new partition %s.%s with interval from %s to %s' , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end-'1sec'::interval)); END IF; SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c 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; v_sql := 'CREATE'; IF v_unlogged = 'u' THEN v_sql := v_sql || ' UNLOGGED'; END IF; -- Close parentheses on LIKE are below due to differing requirements of native subpartitioning -- Same INCLUDING list is used in create_parent() v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING STORAGE INCLUDING COMMENTS ' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); SELECT sub_partition_type, sub_control INTO v_sub_partition_type, v_sub_control FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; IF v_sub_partition_type = 'native' THEN -- NOTE: Need to handle this differently when index inheritance is supported natively -- Cannot include indexes since they cannot exist on native parents v_sql := v_sql || format(') PARTITION BY RANGE (%I) ', v_sub_control); ELSE v_sql := v_sql || format(' INCLUDING INDEXES) ', v_sub_control); END IF; SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c 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; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; IF p_debug THEN RAISE NOTICE 'create_partition_time v_sql: %', v_sql; END IF; EXECUTE v_sql; IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; IF v_partition_type = 'native' THEN IF v_template_table IS NOT NULL THEN PERFORM @extschema@.inherit_template_properties(p_parent_table, v_parent_schema, v_partition_name); END IF; IF v_epoch = 'none' THEN -- Attach with normal, time-based values for native constraint EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end); ELSE -- Must attach with integer based values for native constraint and epoch IF v_epoch = 'seconds' THEN EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , EXTRACT('epoch' FROM v_partition_timestamp_start) , EXTRACT('epoch' FROM v_partition_timestamp_end)); ELSIF v_epoch = 'milliseconds' THEN EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , EXTRACT('epoch' FROM v_partition_timestamp_start) * 1000 , EXTRACT('epoch' FROM v_partition_timestamp_end) * 1000); END IF; -- Create secondary, time-based constraint since native's constraint is already integer based EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%s >= %L AND %4$s < %6$L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_partition_expression , v_partition_timestamp_start , v_partition_timestamp_end); END IF; ELSE -- Non-native always gets time-based constraint EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%s >= %L AND %4$s < %6$L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_partition_expression , v_partition_timestamp_start , v_partition_timestamp_end); IF v_epoch = 'seconds' THEN -- Non-native needs secondary, integer based constraint for epoch EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_int_check' , v_control , EXTRACT('epoch' from v_partition_timestamp_start) , v_control , EXTRACT('epoch' from v_partition_timestamp_end) ); ELSIF v_epoch = 'milliseconds' THEN EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_int_check' , v_control , EXTRACT('epoch' from v_partition_timestamp_start) * 1000 , v_control , EXTRACT('epoch' from v_partition_timestamp_end) * 1000); END IF; EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); -- If custom time, set extra config options. IF v_partition_type = 'time-custom' THEN INSERT INTO @extschema@.custom_time_partitions (parent_table, child_table, partition_range) VALUES ( p_parent_table, v_parent_schema||'.'||v_partition_name, tstzrange(v_partition_timestamp_start, v_partition_timestamp_end, '[)') ); END IF; -- Indexes cannot be created on the parent, so clustering cannot be used for native yet. PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); -- Foreign keys to other tables not supported in native IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; END IF; -- end native check -- NOTE: Privileges not automatically inherited for native. Only do so if config flag is set IF v_partition_type != 'native' OR (v_partition_type = 'native' AND v_inherit_privileges = TRUE) THEN PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_automatic_maintenance , sub_infinite_time_partitions , sub_jobmon , sub_trigger_exception_handling , sub_template_table , sub_inherit_privileges FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Subpartitioning %s.%s', v_parent_schema, v_partition_name)); END IF; 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_inherit_fk := %L , p_epoch := %L , p_template_table := %L , p_jobmon := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_automatic_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_template_table , v_row.sub_jobmon); IF p_debug THEN RAISE NOTICE 'create_partition_time (create_parent loop): %', v_sql; END IF; EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions , trigger_exception_handling = v_row.sub_trigger_exception_handling , inherit_privileges = v_row.sub_inherit_privileges WHERE parent_table = v_parent_schema||'.'||v_partition_name; END LOOP; -- end sub partitioning LOOP -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table, p_job_id := v_job_id, p_debug := p_debug); IF v_publications IS NOT NULL THEN -- NOTE: Publications currently not supported on parent table, but are supported on the table partitions if individually assigned. PERFORM @extschema@.apply_publications(p_parent_table, v_parent_schema, v_partition_name); END IF; v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s. Attempted intervals: %s', p_parent_table, p_partition_times)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; 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_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE OR REPLACE FUNCTION @extschema@.show_partition_info(p_child_table text , p_partition_interval text DEFAULT NULL , p_parent_table text DEFAULT NULL , OUT child_start_time timestamptz , OUT child_end_time timestamptz , OUT child_start_id bigint , OUT child_end_id bigint , OUT suffix text) RETURNS record LANGUAGE plpgsql STABLE AS $$ DECLARE v_child_schema text; v_child_tablename text; v_control text; v_control_type text; v_datetime_string text; v_epoch text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_table text; v_partition_interval text; v_partition_type text; v_quarter text; v_suffix text; v_suffix_position int; v_year text; BEGIN /* * Show the data boundries for a given child table as well as the suffix that will be used. * Passing the parent table argument improves performance by avoiding a catalog lookup. * Passing an interval lets you set one different than the default configured one if desired. */ 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'); SELECT n.nspname, c.relname INTO v_child_schema, v_child_tablename FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = split_part(p_child_table, '.', 1)::name AND c.relname = split_part(p_child_table, '.', 2)::name; IF v_child_tablename IS NULL THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RAISE EXCEPTION 'Child table given does not exist (%)', p_child_table; END IF; IF p_parent_table IS NULL THEN SELECT n.nspname||'.'|| c.relname INTO v_parent_table FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhparent JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhrelid::regclass = p_child_table::regclass; ELSE v_parent_table := p_parent_table; END IF; IF p_partition_interval IS NULL THEN SELECT control, partition_interval, partition_type, datetime_string, epoch INTO v_control, v_partition_interval, v_partition_type, v_datetime_string, v_epoch FROM @extschema@.part_config WHERE parent_table = v_parent_table; ELSE v_partition_interval := p_partition_interval; SELECT control, partition_type, datetime_string, epoch INTO v_control, v_partition_type, v_datetime_string, v_epoch FROM @extschema@.part_config WHERE parent_table = v_parent_table; END IF; IF v_control IS NULL THEN RAISE EXCEPTION 'Parent table of given child not managed by pg_partman: %', v_parent_table; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_child_schema, v_child_tablename, v_control); v_suffix_position := (length(v_child_tablename) - position('p_' in reverse(v_child_tablename))) + 2; v_suffix := substring(v_child_tablename from v_suffix_position); IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN IF v_partition_interval::interval <> '3 months' OR (v_partition_interval::interval = '3 months' AND v_partition_type = 'time-custom') THEN child_start_time := to_timestamp(v_suffix, v_datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(v_suffix, 'q', 1); v_quarter := split_part(v_suffix, 'q', 2); CASE WHEN v_quarter = '1' THEN child_start_time := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN child_start_time := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN child_start_time := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN child_start_time := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); ELSE -- handle case when partition name did not use "q" convetion child_start_time := to_timestamp(v_suffix, v_datetime_string); END CASE; END IF; child_end_time := (child_start_time + v_partition_interval::interval) - '1 second'::interval; ELSIF v_control_type = 'id' THEN child_start_id := v_suffix::bigint; child_end_id := (child_start_id + v_partition_interval::bigint) - 1; ELSE RAISE EXCEPTION 'Invalid partition type encountered in show_partition_info()'; END IF; suffix = v_suffix; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN; END $$; CREATE FUNCTION @extschema@.check_default(p_exact_count boolean DEFAULT true) RETURNS SETOF @extschema@.check_default_table LANGUAGE plpgsql STABLE SET search_path = @extschema@,pg_temp AS $$ DECLARE v_count bigint = 0; v_default_schemaname text; v_default_tablename text; v_parent_schemaname text; v_parent_tablename text; v_row record; v_sql text; v_trouble @extschema@.check_default_table%rowtype; BEGIN /* * Function to monitor for data getting inserted into parent/default tables */ FOR v_row IN SELECT parent_table, partition_type FROM @extschema@.part_config LOOP SELECT schemaname, tablename INTO v_parent_schemaname, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(v_row.parent_table, '.', 1)::name AND tablename = split_part(v_row.parent_table, '.', 2)::name; IF v_row.partition_type = 'partman' THEN -- trigger based checks parent table IF p_exact_count THEN v_sql := format('SELECT count(1) AS n FROM ONLY %I.%I', v_parent_schemaname, v_parent_tablename); ELSE v_sql := format('SELECT count(1) AS n FROM (SELECT 1 FROM ONLY %I.%I LIMIT 1) x', v_parent_schemaname, v_parent_tablename); END IF; EXECUTE v_sql INTO v_count; IF v_count > 0 THEN v_trouble.default_table := v_parent_schemaname ||'.'|| v_parent_tablename; v_trouble.count := v_count; RETURN NEXT v_trouble; END IF; ELSIF v_row.partition_type = 'native' AND current_setting('server_version_num')::int >= 110000 THEN -- native PG11+ checks default if it exists v_sql := format('SELECT n.nspname::text, c.relname::text FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = ''%I.%I''::regclass AND pg_get_expr(relpartbound, c.oid) = ''DEFAULT''' , v_parent_schemaname , v_parent_tablename); EXECUTE v_sql INTO v_default_schemaname, v_default_tablename; IF v_default_schemaname IS NOT NULL AND v_default_tablename IS NOT NULL THEN IF p_exact_count THEN v_sql := format('SELECT count(1) AS n FROM ONLY %I.%I', v_default_schemaname, v_default_tablename); ELSE v_sql := format('SELECT count(1) AS n FROM (SELECT 1 FROM ONLY %I.%I LIMIT 1) x', v_default_schemaname, v_default_tablename); END IF; EXECUTE v_sql INTO v_count; IF v_count > 0 THEN v_trouble.default_table := v_default_schemaname ||'.'|| v_default_tablename; v_trouble.count := v_count; RETURN NEXT v_trouble; END IF; END IF; END IF; v_count := 0; END LOOP; RETURN; 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 , p_debug boolean DEFAULT false) 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_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 (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 ... PARITIONED 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 ) 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 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) 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); -- 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 INTO v_higher_parent_schema, v_higher_parent_table, v_higher_parent_control 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' 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'; IF v_unlogged = 'u' THEN v_sql := v_sql ||' UNLOGGED'; END IF; -- Same INCLUDING list is used in create_partition_*() 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); 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 v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_default_partition, v_parent_tablespace); END IF; 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 $$; CREATE OR REPLACE FUNCTION @extschema@.partition_data_id(p_parent_table text , p_batch_count int DEFAULT 1 , p_batch_interval bigint DEFAULT NULL , p_lock_wait numeric DEFAULT 0 , p_order text DEFAULT 'ASC' , p_analyze boolean DEFAULT true , p_source_table text DEFAULT NULL) RETURNS bigint LANGUAGE plpgsql AS $$ DECLARE v_control text; v_control_type text; v_current_partition_name text; v_default_exists boolean; v_default_schemaname text; v_default_tablename text; v_epoch text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_id bigint; v_min_partition_id bigint; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_tablename text; v_partition_interval bigint; v_partition_id bigint[]; v_partition_type text; v_rowcount bigint; v_source_schemaname text; v_source_tablename text; v_sql text; v_start_control bigint; v_total_rows bigint := 0; BEGIN /* * Populate the child table(s) of an id-based partition set with old data from the original parent */ SELECT partition_interval::bigint , partition_type , control , epoch INTO v_partition_interval , v_partition_type , v_control , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: No entry in part_config found for given table: %', p_parent_table; END IF; SELECT schemaname, tablename INTO v_source_schemaname, v_source_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- Preserve real parent tablename for use below v_parent_tablename := v_source_tablename; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_source_schemaname, v_source_tablename, v_control); IF v_control_type <> 'id' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN RAISE EXCEPTION 'Control column for given partition set is not id/serial based or epoch flag is set for time-based partitioning.'; END IF; IF p_source_table IS NOT NULL THEN -- Set source table to user given source table instead of parent table v_source_schemaname := NULL; v_source_tablename := NULL; SELECT schemaname, tablename INTO v_source_schemaname, v_source_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_source_table, '.', 1)::name AND tablename = split_part(p_source_table, '.', 2)::name; IF v_source_tablename IS NULL THEN RAISE EXCEPTION 'Given source table does not exist in system catalogs: %', p_source_table; END IF; ELSIF v_partition_type = 'native' AND current_setting('server_version_num')::int >= 110000 THEN IF p_batch_interval IS NOT NULL AND p_batch_interval != v_partition_interval THEN -- This is true because all data for a given child table must be moved out of the default partition before the child table can be created. -- So cannot create the child table when only some of the data has been moved out of the default partition. RAISE EXCEPTION 'Custom intervals are not allowed when moving data out of the DEFAULT partition in a native set. Please leave p_interval/p_batch_interval parameters unset or NULL to allow use of partition set''s default partitioning interval.'; END IF; -- Set source table to default table if PG11+, p_source_table is not set, and it exists -- Otherwise just return with a DEBUG that no data source exists v_sql := format('SELECT n.nspname::text, c.relname::text FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = ''%I.%I''::regclass AND pg_get_expr(relpartbound, c.oid) = ''DEFAULT''' , v_source_schemaname , v_source_tablename); EXECUTE v_sql INTO v_default_schemaname, v_default_tablename; IF v_default_tablename IS NOT NULL THEN v_source_schemaname := v_default_schemaname; v_source_tablename := v_default_tablename; v_default_exists := true; EXECUTE format ('CREATE TEMP TABLE IF NOT EXISTS partman_temp_data_storage (LIKE %I.%I INCLUDING INDEXES)', v_source_schemaname, v_source_tablename); ELSE RAISE DEBUG 'No default table found when partition_data_id() was called'; RETURN v_total_rows; END IF; END IF; 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'); IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; FOR i IN 1..p_batch_count LOOP IF p_order = 'ASC' THEN EXECUTE format('SELECT min(%I) FROM ONLY %I.%I', v_control, v_source_schemaname, v_source_tablename) INTO v_start_control; IF v_start_control IS NULL THEN EXIT; END IF; v_min_partition_id = v_start_control - (v_start_control % v_partition_interval); v_partition_id := ARRAY[v_min_partition_id]; -- Check if custom batch interval overflows current partition maximum IF (v_start_control + p_batch_interval) >= (v_min_partition_id + v_partition_interval) THEN v_max_partition_id := v_min_partition_id + v_partition_interval; ELSE v_max_partition_id := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_control, v_source_schemaname, v_source_tablename) INTO v_start_control; IF v_start_control IS NULL THEN EXIT; END IF; v_min_partition_id = v_start_control - (v_start_control % v_partition_interval); -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_id := v_min_partition_id + v_partition_interval; v_partition_id := ARRAY[v_min_partition_id]; -- Make sure minimum doesn't underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_id THEN v_min_partition_id = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN v_sql := format('SELECT * FROM ONLY %I.%I WHERE %I >= %s AND %I < %s FOR UPDATE NOWAIT' , v_source_schemaname , v_source_tablename , v_control , v_min_partition_id , v_control , v_max_partition_id); EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; v_current_partition_name := @extschema@.check_name_length(COALESCE(v_parent_tablename), v_min_partition_id::text, TRUE); IF v_default_exists THEN -- Child tables cannot be created in native partitioning if data that belongs to it exists in the default -- Have to move data out to temporary location, create child table, then move it back -- Temp table created above to avoid excessive temp creation in loop EXECUTE format('WITH partition_data AS ( DELETE FROM %1$I.%2$I WHERE %3$I >= %4$s AND %3$I < %5$s RETURNING *) INSERT INTO partman_temp_data_storage SELECT * FROM partition_data' , v_source_schemaname , v_source_tablename , v_control , v_min_partition_id , v_max_partition_id); PERFORM @extschema@.create_partition_id(p_parent_table, v_partition_id, p_analyze); EXECUTE format('WITH partition_data AS ( DELETE FROM partman_temp_data_storage RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_source_schemaname , v_current_partition_name); ELSE PERFORM @extschema@.create_partition_id(p_parent_table, v_partition_id, p_analyze); EXECUTE format('WITH partition_data AS ( DELETE FROM ONLY %1$I.%2$I WHERE %3$I >= %4$s AND %3$I < %5$s RETURNING *) INSERT INTO %1$I.%6$I SELECT * FROM partition_data' , v_source_schemaname , v_source_tablename , v_control , v_min_partition_id , v_max_partition_id , v_current_partition_name); END IF; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; IF v_partition_type = 'partman' THEN PERFORM @extschema@.create_function_id(p_parent_table); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_total_rows; END $$; CREATE OR REPLACE FUNCTION @extschema@.partition_data_time( p_parent_table text , p_batch_count int DEFAULT 1 , p_batch_interval interval DEFAULT NULL , p_lock_wait numeric DEFAULT 0 , p_order text DEFAULT 'ASC' , p_analyze boolean DEFAULT true , p_source_table text DEFAULT NULL) RETURNS bigint LANGUAGE plpgsql AS $$ DECLARE v_control text; v_control_type text; v_datetime_string text; v_current_partition_name text; v_default_exists boolean; v_default_schemaname text; v_default_tablename text; v_epoch text; v_last_partition text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_timestamp timestamptz; v_min_partition_timestamp timestamptz; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_tablename text; v_parent_tablename_real text; v_partition_expression text; v_partition_interval interval; v_partition_suffix text; v_partition_timestamp timestamptz[]; v_partition_type text; v_source_schemaname text; v_source_tablename text; v_rowcount bigint; v_sql text; v_start_control timestamptz; v_total_rows bigint := 0; BEGIN /* * Populate the child table(s) of a time-based partition set with old data from the original parent */ SELECT partition_type , partition_interval::interval , control , datetime_string , epoch INTO v_partition_type , v_partition_interval , v_control , v_datetime_string , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: No entry in part_config found for given table: %', p_parent_table; END IF; SELECT schemaname, tablename INTO v_source_schemaname, v_source_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- Preserve real parent tablename for use below v_parent_tablename := v_source_tablename; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_source_schemaname, v_source_tablename, v_control); IF v_control_type <> 'time' THEN IF (v_control_type = 'id' AND v_epoch = 'none') OR v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run on partition set without time based control column or epoch flag set with an id column. Found control: %, epoch: %', v_control_type, v_epoch; END IF; END IF; -- Replace the parent variables with the source variables if using source table for child table data IF p_source_table IS NOT NULL THEN -- Set source table to user given source table instead of parent table v_source_schemaname := NULL; v_source_tablename := NULL; SELECT schemaname, tablename INTO v_source_schemaname, v_source_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_source_table, '.', 1)::name AND tablename = split_part(p_source_table, '.', 2)::name; IF v_source_tablename IS NULL THEN RAISE EXCEPTION 'Given source table does not exist in system catalogs: %', p_source_table; END IF; ELSIF v_partition_type = 'native' AND current_setting('server_version_num')::int >= 110000 THEN IF p_batch_interval IS NOT NULL AND p_batch_interval != v_partition_interval THEN -- This is true because all data for a given child table must be moved out of the default partition before the child table can be created. -- So cannot create the child table when only some of the data has been moved out of the default partition. RAISE EXCEPTION 'Custom intervals are not allowed when moving data out of the DEFAULT partition in a native set. Please leave p_interval/p_batch_interval parameters unset or NULL to allow use of partition set''s default partitioning interval.'; END IF; -- Set source table to default table if PG11+, p_source_table is not set, and it exists -- Otherwise just return with a DEBUG that no data source exists v_sql := format('SELECT n.nspname::text, c.relname::text FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = ''%I.%I''::regclass AND pg_get_expr(relpartbound, c.oid) = ''DEFAULT''' , v_source_schemaname , v_source_tablename); EXECUTE v_sql INTO v_default_schemaname, v_default_tablename; IF v_default_tablename IS NOT NULL THEN v_source_schemaname := v_default_schemaname; v_source_tablename := v_default_tablename; v_default_exists := true; EXECUTE format ('CREATE TEMP TABLE IF NOT EXISTS partman_temp_data_storage (LIKE %I.%I INCLUDING INDEXES)', v_source_schemaname, v_source_tablename); ELSE RAISE DEBUG 'No default table found when partition_data_id() was called'; RETURN v_total_rows; END IF; END IF; 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'); IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) END; FOR i IN 1..p_batch_count LOOP IF p_order = 'ASC' THEN EXECUTE format('SELECT min(%s) FROM ONLY %I.%I', v_partition_expression, v_source_schemaname, v_source_tablename) INTO v_start_control; ELSIF p_order = 'DESC' THEN EXECUTE format('SELECT max(%s) FROM ONLY %I.%I', v_partition_expression, v_source_schemaname, v_source_tablename) INTO v_start_control; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; IF v_start_control IS NULL THEN EXIT; END IF; IF v_partition_type = 'partman' THEN CASE WHEN v_partition_interval = '15 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '15min'::interval * floor(date_part('minute', v_start_control) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '30min'::interval * floor(date_part('minute', v_start_control) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control); WHEN v_partition_interval = '1 day' THEN v_min_partition_timestamp := date_trunc('day', v_start_control); WHEN v_partition_interval = '1 week' THEN v_min_partition_timestamp := date_trunc('week', v_start_control); WHEN v_partition_interval = '1 month' THEN v_min_partition_timestamp := date_trunc('month', v_start_control); WHEN v_partition_interval = '3 months' THEN v_min_partition_timestamp := date_trunc('quarter', v_start_control); WHEN v_partition_interval = '1 year' THEN v_min_partition_timestamp := date_trunc('year', v_start_control); END CASE; ELSIF v_partition_type IN ('time-custom', 'native') THEN SELECT child_start_time INTO v_min_partition_timestamp FROM @extschema@.show_partition_info(v_source_schemaname||'.'||v_last_partition , v_partition_interval::text , p_parent_table); v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; LOOP IF v_start_control >= v_min_partition_timestamp AND v_start_control < v_max_partition_timestamp THEN EXIT; ELSE BEGIN IF v_start_control > v_max_partition_timestamp THEN -- Keep going forward in time, checking if child partition time interval encompasses the current v_start_control value v_min_partition_timestamp := v_max_partition_timestamp; v_max_partition_timestamp := v_max_partition_timestamp + v_partition_interval; ELSE -- Keep going backwards in time, checking if child partition time interval encompasses the current v_start_control value v_max_partition_timestamp := v_min_partition_timestamp; v_min_partition_timestamp := v_min_partition_timestamp - v_partition_interval; END IF; EXCEPTION WHEN datetime_field_overflow THEN RAISE EXCEPTION 'Attempted partition time interval is outside PostgreSQL''s supported time range. Unable to create partition with interval before timestamp % ', v_min_partition_timestamp; END; END IF; END LOOP; END IF; v_partition_timestamp := ARRAY[v_min_partition_timestamp]; IF p_order = 'ASC' THEN -- Ensure batch interval given as parameter doesn't cause maximum to overflow the current partition maximum IF (v_start_control + p_batch_interval) >= (v_min_partition_timestamp + v_partition_interval) THEN v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; ELSE v_max_partition_timestamp := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; -- Ensure batch interval given as parameter doesn't cause minimum to underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_timestamp THEN v_min_partition_timestamp = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM ONLY %I.%I WHERE %s >= %L AND %3$s < %5$L FOR UPDATE NOWAIT' , v_source_schemaname , v_source_tablename , v_partition_expression , v_min_partition_timestamp , v_max_partition_timestamp); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; -- This suffix generation code is in create_partition_time() as well v_partition_suffix := to_char(v_min_partition_timestamp, v_datetime_string); v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); IF v_default_exists THEN -- Child tables cannot be created in native partitioning if data that belongs to it exists in the default -- Have to move data out to temporary location, create child table, then move it back -- Temp table created above to avoid excessive temp creation in loop EXECUTE format('WITH partition_data AS ( DELETE FROM %1$I.%2$I WHERE %3$I >= %4$L AND %3$I < %5$L RETURNING *) INSERT INTO partman_temp_data_storage SELECT * FROM partition_data' , v_source_schemaname , v_source_tablename , v_control , v_min_partition_timestamp , v_max_partition_timestamp); PERFORM @extschema@.create_partition_time(p_parent_table, v_partition_timestamp, p_analyze); EXECUTE format('WITH partition_data AS ( DELETE FROM partman_temp_data_storage RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_source_schemaname , v_current_partition_name); ELSE PERFORM @extschema@.create_partition_time(p_parent_table, v_partition_timestamp, p_analyze); EXECUTE format('WITH partition_data AS ( DELETE FROM ONLY %I.%I WHERE %s >= %L AND %3$s < %5$L RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_source_schemaname , v_source_tablename , v_partition_expression , v_min_partition_timestamp , v_max_partition_timestamp , v_source_schemaname , v_current_partition_name); END IF; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; IF v_partition_type IN ('partman', 'time-custom') THEN PERFORM @extschema@.create_function_time(p_parent_table); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_total_rows; END $$; pg_partman-4.6.0/updates/pg_partman--4.1.0--4.2.0.sql000066400000000000000000003534011412761706100214310ustar00rootroot00000000000000-- Preliminary compatability with PostgreSQL 12. -- IMPORTANT SUPPORT CHANGE: As of PostgreSQL 12's stable release, version 9.4 of PostgreSQL will no longer officially be supported by pg_partman. It will likely continue to function without any issue, but any bugs tied specifically to that version and lower will no longer be fixed and new features that are not compatible with 9.4 may be introduced. -- IMPORTANT CHANGE: The UNLOGGED status of a partition set in native partitioning has changed to be managed via the template table, no longer the parent table. -- Currently setting a natively partitioned parent table as UNLOGGED/LOGGED is not a property that can be changed with an ALTER TABLE. So if the state is later changed to LOGGED (or vice versa), this property will not change for future child tables. Current child tables were never handled via this manner before and always had to be fixed manually. -- If any of your native partition sets are UNLOGGED, be sure to set this property on the template table before upgrading to this version. Otherwise new child tables may be created as LOGGED. -- pg_partman will maintain this behavior until core PostgreSQL determines a definative way that UNLOGGED changes will be handled. -- See reported bug at https://www.postgresql.org/message-id/flat/15954-b61523bed4b110c4%40postgresql.org -- As of PostgreSQL 12, pg_partman will no longer support inheriting OIDs since the special column behavior has been removed. See the release notes for PG12 for more information on this change. The feature has not been deprecated from pg_partman for older versions of PostgreSQL, but all tests involving the feature have been removed and any bugs involving it will likely not be fixed unless it is a compatability issue with 12+. It is recommended to remove any dependencies on special OID behavior that you previously had in your partition sets at this time. -- Fixed bug that prevented pg_partman from creating child tables in PG12+ due to useage of pg_class.relhasoids column. (Github Issue #256) -- A new configuration option, "constraint_valid", has been added to the part_config(_sub) table to control whether the additonal constraints that pg_partman can manage (see "Constraint Exclusion" section in pg_partman.md) are added in a NOT VALID state. By default the constraints are added as VALID as they were before. Note that if this config is changed to "false", constraint exclusion will not work until the constraints are validated. -- Fixed bug where primary key/unique indexes applied to pre-existing template tables were not being applied to the DEFAULT partition created in PG11+ native partitioning. Thanks to @dtseiler on Github for testing and reporting this. (Github Issue #266) -- Fixed bug that was causing error in the retention management of time-based partitioning sets. If retention would attempt to remove the last child table, maintenance would throw an error about a missing table instead of just a warning about the last table attempting to be dropped. Thanks to @LaVoCG on Github for the examples to reproduce this scenario. (Github Issues #227 & #237) -- Changed default option to false on PG11+ for whether the maintenance background worker will run an ANALYZE whenever a child table is created. If this is desired, please set the option pg_partman_bgw.analyze to true in your postgresql.conf. (Github Issue #262) -- Properly throw an error with create_parent() when the data type of the partition column is numeric. Previously would not report any errors but would also not add the proper data to the part_config table so future maintenance would not work. Currently testing to see if numeric partitioning can be properly supported in pg_partman. (Github Issue #265) ALTER TABLE @extschema@.part_config ADD COLUMN constraint_valid boolean DEFAULT true NOT NULL; ALTER TABLE @extschema@.part_config_sub ADD COLUMN sub_constraint_valid boolean DEFAULT true NOT NULL; 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 , p_debug boolean DEFAULT false) 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_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 ... PARITIONED 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 ) 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 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) 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); -- 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 INTO v_higher_parent_schema, v_higher_parent_table, v_higher_parent_control 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' 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_*() 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); 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; -- Ensure any primary/unique keys on premade template tables are applied PERFORM @extschema@.inherit_template_properties(p_parent_table, v_parent_schema, v_default_partition); IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_default_partition, v_parent_tablespace); END IF; 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 $$; CREATE OR REPLACE FUNCTION @extschema@.create_partition_id(p_parent_table text, p_partition_ids bigint[], p_analyze boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_control_type text; v_exists text; v_grantees text[]; v_hasoids boolean; v_id bigint; v_inherit_fk boolean; v_inherit_privileges boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_grant record; v_parent_schema text; v_parent_tablename text; v_parent_tablespace text; v_partition_interval bigint; v_partition_created boolean := false; v_partition_name text; v_partition_type text; v_publications text[]; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_sub_control text; v_sub_partition_type text; v_sub_id_max bigint; v_sub_id_min bigint; v_template_table text; v_unlogged char; BEGIN /* * Function to create id partitions */ SELECT control , partition_type , partition_interval , inherit_fk , jobmon , template_table , publications , inherit_privileges INTO v_control , v_partition_type , v_partition_interval , v_inherit_fk , v_jobmon , v_template_table , v_publications , v_inherit_privileges FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT n.nspname, c.relname, t.spcname INTO v_parent_schema, v_parent_tablename, v_parent_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 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; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'id' THEN RAISE EXCEPTION 'ERROR: Given parent table is not set up for id/serial partitioning'; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'id'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; FOREACH v_id IN ARRAY p_partition_ids LOOP -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_id_min IS NOT NULL THEN IF v_id < v_sub_id_min OR v_id > v_sub_id_max THEN CONTINUE; END IF; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_id::text, TRUE); -- If child table already exists, skip creation -- Have to check pg_class because if subpartitioned, table will not be in pg_tables SELECT c.relname INTO v_exists 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_partition_name::name; IF v_exists IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_id||' to '||(v_id + v_partition_interval)-1); END IF; v_sql := 'CREATE'; -- As of PG12, the unlogged/logged status of a native parent table cannot be changed via an ALTER TABLE in order to affect its children. -- As of v4.2x, the unlogged state will be managed via the template table SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c 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; IF v_unlogged = 'u' and v_partition_type != 'native' THEN v_sql := v_sql || ' UNLOGGED'; END IF; -- Close parentheses on LIKE are below due to differing requirements of native subpartitioning -- Same INCLUDING list is used in create_parent() v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING STORAGE INCLUDING COMMENTS ' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); SELECT sub_partition_type, sub_control INTO v_sub_partition_type, v_sub_control FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; IF v_sub_partition_type = 'native' THEN -- NOTE: Need to handle this differently when index inheritance is supported natively -- Cannot include indexes since they cannot exist on native parents. v_sql := v_sql || format(') PARTITION BY RANGE (%I) ', v_sub_control); ELSE v_sql := v_sql || format(' INCLUDING INDEXES) ', v_sub_control); END IF; IF current_setting('server_version_num')::int < 120000 THEN -- column removed from pgclass in pg12 SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c 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; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; END IF; IF p_debug THEN RAISE NOTICE 'create_partition_id v_sql: %', v_sql; END IF; EXECUTE v_sql; IF v_partition_type = 'native' THEN IF v_template_table IS NOT NULL THEN PERFORM @extschema@.inherit_template_properties(p_parent_table, v_parent_schema, v_partition_name); END IF; EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , v_id , v_id + v_partition_interval); ELSE -- Handled in inherit_template_properties for native because CREATE TABLE ignores TABLESPACE flag for native partition parents IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %s AND %I < %s )' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_control , v_id , v_control , v_id + v_partition_interval); EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I', v_parent_schema, v_partition_name, v_parent_schema, v_parent_tablename); -- Indexes cannot be created on the parent, so clustering cannot be used for native yet. PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); -- Foreign keys to other tables not supported on native parent tables IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; END IF; -- NOTE: Privileges not automatically inherited for native. Only do so if config flag is set IF v_partition_type != 'native' OR (v_partition_type = 'native' AND v_inherit_privileges = TRUE) THEN PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_automatic_maintenance , sub_infinite_time_partitions , sub_jobmon , sub_trigger_exception_handling , sub_template_table , sub_inherit_privileges , sub_constraint_valid FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Subpartitioning '||v_partition_name); END IF; 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_inherit_fk := %L , p_epoch := %L , p_template_table := %L , p_jobmon := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_automatic_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_template_table , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions , trigger_exception_handling = v_row.sub_trigger_exception_handling , inherit_privileges = v_row.sub_inherit_privileges , constraint_valid = v_row.sub_constraint_valid WHERE parent_table = v_parent_schema||'.'||v_partition_name; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; -- end sub partitioning LOOP -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table, p_job_id := v_job_id, p_debug := p_debug); IF v_publications IS NOT NULL THEN -- NOTE: Publications currently not supported on parent table, but are supported on the table partitions if individually assigned. PERFORM @extschema@.apply_publications(p_parent_table, v_parent_schema, v_partition_name); END IF; v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s', p_parent_table)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE OR REPLACE FUNCTION @extschema@.create_partition_time(p_parent_table text, p_partition_times timestamptz[], p_analyze boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_control_type text; v_datetime_string text; v_epoch text; v_exists smallint; v_grantees text[]; v_hasoids boolean; v_inherit_privileges boolean; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_grant record; v_parent_schema text; v_parent_tablename text; v_part_col text; v_partition_created boolean := false; v_partition_name text; v_partition_suffix text; v_parent_tablespace text; v_partition_expression text; v_partition_interval interval; v_partition_timestamp_end timestamptz; v_partition_timestamp_start timestamptz; v_publications text[]; v_quarter text; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_sub_control text; v_sub_parent text; v_sub_partition_type text; v_sub_timestamp_max timestamptz; v_sub_timestamp_min timestamptz; v_template_table text; v_trunc_value text; v_time timestamptz; v_partition_type text; v_unlogged char; v_year text; BEGIN /* * Function to create a child table in a time-based partition set */ SELECT partition_type , control , partition_interval , epoch , inherit_fk , jobmon , datetime_string , template_table , publications , inherit_privileges INTO v_partition_type , v_control , v_partition_interval , v_epoch , v_inherit_fk , v_jobmon , v_datetime_string , v_template_table , v_publications , v_inherit_privileges FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT n.nspname, c.relname, t.spcname INTO v_parent_schema, v_parent_tablename, v_parent_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 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; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'time' THEN IF (v_control_type = 'id' AND v_epoch = 'none') OR v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run on partition set without time based control column or epoch flag set with an id column. Found control: %, epoch: %', v_control_type, v_epoch; END IF; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::timestamptz, sub_max::timestamptz INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'time'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) END; IF p_debug THEN RAISE NOTICE 'create_partition_time: v_partition_expression: %', v_partition_expression; END IF; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_timestamp_start := v_time; BEGIN v_partition_timestamp_end := v_time + v_partition_interval; 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_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_time||' skipped'); CONTINUE; END; -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_timestamp_min IS NOT NULL THEN IF v_time < v_sub_timestamp_min OR v_time > v_sub_timestamp_max THEN CONTINUE; END IF; END IF; -- This suffix generation code is in partition_data_time() as well v_partition_suffix := to_char(v_time, v_datetime_string); v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); -- Check if child exists. SELECT count(*) INTO v_exists 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_partition_name::name; IF v_exists > 0 THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Creating new partition %s.%s with interval from %s to %s' , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end-'1sec'::interval)); END IF; v_sql := 'CREATE'; -- As of PG12, the unlogged/logged status of a native parent table cannot be changed via an ALTER TABLE in order to affect its children. -- As of v4.2x, the unlogged state will be managed via the template table SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c 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; IF v_unlogged = 'u' and v_partition_type != 'native' THEN v_sql := v_sql || ' UNLOGGED'; END IF; -- Close parentheses on LIKE are below due to differing requirements of native subpartitioning -- Same INCLUDING list is used in create_parent() v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING STORAGE INCLUDING COMMENTS ' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); SELECT sub_partition_type, sub_control INTO v_sub_partition_type, v_sub_control FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; IF v_sub_partition_type = 'native' THEN -- NOTE: Need to handle this differently when index inheritance is supported natively -- Cannot include indexes since they cannot exist on native parents v_sql := v_sql || format(') PARTITION BY RANGE (%I) ', v_sub_control); ELSE v_sql := v_sql || format(' INCLUDING INDEXES) ', v_sub_control); END IF; IF current_setting('server_version_num')::int < 120000 THEN -- column removed from pgclass in pg12 SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c 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; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; END IF; IF p_debug THEN RAISE NOTICE 'create_partition_time v_sql: %', v_sql; END IF; EXECUTE v_sql; IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; IF v_partition_type = 'native' THEN IF v_template_table IS NOT NULL THEN PERFORM @extschema@.inherit_template_properties(p_parent_table, v_parent_schema, v_partition_name); END IF; IF v_epoch = 'none' THEN -- Attach with normal, time-based values for native constraint EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end); ELSE -- Must attach with integer based values for native constraint and epoch IF v_epoch = 'seconds' THEN EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , EXTRACT('epoch' FROM v_partition_timestamp_start) , EXTRACT('epoch' FROM v_partition_timestamp_end)); ELSIF v_epoch = 'milliseconds' THEN EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , EXTRACT('epoch' FROM v_partition_timestamp_start) * 1000 , EXTRACT('epoch' FROM v_partition_timestamp_end) * 1000); END IF; -- Create secondary, time-based constraint since native's constraint is already integer based EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%s >= %L AND %4$s < %6$L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_partition_expression , v_partition_timestamp_start , v_partition_timestamp_end); END IF; ELSE -- Non-native always gets time-based constraint EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%s >= %L AND %4$s < %6$L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_partition_expression , v_partition_timestamp_start , v_partition_timestamp_end); IF v_epoch = 'seconds' THEN -- Non-native needs secondary, integer based constraint for epoch EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_int_check' , v_control , EXTRACT('epoch' from v_partition_timestamp_start) , v_control , EXTRACT('epoch' from v_partition_timestamp_end) ); ELSIF v_epoch = 'milliseconds' THEN EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_int_check' , v_control , EXTRACT('epoch' from v_partition_timestamp_start) * 1000 , v_control , EXTRACT('epoch' from v_partition_timestamp_end) * 1000); END IF; EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); -- If custom time, set extra config options. IF v_partition_type = 'time-custom' THEN INSERT INTO @extschema@.custom_time_partitions (parent_table, child_table, partition_range) VALUES ( p_parent_table, v_parent_schema||'.'||v_partition_name, tstzrange(v_partition_timestamp_start, v_partition_timestamp_end, '[)') ); END IF; -- Indexes cannot be created on the parent, so clustering cannot be used for native yet. PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); -- Foreign keys to other tables not supported in native IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; END IF; -- end native check -- NOTE: Privileges not automatically inherited for native. Only do so if config flag is set IF v_partition_type != 'native' OR (v_partition_type = 'native' AND v_inherit_privileges = TRUE) THEN PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_automatic_maintenance , sub_infinite_time_partitions , sub_jobmon , sub_trigger_exception_handling , sub_template_table , sub_inherit_privileges , sub_constraint_valid FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Subpartitioning %s.%s', v_parent_schema, v_partition_name)); END IF; 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_inherit_fk := %L , p_epoch := %L , p_template_table := %L , p_jobmon := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_automatic_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_template_table , v_row.sub_jobmon); IF p_debug THEN RAISE NOTICE 'create_partition_time (create_parent loop): %', v_sql; END IF; EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions , trigger_exception_handling = v_row.sub_trigger_exception_handling , inherit_privileges = v_row.sub_inherit_privileges , constraint_valid = v_row.sub_constraint_valid WHERE parent_table = v_parent_schema||'.'||v_partition_name; END LOOP; -- end sub partitioning LOOP -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table, p_job_id := v_job_id, p_debug := p_debug); IF v_publications IS NOT NULL THEN -- NOTE: Publications currently not supported on parent table, but are supported on the table partitions if individually assigned. PERFORM @extschema@.apply_publications(p_parent_table, v_parent_schema, v_partition_name); END IF; v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s. Attempted intervals: %s', p_parent_table, p_partition_times)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; 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_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE OR REPLACE FUNCTION @extschema@.drop_partition_time(p_parent_table text, p_retention interval DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_control text; v_control_type text; v_count int; v_datetime_string text; v_drop_count int := 0; v_epoch text; v_index record; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_partition_timestamp timestamptz; v_partition_type text; v_retention interval; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_row record; v_step_id bigint; BEGIN /* * Function to drop child tables from a time-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman drop_partition_time')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_time already running.'; RETURN 0; END IF; -- Allow override of configuration options IF p_retention IS NULL THEN SELECT partition_type , control , partition_interval::interval , epoch , retention::interval , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_partition_type , v_control , v_partition_interval , v_epoch , v_retention , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND retention IS NOT NULL; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE SELECT partition_type , partition_interval::interval , epoch , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_partition_type , v_partition_interval , v_epoch , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; v_retention := p_retention; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'time' THEN IF (v_control_type = 'id' AND v_epoch = 'none') OR v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run on partition set without time based control column or epoch flag set with an id column. Found control: %, epoch: %', v_control_type, v_epoch; END IF; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- Loop through child tables of the given parent -- Must go in ascending order to avoid dropping what may be the "last" partition in the set after dropping tables that match retention period FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'ASC') LOOP -- pull out datetime portion of partition's tablename to make the next one SELECT child_start_time INTO v_partition_timestamp FROM @extschema@.show_partition_info(v_row.partition_schemaname||'.'||v_row.partition_tablename , v_partition_interval::text , p_parent_table); -- Add one interval since partition names contain the start of the constraint period IF v_retention < (CURRENT_TIMESTAMP - (v_partition_timestamp + v_partition_interval)) THEN -- Do not allow final partition to be dropped SELECT count(*) INTO v_count FROM @extschema@.show_partitions(p_parent_table); IF v_count = 1 THEN RAISE WARNING 'Attempt to drop final partition in partition set % as part of retention policy. If you see this message multiple times for the same table, advise reviewing retention policy and/or data entry into the partition set. Also consider setting "infinite_time_partitions = true" if there are large gaps in data insertion.).', p_parent_table; CONTINUE; END IF; -- Only create a jobmon entry if there's actual retention work done IF v_jobmon_schema IS NOT NULL AND v_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN DROP TIME PARTITION: %s', p_parent_table)); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Detach/Uninherit table %s.%s from %s' , v_row.partition_schemaname , v_row.partition_tablename , p_parent_table)); END IF; IF v_partition_type = 'native' THEN EXECUTE format('ALTER TABLE %I.%I DETACH PARTITION %I.%I' , v_parent_schema , v_parent_tablename , v_row.partition_schemaname , v_row.partition_tablename); ELSE EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_row.partition_schemaname , v_row.partition_tablename , v_parent_schema , v_parent_tablename); END IF; IF v_partition_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_row.partition_schemaname||'.'||v_row.partition_tablename; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop table %s.%s', v_row.partition_schemaname, v_row.partition_tablename)); END IF; EXECUTE format('DROP TABLE %I.%I CASCADE', v_row.partition_schemaname, v_row.partition_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN IF v_partition_type = 'partman' OR ( v_partition_type = 'native' AND current_setting('server_version_num')::int < 110000) THEN -- Cannot drop child indexes on native partition sets in PG11+ FOR v_index IN WITH child_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE c1.relname = v_row.partition_tablename::name AND n1.nspname = v_row.partition_schemaname::name ) SELECT c.relname as name , con.conname FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid LEFT JOIN pg_catalog.pg_constraint con ON i.indexrelid = con.conindid JOIN child_info ON i.indrelid = child_info.oid LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop index %s from %s.%s' , v_index.name , v_row.partition_schemaname , v_row.partition_tablename)); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I DROP CONSTRAINT %I' , v_row.partition_schemaname , v_row.partition_tablename , v_index.conname); ELSE EXECUTE format('DROP INDEX %I.%I', v_parent_schema, v_index.name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; -- end native/11 check END IF; -- end v_retention_keep_index IF ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Moving table %s.%s to schema %s' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema)); END IF; EXECUTE format('ALTER TABLE %I.%I SET SCHEMA %I', v_row.partition_schemaname, v_row.partition_tablename, v_retention_schema); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if -- If child table is a subpartition, remove it from part_config & part_config_sub (should cascade due to FK) DELETE FROM @extschema@.part_config WHERE parent_table = v_row.partition_schemaname||'.'||v_row.partition_tablename; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', format('%s partitions dropped.', v_drop_count)); 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_drop_count; 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 DROP TIME PARTITION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE OR REPLACE FUNCTION @extschema@.drop_partition_id(p_parent_table text, p_retention bigint DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_control text; v_control_type text; v_count int; v_drop_count int := 0; v_index record; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_max bigint; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_partition_id bigint; v_partition_type text; v_retention bigint; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_row record; v_row_max_id record; v_step_id bigint; BEGIN /* * Function to drop child tables from an id-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman drop_partition_id')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_id already running.'; RETURN 0; END IF; IF p_retention IS NULL THEN SELECT partition_interval::bigint , partition_type , control , retention::bigint , retention_keep_table , retention_keep_index , retention_schema , jobmon INTO v_partition_interval , v_partition_type , v_control , v_retention , v_retention_keep_table , v_retention_keep_index , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND retention IS NOT NULL; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE -- Allow override of configuration options SELECT partition_interval::bigint , partition_type , control , retention_keep_table , retention_keep_index , retention_schema , jobmon INTO v_partition_interval , v_partition_type , v_control , v_retention_keep_table , v_retention_keep_index , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; v_retention := p_retention; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'id' THEN RAISE EXCEPTION 'Data type of control column in given partition set is not an integer type'; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%I) FROM %I.%I', v_control, v_row_max_id.partition_schemaname, v_row_max_id.partition_tablename) INTO v_max; IF v_max IS NOT NULL THEN EXIT; END IF; END LOOP; -- Loop through child tables of the given parent -- Must go in ascending order to avoid dropping what may be the "last" partition in the set after dropping tables that match retention period FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'ASC') LOOP SELECT child_start_id INTO v_partition_id FROM @extschema@.show_partition_info(v_row.partition_schemaname||'.'||v_row.partition_tablename , v_partition_interval::text , p_parent_table); -- Add one interval since partition names contain the start of the constraint period IF v_retention <= (v_max - (v_partition_id + v_partition_interval)) THEN -- Do not allow final partition to be dropped SELECT count(*) INTO v_count FROM @extschema@.show_partitions(p_parent_table); IF v_count = 1 THEN RAISE WARNING 'Attempt to drop final partition in partition set % as part of retention policy. Advise reviewing retention policy and/or data entry into the partition set.', p_parent_table; CONTINUE; END IF; -- Only create a jobmon entry if there's actual retention work done IF v_jobmon_schema IS NOT NULL AND v_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN DROP ID PARTITION: %s', p_parent_table)); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Detach/Uninherit table %s.%s from %s', v_row.partition_schemaname, v_row.partition_tablename, p_parent_table)); END IF; IF v_partition_type = 'native' THEN EXECUTE format('ALTER TABLE %I.%I DETACH PARTITION %I.%I' , v_parent_schema , v_parent_tablename , v_row.partition_schemaname , v_row.partition_tablename); ELSE EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_row.partition_schemaname , v_row.partition_tablename , v_parent_schema , v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop table %s.%s', v_row.partition_schemaname, v_row.partition_tablename)); END IF; EXECUTE format('DROP TABLE %I.%I CASCADE', v_row.partition_schemaname, v_row.partition_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN IF v_partition_type = 'partman' OR ( v_partition_type = 'native' AND current_setting('server_version_num')::int < 110000) THEN -- Cannot drop child indexes on native partition sets in PG11+ FOR v_index IN WITH child_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE c1.relname = v_row.partition_tablename::name AND n1.nspname = v_row.partition_schema::name ) SELECT c.relname as name , con.conname FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid LEFT JOIN pg_catalog.pg_constraint con ON i.indexrelid = con.conindid JOIN child_info ON i.indrelid = child_info.oid LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop index %s from %s.%s' , v_index.name , v_row.partition_schemaname , v_row.partition_tablename)); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I DROP CONSTRAINT %I', v_row.partition_schemaname, v_row.partition_tablename, v_index.conname); ELSE EXECUTE format('DROP INDEX %I.%I', v_row.partition_schemaname, v_index.name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; -- end native/11 check END IF; -- end v_retention_keep_index IF ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Moving table %s.%s to schema %s' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema)); END IF; EXECUTE format('ALTER TABLE %I.%I SET SCHEMA %I' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if -- If child table is a subpartition, remove it from part_config & part_config_sub (should cascade due to FK) DELETE FROM @extschema@.part_config WHERE parent_table = v_row.partition_schemaname ||'.'||v_row.partition_tablename; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', format('%s partitions dropped.', v_drop_count)); 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_drop_count; 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 DROP ID PARTITION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE OR REPLACE FUNCTION @extschema@.apply_constraints(p_parent_table text, p_child_table text DEFAULT NULL, p_analyze boolean DEFAULT FALSE, p_job_id bigint DEFAULT NULL, p_debug boolean DEFAULT FALSE) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_child_exists text; v_child_tablename text; v_col text; v_constraint_cols text[]; v_constraint_col_type text; v_constraint_name text; v_constraint_valid boolean; v_constraint_values record; v_control text; v_control_type text; v_datetime_string text; v_epoch text; v_existing_constraint_name text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_id bigint; v_last_partition_timestamp timestamptz; v_max_id bigint; v_max_timestamp timestamptz; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_optimize_constraint int; v_parent_schema text; v_parent_table text; v_parent_tablename text; v_partition_interval text; v_partition_suffix text; v_premake int; v_sql text; v_step_id bigint; v_suffix_position int; v_type text; BEGIN /* * Apply constraints managed by partman extension */ SELECT parent_table , partition_type , control , premake , partition_interval , optimize_constraint , epoch , datetime_string , constraint_cols , jobmon , constraint_valid INTO v_parent_table , v_type , v_control , v_premake , v_partition_interval , v_optimize_constraint , v_epoch , v_datetime_string , v_constraint_cols , v_jobmon , v_constraint_valid FROM @extschema@.part_config WHERE parent_table = p_parent_table AND constraint_cols IS NOT NULL; IF v_constraint_cols IS NULL THEN IF p_debug THEN RAISE NOTICE 'Given parent table (%) not set up for constraint management (constraint_cols is NULL)', p_parent_table; END IF; -- Returns silently to allow this function to be simply called by maintenance processes without having to check if config options are set. RETURN; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(v_parent_table, '.', 1)::name AND tablename = split_part(v_parent_table, '.', 2)::name; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE CONSTRAINT: %s', v_parent_table)); ELSE v_job_id = p_job_id; END IF; END IF; -- If p_child_table is null, figure out the partition that is the one right before the optimize_constraint value backwards. IF p_child_table IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying additional constraints: Automatically determining most recent child on which to apply constraints'); END IF; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(v_parent_table, 'DESC') LIMIT 1; IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN SELECT child_start_time INTO v_last_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_partition_interval, v_parent_table); v_partition_suffix := to_char(v_last_partition_timestamp - (v_partition_interval::interval * (v_optimize_constraint + v_premake + 1) ), v_datetime_string); ELSIF v_control_type = 'id' THEN SELECT child_start_id INTO v_last_partition_id FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_partition_interval, v_parent_table); v_partition_suffix := (v_last_partition_id - (v_partition_interval::int * (v_optimize_constraint + v_premake + 1) ))::text; END IF; v_child_tablename := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); IF p_debug THEN RAISE NOTICE 'apply_constraint: v_parent_tablename: % , v_partition_suffix: %', v_parent_tablename, v_partition_suffix; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Target child table: %s.%s', v_parent_schema, v_child_tablename)); END IF; ELSE v_child_tablename = split_part(p_child_table, '.', 2); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying additional constraints: Checking if target child table exists'); END IF; SELECT tablename FROM pg_catalog.pg_tables INTO v_child_exists WHERE schemaname = v_parent_schema::name AND tablename = v_child_tablename::name; IF v_child_exists IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Target child table (%s) does not exist. Skipping constraint creation.', v_child_tablename)); IF p_job_id IS NULL THEN PERFORM close_job(v_job_id); END IF; END IF; IF p_debug THEN RAISE NOTICE 'Target child table (%) does not exist. Skipping constraint creation.', v_child_tablename; END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; FOREACH v_col IN ARRAY v_constraint_cols LOOP SELECT con.conname INTO v_existing_constraint_name FROM pg_catalog.pg_constraint con JOIN pg_class c ON c.oid = con.conrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN pg_catalog.pg_attribute a ON con.conrelid = a.attrelid WHERE c.relname = v_child_tablename::name AND n.nspname = v_parent_schema::name AND con.conname LIKE 'partmanconstr_%' AND con.contype = 'c' AND a.attname = v_col::name AND ARRAY[a.attnum] OPERATOR(pg_catalog.<@) con.conkey AND a.attisdropped = false; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Applying additional constraints: Applying new constraint on column: %s', v_col)); END IF; IF v_existing_constraint_name IS NOT NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Partman managed constraint already exists on this table (%s) and column (%s). Skipping creation.', v_child_tablename, v_col)); END IF; IF p_debug THEN RAISE NOTICE 'Partman managed constraint already exists on this table (%) and column (%). Skipping creation.', v_child_tablename, v_col ; END IF; CONTINUE; END IF; -- Ensure column name gets put on end of constraint name to help avoid naming conflicts v_constraint_name := @extschema@.check_name_length('partmanconstr_'||v_child_tablename, p_suffix := '_'||v_col); EXECUTE format('SELECT min(%I)::text AS min, max(%I)::text AS max FROM %I.%I', v_col, v_col, v_parent_schema, v_child_tablename) INTO v_constraint_values; IF v_constraint_values IS NOT NULL THEN v_sql := format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I <= %L)' , v_parent_schema , v_child_tablename , v_constraint_name , v_col , v_constraint_values.min , v_col , v_constraint_values.max); IF v_constraint_valid = false THEN v_sql := format('%s NOT VALID', v_sql); END IF; IF p_debug THEN RAISE NOTICE 'Constraint creation query: %', v_sql; END IF; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('New constraint created: %s', v_sql)); END IF; ELSE IF p_debug THEN RAISE NOTICE 'Given column (%) contains all NULLs. No constraint created', v_col; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Given column (%s) contains all NULLs. No constraint created', v_col)); END IF; END IF; END LOOP; IF p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Applying additional constraints: Running analyze on partition set: %s', v_parent_table)); END IF; IF p_debug THEN RAISE NOTICE 'Running analyze on partition set: %', v_parent_table; END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); 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 CONSTRAINT: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; 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_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 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 '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; 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 '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 '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 'Create FK: %', v_sql; EXECUTE v_sql; END LOOP; END IF; END IF; -- End foreign key creation -- Tablespace inheritance IF 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; 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 '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; EXECUTE v_sql; END IF; RETURN true; END $$; pg_partman-4.6.0/updates/pg_partman--4.2.0--4.2.1.sql000066400000000000000000000332171412761706100214330ustar00rootroot00000000000000-- Fix bug causing never-ending loop while calculating partition range for natively partitioned tables. Github issue #273 CREATE OR REPLACE FUNCTION @extschema@.partition_data_time( p_parent_table text , p_batch_count int DEFAULT 1 , p_batch_interval interval DEFAULT NULL , p_lock_wait numeric DEFAULT 0 , p_order text DEFAULT 'ASC' , p_analyze boolean DEFAULT true , p_source_table text DEFAULT NULL) RETURNS bigint LANGUAGE plpgsql AS $$ DECLARE v_control text; v_control_type text; v_datetime_string text; v_current_partition_name text; v_default_exists boolean; v_default_schemaname text; v_default_tablename text; v_epoch text; v_last_partition text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_timestamp timestamptz; v_min_partition_timestamp timestamptz; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_tablename text; v_parent_tablename_real text; v_partition_expression text; v_partition_interval interval; v_partition_suffix text; v_partition_timestamp timestamptz[]; v_partition_type text; v_source_schemaname text; v_source_tablename text; v_rowcount bigint; v_sql text; v_start_control timestamptz; v_total_rows bigint := 0; BEGIN /* * Populate the child table(s) of a time-based partition set with old data from the original parent */ SELECT partition_type , partition_interval::interval , control , datetime_string , epoch INTO v_partition_type , v_partition_interval , v_control , v_datetime_string , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: No entry in part_config found for given table: %', p_parent_table; END IF; SELECT schemaname, tablename INTO v_source_schemaname, v_source_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- Preserve real parent tablename for use below v_parent_tablename := v_source_tablename; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_source_schemaname, v_source_tablename, v_control); IF v_control_type <> 'time' THEN IF (v_control_type = 'id' AND v_epoch = 'none') OR v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run on partition set without time based control column or epoch flag set with an id column. Found control: %, epoch: %', v_control_type, v_epoch; END IF; END IF; -- Replace the parent variables with the source variables if using source table for child table data IF p_source_table IS NOT NULL THEN -- Set source table to user given source table instead of parent table v_source_schemaname := NULL; v_source_tablename := NULL; SELECT schemaname, tablename INTO v_source_schemaname, v_source_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_source_table, '.', 1)::name AND tablename = split_part(p_source_table, '.', 2)::name; IF v_source_tablename IS NULL THEN RAISE EXCEPTION 'Given source table does not exist in system catalogs: %', p_source_table; END IF; ELSIF v_partition_type = 'native' AND current_setting('server_version_num')::int >= 110000 THEN IF p_batch_interval IS NOT NULL AND p_batch_interval != v_partition_interval THEN -- This is true because all data for a given child table must be moved out of the default partition before the child table can be created. -- So cannot create the child table when only some of the data has been moved out of the default partition. RAISE EXCEPTION 'Custom intervals are not allowed when moving data out of the DEFAULT partition in a native set. Please leave p_interval/p_batch_interval parameters unset or NULL to allow use of partition set''s default partitioning interval.'; END IF; -- Set source table to default table if PG11+, p_source_table is not set, and it exists -- Otherwise just return with a DEBUG that no data source exists v_sql := format('SELECT n.nspname::text, c.relname::text FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = ''%I.%I''::regclass AND pg_get_expr(relpartbound, c.oid) = ''DEFAULT''' , v_source_schemaname , v_source_tablename); EXECUTE v_sql INTO v_default_schemaname, v_default_tablename; IF v_default_tablename IS NOT NULL THEN v_source_schemaname := v_default_schemaname; v_source_tablename := v_default_tablename; v_default_exists := true; EXECUTE format ('CREATE TEMP TABLE IF NOT EXISTS partman_temp_data_storage (LIKE %I.%I INCLUDING INDEXES)', v_source_schemaname, v_source_tablename); ELSE RAISE DEBUG 'No default table found when partition_data_id() was called'; RETURN v_total_rows; END IF; END IF; 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'); IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) END; FOR i IN 1..p_batch_count LOOP IF p_order = 'ASC' THEN EXECUTE format('SELECT min(%s) FROM ONLY %I.%I', v_partition_expression, v_source_schemaname, v_source_tablename) INTO v_start_control; ELSIF p_order = 'DESC' THEN EXECUTE format('SELECT max(%s) FROM ONLY %I.%I', v_partition_expression, v_source_schemaname, v_source_tablename) INTO v_start_control; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; IF v_start_control IS NULL THEN EXIT; END IF; IF v_partition_type = 'partman' THEN CASE WHEN v_partition_interval = '15 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '15min'::interval * floor(date_part('minute', v_start_control) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '30min'::interval * floor(date_part('minute', v_start_control) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control); WHEN v_partition_interval = '1 day' THEN v_min_partition_timestamp := date_trunc('day', v_start_control); WHEN v_partition_interval = '1 week' THEN v_min_partition_timestamp := date_trunc('week', v_start_control); WHEN v_partition_interval = '1 month' THEN v_min_partition_timestamp := date_trunc('month', v_start_control); WHEN v_partition_interval = '3 months' THEN v_min_partition_timestamp := date_trunc('quarter', v_start_control); WHEN v_partition_interval = '1 year' THEN v_min_partition_timestamp := date_trunc('year', v_start_control); END CASE; ELSIF v_partition_type IN ('time-custom', 'native') THEN SELECT child_start_time INTO v_min_partition_timestamp FROM @extschema@.show_partition_info(v_source_schemaname||'.'||v_last_partition , v_partition_interval::text , p_parent_table); v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; LOOP IF v_start_control >= v_min_partition_timestamp AND v_start_control < v_max_partition_timestamp THEN EXIT; ELSE BEGIN IF v_start_control >= v_max_partition_timestamp THEN -- Keep going forward in time, checking if child partition time interval encompasses the current v_start_control value v_min_partition_timestamp := v_max_partition_timestamp; v_max_partition_timestamp := v_max_partition_timestamp + v_partition_interval; ELSE -- Keep going backwards in time, checking if child partition time interval encompasses the current v_start_control value v_max_partition_timestamp := v_min_partition_timestamp; v_min_partition_timestamp := v_min_partition_timestamp - v_partition_interval; END IF; EXCEPTION WHEN datetime_field_overflow THEN RAISE EXCEPTION 'Attempted partition time interval is outside PostgreSQL''s supported time range. Unable to create partition with interval before timestamp % ', v_min_partition_timestamp; END; END IF; END LOOP; END IF; v_partition_timestamp := ARRAY[v_min_partition_timestamp]; IF p_order = 'ASC' THEN -- Ensure batch interval given as parameter doesn't cause maximum to overflow the current partition maximum IF (v_start_control + p_batch_interval) >= (v_min_partition_timestamp + v_partition_interval) THEN v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; ELSE v_max_partition_timestamp := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; -- Ensure batch interval given as parameter doesn't cause minimum to underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_timestamp THEN v_min_partition_timestamp = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM ONLY %I.%I WHERE %s >= %L AND %3$s < %5$L FOR UPDATE NOWAIT' , v_source_schemaname , v_source_tablename , v_partition_expression , v_min_partition_timestamp , v_max_partition_timestamp); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; -- This suffix generation code is in create_partition_time() as well v_partition_suffix := to_char(v_min_partition_timestamp, v_datetime_string); v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); IF v_default_exists THEN -- Child tables cannot be created in native partitioning if data that belongs to it exists in the default -- Have to move data out to temporary location, create child table, then move it back -- Temp table created above to avoid excessive temp creation in loop EXECUTE format('WITH partition_data AS ( DELETE FROM %1$I.%2$I WHERE %3$I >= %4$L AND %3$I < %5$L RETURNING *) INSERT INTO partman_temp_data_storage SELECT * FROM partition_data' , v_source_schemaname , v_source_tablename , v_control , v_min_partition_timestamp , v_max_partition_timestamp); PERFORM @extschema@.create_partition_time(p_parent_table, v_partition_timestamp, p_analyze); EXECUTE format('WITH partition_data AS ( DELETE FROM partman_temp_data_storage RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_source_schemaname , v_current_partition_name); ELSE PERFORM @extschema@.create_partition_time(p_parent_table, v_partition_timestamp, p_analyze); EXECUTE format('WITH partition_data AS ( DELETE FROM ONLY %I.%I WHERE %s >= %L AND %3$s < %5$L RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_source_schemaname , v_source_tablename , v_partition_expression , v_min_partition_timestamp , v_max_partition_timestamp , v_source_schemaname , v_current_partition_name); END IF; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; IF v_partition_type IN ('partman', 'time-custom') THEN PERFORM @extschema@.create_function_time(p_parent_table); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_total_rows; END $$; pg_partman-4.6.0/updates/pg_partman--4.2.1--4.2.2.sql000066400000000000000000000217521412761706100214360ustar00rootroot00000000000000CREATE 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_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 '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; 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 '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 '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 'Create FK: %', v_sql; EXECUTE v_sql; END LOOP; END IF; END IF; -- End foreign key creation -- Tablespace inheritance IF 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; 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 '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; EXECUTE v_sql; END IF; RETURN true; END $$; pg_partman-4.6.0/updates/pg_partman--4.2.2--4.3.0.sql000066400000000000000000004523261412761706100214430ustar00rootroot00000000000000-- NEW FEATURES -- ============ -- New documentation for how to migrate a trigger-based partition set managed by pg_partman to a native set - doc/migrate_to_native.md (Github Issue #282) -- Added new table matrix to the pg_partman.md documentation for which properties are handled via the special pg_partman template table depending on your version of PG. Please review this matrix carefully when updating the major version of PG to see which template properties may need to be applied to the real parent for inheritance to keep working as expected. -- Tablespace inheritance for PG12+ is now handled via the real parent table of the partition set, no longer via the pg_partman template table. Note that tablespaces for primary/unique keys that are not part of the partition key must still be handled via the template where those indexes are defined. For PG11 and earlier, tablespace inheritance must still be handled via the template table. If you are upgrading from PG11 or earlier to PG12+, ensure the tablespace is properly set on the parent table for inheritance to work as expected. (Github Issue #274) -- Added new function dump_partitioned_table_definition() to provide a means of replaying partition setup statements in another database or during a dump/restore. Thanks to James Coleman (jcoleman) for providing this new feature! (Github Issue #260, PR #291) -- BUG FIXES -- ========= -- Fixed maintenance runs with retention, or direct calls to drop_partition*() functions, failing if there were foreign keys pointing to the child table that was to be dropped. (Github Issue #294) -- Fixed partition_data*() functions failing if they are run in the same session multiple times. Temporary tables were not being cleaned up properly. (Github Issue #295) -- Changed child_end_time value of internal show_partition_info() function to show the non-inclusive end time boundary value. Previously it would subtract one second to try and show the actual values being stored in the partition, but this was causing issues trying to build boundary values when trying to migrate to native partitioning. This was previously an undocumented function, so its API was not guaranteed to be stable. It is now documented so any further changes will be fully documented. CREATE OR REPLACE FUNCTION @extschema@.show_partition_info(p_child_table text , p_partition_interval text DEFAULT NULL , p_parent_table text DEFAULT NULL , OUT child_start_time timestamptz , OUT child_end_time timestamptz , OUT child_start_id bigint , OUT child_end_id bigint , OUT suffix text) RETURNS record LANGUAGE plpgsql STABLE AS $$ DECLARE v_child_schema text; v_child_tablename text; v_control text; v_control_type text; v_datetime_string text; v_epoch text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_table text; v_partition_interval text; v_partition_type text; v_quarter text; v_suffix text; v_suffix_position int; v_year text; BEGIN /* * Show the data boundries for a given child table as well as the suffix that will be used. * Passing the parent table argument improves performance by avoiding a catalog lookup. * Passing an interval lets you set one different than the default configured one if desired. */ 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'); SELECT n.nspname, c.relname INTO v_child_schema, v_child_tablename FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = split_part(p_child_table, '.', 1)::name AND c.relname = split_part(p_child_table, '.', 2)::name; IF v_child_tablename IS NULL THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RAISE EXCEPTION 'Child table given does not exist (%)', p_child_table; END IF; IF p_parent_table IS NULL THEN SELECT n.nspname||'.'|| c.relname INTO v_parent_table FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhparent JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhrelid::regclass = p_child_table::regclass; ELSE v_parent_table := p_parent_table; END IF; IF p_partition_interval IS NULL THEN SELECT control, partition_interval, partition_type, datetime_string, epoch INTO v_control, v_partition_interval, v_partition_type, v_datetime_string, v_epoch FROM @extschema@.part_config WHERE parent_table = v_parent_table; ELSE v_partition_interval := p_partition_interval; SELECT control, partition_type, datetime_string, epoch INTO v_control, v_partition_type, v_datetime_string, v_epoch FROM @extschema@.part_config WHERE parent_table = v_parent_table; END IF; IF v_control IS NULL THEN RAISE EXCEPTION 'Parent table of given child not managed by pg_partman: %', v_parent_table; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_child_schema, v_child_tablename, v_control); v_suffix_position := (length(v_child_tablename) - position('p_' in reverse(v_child_tablename))) + 2; v_suffix := substring(v_child_tablename from v_suffix_position); IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN IF v_partition_interval::interval <> '3 months' OR (v_partition_interval::interval = '3 months' AND v_partition_type = 'time-custom') THEN child_start_time := to_timestamp(v_suffix, v_datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(v_suffix, 'q', 1); v_quarter := split_part(v_suffix, 'q', 2); CASE WHEN v_quarter = '1' THEN child_start_time := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN child_start_time := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN child_start_time := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN child_start_time := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); ELSE -- handle case when partition name did not use "q" convetion child_start_time := to_timestamp(v_suffix, v_datetime_string); END CASE; END IF; child_end_time := (child_start_time + v_partition_interval::interval); ELSIF v_control_type = 'id' THEN child_start_id := v_suffix::bigint; child_end_id := (child_start_id + v_partition_interval::bigint) - 1; ELSE RAISE EXCEPTION 'Invalid partition type encountered in show_partition_info()'; END IF; suffix = v_suffix; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN; END $$; 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_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 '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; 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 '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 '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 '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 '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 '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; EXECUTE v_sql; END IF; RETURN true; END $$; CREATE OR REPLACE FUNCTION @extschema@.create_partition_time(p_parent_table text, p_partition_times timestamptz[], p_analyze boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_control_type text; v_datetime_string text; v_epoch text; v_exists smallint; v_grantees text[]; v_hasoids boolean; v_inherit_privileges boolean; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_grant record; v_parent_schema text; v_parent_tablename text; v_part_col text; v_partition_created boolean := false; v_partition_name text; v_partition_suffix text; v_parent_tablespace text; v_partition_expression text; v_partition_interval interval; v_partition_timestamp_end timestamptz; v_partition_timestamp_start timestamptz; v_publications text[]; v_quarter text; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_sub_control text; v_sub_parent text; v_sub_partition_type text; v_sub_timestamp_max timestamptz; v_sub_timestamp_min timestamptz; v_template_table text; v_trunc_value text; v_time timestamptz; v_partition_type text; v_unlogged char; v_year text; BEGIN /* * Function to create a child table in a time-based partition set */ SELECT partition_type , control , partition_interval , epoch , inherit_fk , jobmon , datetime_string , template_table , publications , inherit_privileges INTO v_partition_type , v_control , v_partition_interval , v_epoch , v_inherit_fk , v_jobmon , v_datetime_string , v_template_table , v_publications , v_inherit_privileges FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT n.nspname, c.relname, t.spcname INTO v_parent_schema, v_parent_tablename, v_parent_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 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; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'time' THEN IF (v_control_type = 'id' AND v_epoch = 'none') OR v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run on partition set without time based control column or epoch flag set with an id column. Found control: %, epoch: %', v_control_type, v_epoch; END IF; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::timestamptz, sub_max::timestamptz INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'time'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) END; IF p_debug THEN RAISE NOTICE 'create_partition_time: v_partition_expression: %', v_partition_expression; END IF; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_timestamp_start := v_time; BEGIN v_partition_timestamp_end := v_time + v_partition_interval; 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_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_time||' skipped'); CONTINUE; END; -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_timestamp_min IS NOT NULL THEN IF v_time < v_sub_timestamp_min OR v_time > v_sub_timestamp_max THEN CONTINUE; END IF; END IF; -- This suffix generation code is in partition_data_time() as well v_partition_suffix := to_char(v_time, v_datetime_string); v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); -- Check if child exists. SELECT count(*) INTO v_exists 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_partition_name::name; IF v_exists > 0 THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Creating new partition %s.%s with interval from %s to %s' , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end-'1sec'::interval)); END IF; v_sql := 'CREATE'; -- As of PG12, the unlogged/logged status of a native parent table cannot be changed via an ALTER TABLE in order to affect its children. -- As of v4.2x, the unlogged state will be managed via the template table SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c 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; IF v_unlogged = 'u' and v_partition_type != 'native' THEN v_sql := v_sql || ' UNLOGGED'; END IF; -- Close parentheses on LIKE are below due to differing requirements of native subpartitioning -- Same INCLUDING list is used in create_parent() v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING STORAGE INCLUDING COMMENTS ' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); SELECT sub_partition_type, sub_control INTO v_sub_partition_type, v_sub_control FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; IF v_sub_partition_type = 'native' THEN -- INCLUDING INDEXES isn't necessary for native partitioning. It isn't supported in v10 and -- for v11+ index inheritance is automatically handled when the partition is attached v_sql := v_sql || format(') PARTITION BY RANGE (%I) ', v_sub_control); ELSE v_sql := v_sql || format(' INCLUDING INDEXES) ', v_sub_control); END IF; IF current_setting('server_version_num')::int < 120000 THEN -- column removed from pgclass in pg12 SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c 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; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; END IF; IF p_debug THEN RAISE NOTICE 'create_partition_time v_sql: %', v_sql; END IF; EXECUTE v_sql; IF v_partition_type = 'native' THEN IF current_setting('server_version_num')::int >= 120000 THEN -- PG12 fixed tablespace marking on the parent of a native partition set -- Versions older than 12 handle tablespace setting via inherit_template_properties() call below IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; END IF; IF v_template_table IS NOT NULL THEN PERFORM @extschema@.inherit_template_properties(p_parent_table, v_parent_schema, v_partition_name); END IF; IF v_epoch = 'none' THEN -- Attach with normal, time-based values for native constraint EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end); ELSE -- Must attach with integer based values for native constraint and epoch IF v_epoch = 'seconds' THEN EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , EXTRACT('epoch' FROM v_partition_timestamp_start) , EXTRACT('epoch' FROM v_partition_timestamp_end)); ELSIF v_epoch = 'milliseconds' THEN EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , EXTRACT('epoch' FROM v_partition_timestamp_start) * 1000 , EXTRACT('epoch' FROM v_partition_timestamp_end) * 1000); END IF; -- Create secondary, time-based constraint since native's constraint is already integer based EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%s >= %L AND %4$s < %6$L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_partition_expression , v_partition_timestamp_start , v_partition_timestamp_end); END IF; ELSE -- non-native IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; -- Non-native always gets time-based constraint EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%s >= %L AND %4$s < %6$L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_partition_expression , v_partition_timestamp_start , v_partition_timestamp_end); IF v_epoch = 'seconds' THEN -- Non-native needs secondary, integer based constraint for epoch EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_int_check' , v_control , EXTRACT('epoch' from v_partition_timestamp_start) , v_control , EXTRACT('epoch' from v_partition_timestamp_end) ); ELSIF v_epoch = 'milliseconds' THEN EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_int_check' , v_control , EXTRACT('epoch' from v_partition_timestamp_start) * 1000 , v_control , EXTRACT('epoch' from v_partition_timestamp_end) * 1000); END IF; EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); -- If custom time, set extra config options. IF v_partition_type = 'time-custom' THEN INSERT INTO @extschema@.custom_time_partitions (parent_table, child_table, partition_range) VALUES ( p_parent_table, v_parent_schema||'.'||v_partition_name, tstzrange(v_partition_timestamp_start, v_partition_timestamp_end, '[)') ); END IF; -- Indexes cannot be created on the parent, so clustering cannot be used for native yet. PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); -- Foreign keys to other tables not supported in native IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; END IF; -- end native check -- NOTE: Privileges not automatically inherited for native. Only do so if config flag is set IF v_partition_type != 'native' OR (v_partition_type = 'native' AND v_inherit_privileges = TRUE) THEN PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_automatic_maintenance , sub_infinite_time_partitions , sub_jobmon , sub_trigger_exception_handling , sub_template_table , sub_inherit_privileges , sub_constraint_valid FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Subpartitioning %s.%s', v_parent_schema, v_partition_name)); END IF; 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_inherit_fk := %L , p_epoch := %L , p_template_table := %L , p_jobmon := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_automatic_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_template_table , v_row.sub_jobmon); IF p_debug THEN RAISE NOTICE 'create_partition_time (create_parent loop): %', v_sql; END IF; EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions , trigger_exception_handling = v_row.sub_trigger_exception_handling , inherit_privileges = v_row.sub_inherit_privileges , constraint_valid = v_row.sub_constraint_valid WHERE parent_table = v_parent_schema||'.'||v_partition_name; END LOOP; -- end sub partitioning LOOP -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table, p_job_id := v_job_id, p_debug := p_debug); IF v_publications IS NOT NULL THEN -- NOTE: Publications currently not supported on parent table, but are supported on the table partitions if individually assigned. PERFORM @extschema@.apply_publications(p_parent_table, v_parent_schema, v_partition_name); END IF; v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s. Attempted intervals: %s', p_parent_table, p_partition_times)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; 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_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE OR REPLACE FUNCTION @extschema@.create_partition_id(p_parent_table text, p_partition_ids bigint[], p_analyze boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_control_type text; v_exists text; v_grantees text[]; v_hasoids boolean; v_id bigint; v_inherit_fk boolean; v_inherit_privileges boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_grant record; v_parent_schema text; v_parent_tablename text; v_parent_tablespace text; v_partition_interval bigint; v_partition_created boolean := false; v_partition_name text; v_partition_type text; v_publications text[]; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_sub_control text; v_sub_partition_type text; v_sub_id_max bigint; v_sub_id_min bigint; v_template_table text; v_unlogged char; BEGIN /* * Function to create id partitions */ SELECT control , partition_type , partition_interval , inherit_fk , jobmon , template_table , publications , inherit_privileges INTO v_control , v_partition_type , v_partition_interval , v_inherit_fk , v_jobmon , v_template_table , v_publications , v_inherit_privileges FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT n.nspname, c.relname, t.spcname INTO v_parent_schema, v_parent_tablename, v_parent_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 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; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'id' THEN RAISE EXCEPTION 'ERROR: Given parent table is not set up for id/serial partitioning'; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'id'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; FOREACH v_id IN ARRAY p_partition_ids LOOP -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_id_min IS NOT NULL THEN IF v_id < v_sub_id_min OR v_id > v_sub_id_max THEN CONTINUE; END IF; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_id::text, TRUE); -- If child table already exists, skip creation -- Have to check pg_class because if subpartitioned, table will not be in pg_tables SELECT c.relname INTO v_exists 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_partition_name::name; IF v_exists IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_id||' to '||(v_id + v_partition_interval)-1); END IF; v_sql := 'CREATE'; -- As of PG12, the unlogged/logged status of a native parent table cannot be changed via an ALTER TABLE in order to affect its children. -- As of v4.2x, the unlogged state will be managed via the template table SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c 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; IF v_unlogged = 'u' and v_partition_type != 'native' THEN v_sql := v_sql || ' UNLOGGED'; END IF; -- Close parentheses on LIKE are below due to differing requirements of native subpartitioning -- Same INCLUDING list is used in create_parent() v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING STORAGE INCLUDING COMMENTS ' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); SELECT sub_partition_type, sub_control INTO v_sub_partition_type, v_sub_control FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; IF v_sub_partition_type = 'native' THEN -- INCLUDING INDEXES isn't necessary for native partitioning. It isn't supported in v10 and -- for v11+ index inheritance is automatically handled when the partition is attached v_sql := v_sql || format(') PARTITION BY RANGE (%I) ', v_sub_control); ELSE v_sql := v_sql || format(' INCLUDING INDEXES) ', v_sub_control); END IF; IF current_setting('server_version_num')::int < 120000 THEN -- column removed from pgclass in pg12 SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c 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; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; END IF; IF p_debug THEN RAISE NOTICE 'create_partition_id v_sql: %', v_sql; END IF; EXECUTE v_sql; IF v_partition_type = 'native' THEN IF current_setting('server_version_num')::int >= 120000 THEN -- PG12 fixed tablespace marking on the parent of a native partition set -- Versions older than 12 handle tablespace setting via inherit_template_properties() call below IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; END IF; IF v_template_table IS NOT NULL THEN PERFORM @extschema@.inherit_template_properties(p_parent_table, v_parent_schema, v_partition_name); END IF; EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , v_id , v_id + v_partition_interval); ELSE -- non-native IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %s AND %I < %s )' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_control , v_id , v_control , v_id + v_partition_interval); EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I', v_parent_schema, v_partition_name, v_parent_schema, v_parent_tablename); -- Indexes cannot be created on the parent, so clustering cannot be used for native yet. PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); -- Foreign keys to other tables not supported on native parent tables IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; END IF; -- NOTE: Privileges not automatically inherited for native. Only do so if config flag is set IF v_partition_type != 'native' OR (v_partition_type = 'native' AND v_inherit_privileges = TRUE) THEN PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_automatic_maintenance , sub_infinite_time_partitions , sub_jobmon , sub_trigger_exception_handling , sub_template_table , sub_inherit_privileges , sub_constraint_valid FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Subpartitioning '||v_partition_name); END IF; 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_inherit_fk := %L , p_epoch := %L , p_template_table := %L , p_jobmon := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_automatic_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_template_table , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions , trigger_exception_handling = v_row.sub_trigger_exception_handling , inherit_privileges = v_row.sub_inherit_privileges , constraint_valid = v_row.sub_constraint_valid WHERE parent_table = v_parent_schema||'.'||v_partition_name; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; -- end sub partitioning LOOP -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table, p_job_id := v_job_id, p_debug := p_debug); IF v_publications IS NOT NULL THEN -- NOTE: Publications currently not supported on parent table, but are supported on the table partitions if individually assigned. PERFORM @extschema@.apply_publications(p_parent_table, v_parent_schema, v_partition_name); END IF; v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s', p_parent_table)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; 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 , p_debug boolean DEFAULT false) 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_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 ... PARITIONED 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 ) 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 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) 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); -- 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 INTO v_higher_parent_schema, v_higher_parent_table, v_higher_parent_control 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' 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); 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 $$; CREATE FUNCTION partition_gap_fill(p_parent_table text) RETURNS integer LANGUAGE plpgsql AS $$ DECLARE v_child_created boolean; v_children_created_count int := 0; v_control text; v_control_type text; v_current_child_start_id bigint; v_current_child_start_timestamp timestamptz; v_expected_next_child_id bigint; v_expected_next_child_timestamp timestamptz; v_final_child_schemaname text; v_final_child_start_id bigint; v_final_child_start_timestamp timestamptz; v_final_child_tablename text; v_interval_id bigint; v_interval_time interval; v_previous_child_schemaname text; v_previous_child_tablename text; v_previous_child_start_id bigint; v_previous_child_start_timestamp timestamptz; v_parent_schema text; v_parent_table text; v_parent_tablename text; v_partition_interval text; v_row record; BEGIN SELECT parent_table, partition_interval, control INTO v_parent_table, v_partition_interval, v_control 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; END IF; SELECT n.nspname, c.relname INTO v_parent_schema, v_parent_tablename 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_tablename IS NULL THEN RAISE EXCEPTION 'Unable to find given parent table in system catalogs. Ensure it is schema qualified: %', p_parent_table; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); SELECT partition_schemaname, partition_tablename INTO v_final_child_schemaname, v_final_child_tablename FROM @extschema@.show_partitions(v_parent_table, 'DESC') LIMIT 1; IF v_control_type = 'time' THEN v_interval_time := v_partition_interval::interval; SELECT child_start_time INTO v_final_child_start_timestamp FROM @extschema@.show_partition_info(format('%s', v_final_child_schemaname||'.'||v_final_child_tablename), p_parent_table := v_parent_table); FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_parent_table, 'ASC') LOOP RAISE DEBUG 'v_row.partition_tablename: %, v_final_child_start_timestamp: %', v_row.partition_tablename, v_final_child_start_timestamp; IF v_previous_child_tablename IS NULL THEN v_previous_child_schemaname := v_row.partition_schemaname; v_previous_child_tablename := v_row.partition_tablename; SELECT child_start_time INTO v_previous_child_start_timestamp FROM @extschema@.show_partition_info(format('%s', v_previous_child_schemaname||'.'||v_previous_child_tablename), p_parent_table := v_parent_table); CONTINUE; END IF; v_expected_next_child_timestamp := v_previous_child_start_timestamp + v_interval_time; RAISE DEBUG 'v_expected_next_child_timestamp: %', v_expected_next_child_timestamp; IF v_expected_next_child_timestamp = v_final_child_start_timestamp THEN EXIT; END IF; SELECT child_start_time INTO v_current_child_start_timestamp FROM @extschema@.show_partition_info(format('%s', v_row.partition_schemaname||'.'||v_row.partition_tablename), p_parent_table := v_parent_table); RAISE DEBUG 'v_current_child_start_timestamp: %', v_current_child_start_timestamp; IF v_expected_next_child_timestamp != v_current_child_start_timestamp THEN v_child_created := @extschema@.create_partition_time(v_parent_table, ARRAY[v_expected_next_child_timestamp]); IF v_child_created THEN v_children_created_count := v_children_created_count + 1; v_child_created := false; END IF; SELECT partition_schema, partition_table INTO v_previous_child_schemaname, v_previous_child_tablename FROM @extschema@.show_partition_name(v_parent_table, v_expected_next_child_timestamp::text); -- Need to stay in another inner loop until the next expected child timestamp matches the current one -- Once it does, exit. This means gap is filled. LOOP v_previous_child_start_timestamp := v_expected_next_child_timestamp; v_expected_next_child_timestamp := v_expected_next_child_timestamp + v_interval_time; IF v_expected_next_child_timestamp = v_current_child_start_timestamp THEN EXIT; ELSE RAISE DEBUG 'inner loop: v_previous_child_start_timestamp: %, v_expected_next_child_timestamp: %, v_children_created_count: %' , v_previous_child_start_timestamp, v_expected_next_child_timestamp, v_children_created_count; v_child_created := @extschema@.create_partition_time(v_parent_table, ARRAY[v_expected_next_child_timestamp]); IF v_child_created THEN v_children_created_count := v_children_created_count + 1; v_child_created := false; END IF; END IF; END LOOP; -- end expected child loop END IF; v_previous_child_schemaname := v_row.partition_schemaname; v_previous_child_tablename := v_row.partition_tablename; SELECT child_start_time INTO v_previous_child_start_timestamp FROM @extschema@.show_partition_info(format('%s', v_previous_child_schemaname||'.'||v_previous_child_tablename), p_parent_table := v_parent_table); END LOOP; -- end time loop ELSIF v_control_type = 'id' THEN v_interval_id := v_partition_interval::bigint; SELECT child_start_id INTO v_final_child_start_id FROM @extschema@.show_partition_info(format('%s', v_final_child_schemaname||'.'||v_final_child_tablename), p_parent_table := v_parent_table); FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_parent_table, 'ASC') LOOP RAISE DEBUG 'v_row.partition_tablename: %, v_final_child_start_id: %', v_row.partition_tablename, v_final_child_start_id; IF v_previous_child_tablename IS NULL THEN v_previous_child_schemaname := v_row.partition_schemaname; v_previous_child_tablename := v_row.partition_tablename; SELECT child_start_id INTO v_previous_child_start_id FROM @extschema@.show_partition_info(format('%s', v_previous_child_schemaname||'.'||v_previous_child_tablename), p_parent_table := v_parent_table); CONTINUE; END IF; v_expected_next_child_id := v_previous_child_start_id + v_interval_id; RAISE DEBUG 'v_expected_next_child_id: %', v_expected_next_child_id; IF v_expected_next_child_id = v_final_child_start_id THEN EXIT; END IF; SELECT child_start_id INTO v_current_child_start_id FROM @extschema@.show_partition_info(format('%s', v_row.partition_schemaname||'.'||v_row.partition_tablename), p_parent_table := v_parent_table); RAISE DEBUG 'v_current_child_start_id: %', v_current_child_start_id; IF v_expected_next_child_id != v_current_child_start_id THEN v_child_created := @extschema@.create_partition_id(v_parent_table, ARRAY[v_expected_next_child_id]); IF v_child_created THEN v_children_created_count := v_children_created_count + 1; v_child_created := false; END IF; SELECT partition_schema, partition_table INTO v_previous_child_schemaname, v_previous_child_tablename FROM @extschema@.show_partition_name(v_parent_table, v_expected_next_child_id::text); -- Need to stay in another inner loop until the next expected child id matches the current one -- Once it does, exit. This means gap is filled. LOOP v_previous_child_start_id := v_expected_next_child_id; v_expected_next_child_id := v_expected_next_child_id + v_interval_id; IF v_expected_next_child_id = v_current_child_start_id THEN EXIT; ELSE RAISE DEBUG 'inner loop: v_previous_child_start_id: %, v_expected_next_child_id: %, v_children_created_count: %' , v_previous_child_start_id, v_expected_next_child_id, v_children_created_count; v_child_created := @extschema@.create_partition_id(v_parent_table, ARRAY[v_expected_next_child_id]); IF v_child_created THEN v_children_created_count := v_children_created_count + 1; v_child_created := false; END IF; END IF; END LOOP; -- end expected child loop END IF; v_previous_child_schemaname := v_row.partition_schemaname; v_previous_child_tablename := v_row.partition_tablename; SELECT child_start_id INTO v_previous_child_start_id FROM @extschema@.show_partition_info(format('%s', v_previous_child_schemaname||'.'||v_previous_child_tablename), p_parent_table := v_parent_table); END LOOP; -- end id loop END IF; -- end time/id if RETURN v_children_created_count; END $$; CREATE FUNCTION @extschema@.dump_partitioned_table_definition( p_parent_table TEXT, p_ignore_template_table BOOLEAN DEFAULT false ) RETURNS TEXT LANGUAGE PLPGSQL STABLE AS $$ DECLARE v_create_parent_definition TEXT; v_update_part_config_definition TEXT; -- Columns from part_config table. v_parent_table TEXT; -- NOT NULL v_control TEXT; -- NOT NULL v_partition_type TEXT; -- NOT NULL v_partition_interval TEXT; -- NOT NULL v_constraint_cols TEXT[]; v_premake integer; -- NOT NULL v_optimize_trigger integer; -- NOT NULL v_optimize_constraint integer; -- NOT NULL v_epoch text; -- NOT NULL v_inherit_fk BOOLEAN; -- NOT NULL v_retention TEXT; v_retention_schema TEXT; v_retention_keep_table BOOLEAN; -- NOT NULL v_retention_keep_index BOOLEAN; -- NOT NULL v_infinite_time_partitions BOOLEAN; -- NOT NULL v_datetime_string TEXT; v_automatic_maintenance TEXT; -- NOT NULL v_jobmon BOOLEAN; -- NOT NULL v_sub_partition_set_full BOOLEAN; -- NOT NULL v_trigger_exception_handling BOOLEAN; v_upsert TEXT; -- NOT NULL v_trigger_return_null BOOLEAN; -- NOT NULL v_template_table TEXT; v_publications TEXT[]; v_inherit_privileges BOOLEAN; -- DEFAULT false v_constraint_valid BOOLEAN; -- DEFAULT true NOT NULL BEGIN SELECT pc.parent_table, pc.control, pc.partition_type, pc.partition_interval, pc.constraint_cols, pc.premake, pc.optimize_trigger, pc.optimize_constraint, pc.epoch, pc.inherit_fk, pc.retention, pc.retention_schema, pc.retention_keep_table, pc.retention_keep_index, pc.infinite_time_partitions, pc.datetime_string, pc.automatic_maintenance, pc.jobmon, pc.sub_partition_set_full, pc.trigger_exception_handling, pc.upsert, pc.trigger_return_null, pc.template_table, pc.publications, pc.inherit_privileges, pc.constraint_valid INTO v_parent_table, v_control, v_partition_type, v_partition_interval, v_constraint_cols, v_premake, v_optimize_trigger, v_optimize_constraint, v_epoch, v_inherit_fk, v_retention, v_retention_schema, v_retention_keep_table, v_retention_keep_index, v_infinite_time_partitions, v_datetime_string, v_automatic_maintenance, v_jobmon, v_sub_partition_set_full, v_trigger_exception_handling, v_upsert, v_trigger_return_null, v_template_table, v_publications, v_inherit_privileges, v_constraint_valid FROM @extschema@.part_config pc WHERE pc.parent_table = p_parent_table; IF v_partition_type = 'partman' THEN CASE WHEN v_partition_interval::INTERVAL = '1 year'::INTERVAL THEN v_partition_interval := 'yearly'; WHEN v_partition_interval::INTERVAL = '3 months'::INTERVAL THEN v_partition_interval := 'quarterly'; WHEN v_partition_interval::INTERVAL = '1 month'::INTERVAL THEN v_partition_interval := 'monthly'; WHEN v_partition_interval::INTERVAL = '1 week'::INTERVAL THEN v_partition_interval := 'weekly'; WHEN v_partition_interval::INTERVAL = '1 day'::INTERVAL THEN v_partition_interval := 'daily'; WHEN v_partition_interval::INTERVAL = '1 hour'::INTERVAL THEN v_partition_interval := 'hourly'; WHEN v_partition_interval::INTERVAL = '30 mins'::INTERVAL THEN v_partition_interval := 'half-hour'; WHEN v_partition_interval::INTERVAL = '15 mins'::INTERVAL THEN v_partition_interval := 'quarter-hour'; ELSE RAISE EXCEPTION 'Partitioning interval not recognized for "partman" partitioning type'; END CASE; END IF; IF v_partition_type = 'native' AND p_ignore_template_table THEN v_template_table := NULL; END IF; v_create_parent_definition := format( E'SELECT @extschema@.create_parent( \tp_parent_table := %L, \tp_control := %L, \tp_type := %L, \tp_interval := %L, \tp_constraint_cols := %L, \tp_premake := %s, \tp_automatic_maintenance := %L, \tp_inherit_fk := %L, \tp_epoch := %L, \tp_upsert := %L, \tp_publications := %L, \tp_trigger_return_null := %L, \tp_template_table := %L, \tp_jobmon := %L \t-- v_start_partition is intentionally ignored as there \t-- isn''t any obviously correct definition. );', v_parent_table, v_control, v_partition_type, v_partition_interval, v_constraint_cols, v_premake, v_automatic_maintenance, v_inherit_fk, v_epoch, v_upsert, v_publications, v_trigger_return_null, v_template_table, v_jobmon ); v_update_part_config_definition := format( E'UPDATE @extschema@.part_config SET \toptimize_trigger = %s, \toptimize_constraint = %s, \tretention = %L, \tretention_schema = %L, \tretention_keep_table = %L, \tretention_keep_index = %L, \tinfinite_time_partitions = %L, \tdatetime_string = %L, \tsub_partition_set_full = %L, \ttrigger_exception_handling = %L, \tinherit_privileges = %L, \tconstraint_valid = %L WHERE parent_table = %L;', v_optimize_trigger, v_optimize_constraint, v_retention, v_retention_schema, v_retention_keep_table, v_retention_keep_index, v_infinite_time_partitions, v_datetime_string, v_sub_partition_set_full, v_trigger_exception_handling, v_inherit_privileges, v_constraint_valid, v_parent_table ); RETURN concat_ws(E'\n', v_create_parent_definition, v_update_part_config_definition ); END $$; CREATE OR REPLACE FUNCTION @extschema@.drop_partition_time(p_parent_table text, p_retention interval DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_control text; v_control_type text; v_count int; v_datetime_string text; v_drop_count int := 0; v_epoch text; v_index record; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_partition_timestamp timestamptz; v_partition_type text; v_retention interval; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_row record; v_step_id bigint; BEGIN /* * Function to drop child tables from a time-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman drop_partition_time')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_time already running.'; RETURN 0; END IF; -- Allow override of configuration options IF p_retention IS NULL THEN SELECT partition_type , control , partition_interval::interval , epoch , retention::interval , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_partition_type , v_control , v_partition_interval , v_epoch , v_retention , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND retention IS NOT NULL; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE SELECT partition_type , partition_interval::interval , epoch , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_partition_type , v_partition_interval , v_epoch , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; v_retention := p_retention; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'time' THEN IF (v_control_type = 'id' AND v_epoch = 'none') OR v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run on partition set without time based control column or epoch flag set with an id column. Found control: %, epoch: %', v_control_type, v_epoch; END IF; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- Loop through child tables of the given parent -- Must go in ascending order to avoid dropping what may be the "last" partition in the set after dropping tables that match retention period FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'ASC') LOOP -- pull out datetime portion of partition's tablename to make the next one SELECT child_start_time INTO v_partition_timestamp FROM @extschema@.show_partition_info(v_row.partition_schemaname||'.'||v_row.partition_tablename , v_partition_interval::text , p_parent_table); -- Add one interval since partition names contain the start of the constraint period IF v_retention < (CURRENT_TIMESTAMP - (v_partition_timestamp + v_partition_interval)) THEN -- Do not allow final partition to be dropped SELECT count(*) INTO v_count FROM @extschema@.show_partitions(p_parent_table); IF v_count = 1 THEN RAISE WARNING 'Attempt to drop final partition in partition set % as part of retention policy. If you see this message multiple times for the same table, advise reviewing retention policy and/or data entry into the partition set. Also consider setting "infinite_time_partitions = true" if there are large gaps in data insertion.).', p_parent_table; CONTINUE; END IF; -- Only create a jobmon entry if there's actual retention work done IF v_jobmon_schema IS NOT NULL AND v_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN DROP TIME PARTITION: %s', p_parent_table)); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Detach/Uninherit table %s.%s from %s' , v_row.partition_schemaname , v_row.partition_tablename , p_parent_table)); END IF; IF v_retention_keep_table = true THEN -- No need to detach partition before dropping since it's going away anyway -- Avoids issue of FKs not allowing detachment (Github Issue #294). IF v_partition_type = 'native' THEN EXECUTE format('ALTER TABLE %I.%I DETACH PARTITION %I.%I' , v_parent_schema , v_parent_tablename , v_row.partition_schemaname , v_row.partition_tablename); ELSE EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_row.partition_schemaname , v_row.partition_tablename , v_parent_schema , v_parent_tablename); END IF; END IF; IF v_partition_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_row.partition_schemaname||'.'||v_row.partition_tablename; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop table %s.%s', v_row.partition_schemaname, v_row.partition_tablename)); END IF; EXECUTE format('DROP TABLE %I.%I CASCADE', v_row.partition_schemaname, v_row.partition_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN IF v_partition_type = 'partman' OR ( v_partition_type = 'native' AND current_setting('server_version_num')::int < 110000) THEN -- Cannot drop child indexes on native partition sets in PG11+ FOR v_index IN WITH child_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE c1.relname = v_row.partition_tablename::name AND n1.nspname = v_row.partition_schemaname::name ) SELECT c.relname as name , con.conname FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid LEFT JOIN pg_catalog.pg_constraint con ON i.indexrelid = con.conindid JOIN child_info ON i.indrelid = child_info.oid LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop index %s from %s.%s' , v_index.name , v_row.partition_schemaname , v_row.partition_tablename)); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I DROP CONSTRAINT %I' , v_row.partition_schemaname , v_row.partition_tablename , v_index.conname); ELSE EXECUTE format('DROP INDEX %I.%I', v_parent_schema, v_index.name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; -- end native/11 check END IF; -- end v_retention_keep_index IF ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Moving table %s.%s to schema %s' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema)); END IF; EXECUTE format('ALTER TABLE %I.%I SET SCHEMA %I', v_row.partition_schemaname, v_row.partition_tablename, v_retention_schema); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if -- If child table is a subpartition, remove it from part_config & part_config_sub (should cascade due to FK) DELETE FROM @extschema@.part_config WHERE parent_table = v_row.partition_schemaname||'.'||v_row.partition_tablename; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', format('%s partitions dropped.', v_drop_count)); 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_drop_count; 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 DROP TIME PARTITION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE OR REPLACE FUNCTION @extschema@.drop_partition_id(p_parent_table text, p_retention bigint DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_control text; v_control_type text; v_count int; v_drop_count int := 0; v_index record; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_max bigint; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_partition_id bigint; v_partition_type text; v_retention bigint; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_row record; v_row_max_id record; v_step_id bigint; BEGIN /* * Function to drop child tables from an id-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman drop_partition_id')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_id already running.'; RETURN 0; END IF; IF p_retention IS NULL THEN SELECT partition_interval::bigint , partition_type , control , retention::bigint , retention_keep_table , retention_keep_index , retention_schema , jobmon INTO v_partition_interval , v_partition_type , v_control , v_retention , v_retention_keep_table , v_retention_keep_index , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND retention IS NOT NULL; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE -- Allow override of configuration options SELECT partition_interval::bigint , partition_type , control , retention_keep_table , retention_keep_index , retention_schema , jobmon INTO v_partition_interval , v_partition_type , v_control , v_retention_keep_table , v_retention_keep_index , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; v_retention := p_retention; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'id' THEN RAISE EXCEPTION 'Data type of control column in given partition set is not an integer type'; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%I) FROM %I.%I', v_control, v_row_max_id.partition_schemaname, v_row_max_id.partition_tablename) INTO v_max; IF v_max IS NOT NULL THEN EXIT; END IF; END LOOP; -- Loop through child tables of the given parent -- Must go in ascending order to avoid dropping what may be the "last" partition in the set after dropping tables that match retention period FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'ASC') LOOP SELECT child_start_id INTO v_partition_id FROM @extschema@.show_partition_info(v_row.partition_schemaname||'.'||v_row.partition_tablename , v_partition_interval::text , p_parent_table); -- Add one interval since partition names contain the start of the constraint period IF v_retention <= (v_max - (v_partition_id + v_partition_interval)) THEN -- Do not allow final partition to be dropped SELECT count(*) INTO v_count FROM @extschema@.show_partitions(p_parent_table); IF v_count = 1 THEN RAISE WARNING 'Attempt to drop final partition in partition set % as part of retention policy. Advise reviewing retention policy and/or data entry into the partition set.', p_parent_table; CONTINUE; END IF; -- Only create a jobmon entry if there's actual retention work done IF v_jobmon_schema IS NOT NULL AND v_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN DROP ID PARTITION: %s', p_parent_table)); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Detach/Uninherit table %s.%s from %s', v_row.partition_schemaname, v_row.partition_tablename, p_parent_table)); END IF; IF v_retention_keep_table = true THEN -- No need to detach partition before dropping since it's going away anyway -- Avoids issue of FKs not allowing detachment (Github Issue #294). IF v_partition_type = 'native' THEN EXECUTE format('ALTER TABLE %I.%I DETACH PARTITION %I.%I' , v_parent_schema , v_parent_tablename , v_row.partition_schemaname , v_row.partition_tablename); ELSE EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_row.partition_schemaname , v_row.partition_tablename , v_parent_schema , v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop table %s.%s', v_row.partition_schemaname, v_row.partition_tablename)); END IF; EXECUTE format('DROP TABLE %I.%I CASCADE', v_row.partition_schemaname, v_row.partition_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN IF v_partition_type = 'partman' OR ( v_partition_type = 'native' AND current_setting('server_version_num')::int < 110000) THEN -- Cannot drop child indexes on native partition sets in PG11+ FOR v_index IN WITH child_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE c1.relname = v_row.partition_tablename::name AND n1.nspname = v_row.partition_schema::name ) SELECT c.relname as name , con.conname FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid LEFT JOIN pg_catalog.pg_constraint con ON i.indexrelid = con.conindid JOIN child_info ON i.indrelid = child_info.oid LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop index %s from %s.%s' , v_index.name , v_row.partition_schemaname , v_row.partition_tablename)); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I DROP CONSTRAINT %I', v_row.partition_schemaname, v_row.partition_tablename, v_index.conname); ELSE EXECUTE format('DROP INDEX %I.%I', v_row.partition_schemaname, v_index.name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; -- end native/11 check END IF; -- end v_retention_keep_index IF ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Moving table %s.%s to schema %s' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema)); END IF; EXECUTE format('ALTER TABLE %I.%I SET SCHEMA %I' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if -- If child table is a subpartition, remove it from part_config & part_config_sub (should cascade due to FK) DELETE FROM @extschema@.part_config WHERE parent_table = v_row.partition_schemaname ||'.'||v_row.partition_tablename; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', format('%s partitions dropped.', v_drop_count)); 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_drop_count; 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 DROP ID PARTITION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE OR REPLACE FUNCTION @extschema@.partition_data_id(p_parent_table text , p_batch_count int DEFAULT 1 , p_batch_interval bigint DEFAULT NULL , p_lock_wait numeric DEFAULT 0 , p_order text DEFAULT 'ASC' , p_analyze boolean DEFAULT true , p_source_table text DEFAULT NULL) RETURNS bigint LANGUAGE plpgsql AS $$ DECLARE v_control text; v_control_type text; v_current_partition_name text; v_default_exists boolean; v_default_schemaname text; v_default_tablename text; v_epoch text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_id bigint; v_min_partition_id bigint; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_tablename text; v_partition_interval bigint; v_partition_id bigint[]; v_partition_type text; v_rowcount bigint; v_source_schemaname text; v_source_tablename text; v_sql text; v_start_control bigint; v_total_rows bigint := 0; BEGIN /* * Populate the child table(s) of an id-based partition set with old data from the original parent */ SELECT partition_interval::bigint , partition_type , control , epoch INTO v_partition_interval , v_partition_type , v_control , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: No entry in part_config found for given table: %', p_parent_table; END IF; SELECT schemaname, tablename INTO v_source_schemaname, v_source_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- Preserve real parent tablename for use below v_parent_tablename := v_source_tablename; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_source_schemaname, v_source_tablename, v_control); IF v_control_type <> 'id' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN RAISE EXCEPTION 'Control column for given partition set is not id/serial based or epoch flag is set for time-based partitioning.'; END IF; IF p_source_table IS NOT NULL THEN -- Set source table to user given source table instead of parent table v_source_schemaname := NULL; v_source_tablename := NULL; SELECT schemaname, tablename INTO v_source_schemaname, v_source_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_source_table, '.', 1)::name AND tablename = split_part(p_source_table, '.', 2)::name; IF v_source_tablename IS NULL THEN RAISE EXCEPTION 'Given source table does not exist in system catalogs: %', p_source_table; END IF; ELSIF v_partition_type = 'native' AND current_setting('server_version_num')::int >= 110000 THEN IF p_batch_interval IS NOT NULL AND p_batch_interval != v_partition_interval THEN -- This is true because all data for a given child table must be moved out of the default partition before the child table can be created. -- So cannot create the child table when only some of the data has been moved out of the default partition. RAISE EXCEPTION 'Custom intervals are not allowed when moving data out of the DEFAULT partition in a native set. Please leave p_interval/p_batch_interval parameters unset or NULL to allow use of partition set''s default partitioning interval.'; END IF; -- Set source table to default table if PG11+, p_source_table is not set, and it exists -- Otherwise just return with a DEBUG that no data source exists v_sql := format('SELECT n.nspname::text, c.relname::text FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = ''%I.%I''::regclass AND pg_get_expr(relpartbound, c.oid) = ''DEFAULT''' , v_source_schemaname , v_source_tablename); EXECUTE v_sql INTO v_default_schemaname, v_default_tablename; IF v_default_tablename IS NOT NULL THEN v_source_schemaname := v_default_schemaname; v_source_tablename := v_default_tablename; v_default_exists := true; EXECUTE format ('CREATE TEMP TABLE IF NOT EXISTS partman_temp_data_storage (LIKE %I.%I INCLUDING INDEXES) ON COMMIT DROP', v_source_schemaname, v_source_tablename); ELSE RAISE DEBUG 'No default table found when partition_data_id() was called'; RETURN v_total_rows; END IF; END IF; 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'); IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; FOR i IN 1..p_batch_count LOOP IF p_order = 'ASC' THEN EXECUTE format('SELECT min(%I) FROM ONLY %I.%I', v_control, v_source_schemaname, v_source_tablename) INTO v_start_control; IF v_start_control IS NULL THEN EXIT; END IF; v_min_partition_id = v_start_control - (v_start_control % v_partition_interval); v_partition_id := ARRAY[v_min_partition_id]; -- Check if custom batch interval overflows current partition maximum IF (v_start_control + p_batch_interval) >= (v_min_partition_id + v_partition_interval) THEN v_max_partition_id := v_min_partition_id + v_partition_interval; ELSE v_max_partition_id := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_control, v_source_schemaname, v_source_tablename) INTO v_start_control; IF v_start_control IS NULL THEN EXIT; END IF; v_min_partition_id = v_start_control - (v_start_control % v_partition_interval); -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_id := v_min_partition_id + v_partition_interval; v_partition_id := ARRAY[v_min_partition_id]; -- Make sure minimum doesn't underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_id THEN v_min_partition_id = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN v_sql := format('SELECT * FROM ONLY %I.%I WHERE %I >= %s AND %I < %s FOR UPDATE NOWAIT' , v_source_schemaname , v_source_tablename , v_control , v_min_partition_id , v_control , v_max_partition_id); EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; v_current_partition_name := @extschema@.check_name_length(COALESCE(v_parent_tablename), v_min_partition_id::text, TRUE); IF v_default_exists THEN -- Child tables cannot be created in native partitioning if data that belongs to it exists in the default -- Have to move data out to temporary location, create child table, then move it back -- Temp table created above to avoid excessive temp creation in loop EXECUTE format('WITH partition_data AS ( DELETE FROM %1$I.%2$I WHERE %3$I >= %4$s AND %3$I < %5$s RETURNING *) INSERT INTO partman_temp_data_storage SELECT * FROM partition_data' , v_source_schemaname , v_source_tablename , v_control , v_min_partition_id , v_max_partition_id); PERFORM @extschema@.create_partition_id(p_parent_table, v_partition_id, p_analyze); EXECUTE format('WITH partition_data AS ( DELETE FROM partman_temp_data_storage RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_source_schemaname , v_current_partition_name); ELSE PERFORM @extschema@.create_partition_id(p_parent_table, v_partition_id, p_analyze); EXECUTE format('WITH partition_data AS ( DELETE FROM ONLY %1$I.%2$I WHERE %3$I >= %4$s AND %3$I < %5$s RETURNING *) INSERT INTO %1$I.%6$I SELECT * FROM partition_data' , v_source_schemaname , v_source_tablename , v_control , v_min_partition_id , v_max_partition_id , v_current_partition_name); END IF; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; IF v_partition_type = 'partman' THEN PERFORM @extschema@.create_function_id(p_parent_table); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_total_rows; END $$; CREATE OR REPLACE FUNCTION @extschema@.partition_data_time( p_parent_table text , p_batch_count int DEFAULT 1 , p_batch_interval interval DEFAULT NULL , p_lock_wait numeric DEFAULT 0 , p_order text DEFAULT 'ASC' , p_analyze boolean DEFAULT true , p_source_table text DEFAULT NULL) RETURNS bigint LANGUAGE plpgsql AS $$ DECLARE v_control text; v_control_type text; v_datetime_string text; v_current_partition_name text; v_default_exists boolean; v_default_schemaname text; v_default_tablename text; v_epoch text; v_last_partition text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_timestamp timestamptz; v_min_partition_timestamp timestamptz; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_tablename text; v_parent_tablename_real text; v_partition_expression text; v_partition_interval interval; v_partition_suffix text; v_partition_timestamp timestamptz[]; v_partition_type text; v_source_schemaname text; v_source_tablename text; v_rowcount bigint; v_sql text; v_start_control timestamptz; v_total_rows bigint := 0; BEGIN /* * Populate the child table(s) of a time-based partition set with old data from the original parent */ SELECT partition_type , partition_interval::interval , control , datetime_string , epoch INTO v_partition_type , v_partition_interval , v_control , v_datetime_string , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: No entry in part_config found for given table: %', p_parent_table; END IF; SELECT schemaname, tablename INTO v_source_schemaname, v_source_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- Preserve real parent tablename for use below v_parent_tablename := v_source_tablename; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_source_schemaname, v_source_tablename, v_control); IF v_control_type <> 'time' THEN IF (v_control_type = 'id' AND v_epoch = 'none') OR v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run on partition set without time based control column or epoch flag set with an id column. Found control: %, epoch: %', v_control_type, v_epoch; END IF; END IF; -- Replace the parent variables with the source variables if using source table for child table data IF p_source_table IS NOT NULL THEN -- Set source table to user given source table instead of parent table v_source_schemaname := NULL; v_source_tablename := NULL; SELECT schemaname, tablename INTO v_source_schemaname, v_source_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_source_table, '.', 1)::name AND tablename = split_part(p_source_table, '.', 2)::name; IF v_source_tablename IS NULL THEN RAISE EXCEPTION 'Given source table does not exist in system catalogs: %', p_source_table; END IF; ELSIF v_partition_type = 'native' AND current_setting('server_version_num')::int >= 110000 THEN IF p_batch_interval IS NOT NULL AND p_batch_interval != v_partition_interval THEN -- This is true because all data for a given child table must be moved out of the default partition before the child table can be created. -- So cannot create the child table when only some of the data has been moved out of the default partition. RAISE EXCEPTION 'Custom intervals are not allowed when moving data out of the DEFAULT partition in a native set. Please leave p_interval/p_batch_interval parameters unset or NULL to allow use of partition set''s default partitioning interval.'; END IF; -- Set source table to default table if PG11+, p_source_table is not set, and it exists -- Otherwise just return with a DEBUG that no data source exists v_sql := format('SELECT n.nspname::text, c.relname::text FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = ''%I.%I''::regclass AND pg_get_expr(relpartbound, c.oid) = ''DEFAULT''' , v_source_schemaname , v_source_tablename); EXECUTE v_sql INTO v_default_schemaname, v_default_tablename; IF v_default_tablename IS NOT NULL THEN v_source_schemaname := v_default_schemaname; v_source_tablename := v_default_tablename; v_default_exists := true; EXECUTE format ('CREATE TEMP TABLE IF NOT EXISTS partman_temp_data_storage (LIKE %I.%I INCLUDING INDEXES) ON COMMIT DROP', v_source_schemaname, v_source_tablename); ELSE RAISE DEBUG 'No default table found when partition_data_id() was called'; RETURN v_total_rows; END IF; END IF; 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'); IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) END; FOR i IN 1..p_batch_count LOOP IF p_order = 'ASC' THEN EXECUTE format('SELECT min(%s) FROM ONLY %I.%I', v_partition_expression, v_source_schemaname, v_source_tablename) INTO v_start_control; ELSIF p_order = 'DESC' THEN EXECUTE format('SELECT max(%s) FROM ONLY %I.%I', v_partition_expression, v_source_schemaname, v_source_tablename) INTO v_start_control; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; IF v_start_control IS NULL THEN EXIT; END IF; IF v_partition_type = 'partman' THEN CASE WHEN v_partition_interval = '15 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '15min'::interval * floor(date_part('minute', v_start_control) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '30min'::interval * floor(date_part('minute', v_start_control) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control); WHEN v_partition_interval = '1 day' THEN v_min_partition_timestamp := date_trunc('day', v_start_control); WHEN v_partition_interval = '1 week' THEN v_min_partition_timestamp := date_trunc('week', v_start_control); WHEN v_partition_interval = '1 month' THEN v_min_partition_timestamp := date_trunc('month', v_start_control); WHEN v_partition_interval = '3 months' THEN v_min_partition_timestamp := date_trunc('quarter', v_start_control); WHEN v_partition_interval = '1 year' THEN v_min_partition_timestamp := date_trunc('year', v_start_control); END CASE; ELSIF v_partition_type IN ('time-custom', 'native') THEN SELECT child_start_time INTO v_min_partition_timestamp FROM @extschema@.show_partition_info(v_source_schemaname||'.'||v_last_partition , v_partition_interval::text , p_parent_table); v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; LOOP IF v_start_control >= v_min_partition_timestamp AND v_start_control < v_max_partition_timestamp THEN EXIT; ELSE BEGIN IF v_start_control >= v_max_partition_timestamp THEN -- Keep going forward in time, checking if child partition time interval encompasses the current v_start_control value v_min_partition_timestamp := v_max_partition_timestamp; v_max_partition_timestamp := v_max_partition_timestamp + v_partition_interval; ELSE -- Keep going backwards in time, checking if child partition time interval encompasses the current v_start_control value v_max_partition_timestamp := v_min_partition_timestamp; v_min_partition_timestamp := v_min_partition_timestamp - v_partition_interval; END IF; EXCEPTION WHEN datetime_field_overflow THEN RAISE EXCEPTION 'Attempted partition time interval is outside PostgreSQL''s supported time range. Unable to create partition with interval before timestamp % ', v_min_partition_timestamp; END; END IF; END LOOP; END IF; v_partition_timestamp := ARRAY[v_min_partition_timestamp]; IF p_order = 'ASC' THEN -- Ensure batch interval given as parameter doesn't cause maximum to overflow the current partition maximum IF (v_start_control + p_batch_interval) >= (v_min_partition_timestamp + v_partition_interval) THEN v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; ELSE v_max_partition_timestamp := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; -- Ensure batch interval given as parameter doesn't cause minimum to underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_timestamp THEN v_min_partition_timestamp = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM ONLY %I.%I WHERE %s >= %L AND %3$s < %5$L FOR UPDATE NOWAIT' , v_source_schemaname , v_source_tablename , v_partition_expression , v_min_partition_timestamp , v_max_partition_timestamp); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; -- This suffix generation code is in create_partition_time() as well v_partition_suffix := to_char(v_min_partition_timestamp, v_datetime_string); v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); IF v_default_exists THEN -- Child tables cannot be created in native partitioning if data that belongs to it exists in the default -- Have to move data out to temporary location, create child table, then move it back -- Temp table created above to avoid excessive temp creation in loop EXECUTE format('WITH partition_data AS ( DELETE FROM %1$I.%2$I WHERE %3$I >= %4$L AND %3$I < %5$L RETURNING *) INSERT INTO partman_temp_data_storage SELECT * FROM partition_data' , v_source_schemaname , v_source_tablename , v_control , v_min_partition_timestamp , v_max_partition_timestamp); PERFORM @extschema@.create_partition_time(p_parent_table, v_partition_timestamp, p_analyze); EXECUTE format('WITH partition_data AS ( DELETE FROM partman_temp_data_storage RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_source_schemaname , v_current_partition_name); ELSE PERFORM @extschema@.create_partition_time(p_parent_table, v_partition_timestamp, p_analyze); EXECUTE format('WITH partition_data AS ( DELETE FROM ONLY %I.%I WHERE %s >= %L AND %3$s < %5$L RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_source_schemaname , v_source_tablename , v_partition_expression , v_min_partition_timestamp , v_max_partition_timestamp , v_source_schemaname , v_current_partition_name); END IF; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; IF v_partition_type IN ('partman', 'time-custom') THEN PERFORM @extschema@.create_function_time(p_parent_table); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_total_rows; END $$; pg_partman-4.6.0/updates/pg_partman--4.3.0--4.3.1.sql000066400000000000000000002702551412761706100214420ustar00rootroot00000000000000-- Add support for inheriting GENERATED columns from the parent table to the child tables. Thanks to @Dishwasha on Github for reporting the issue! (Github PR #302) -- Fix handling of data existing in the default partition with time-based partitioning when that data is further ahead of any data that exists in non-default children. New child tables may not have been created as expected. Thanks to @adrianlzt on Github for reporting the issue! (Github Issue #303) CREATE OR REPLACE FUNCTION @extschema@.create_partition_id(p_parent_table text, p_partition_ids bigint[], p_analyze boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_control_type text; v_exists text; v_grantees text[]; v_hasoids boolean; v_id bigint; v_inherit_fk boolean; v_inherit_privileges boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_grant record; v_parent_schema text; v_parent_tablename text; v_parent_tablespace text; v_partition_interval bigint; v_partition_created boolean := false; v_partition_name text; v_partition_type text; v_publications text[]; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_sub_control text; v_sub_partition_type text; v_sub_id_max bigint; v_sub_id_min bigint; v_template_table text; v_unlogged char; BEGIN /* * Function to create id partitions */ SELECT control , partition_type , partition_interval , inherit_fk , jobmon , template_table , publications , inherit_privileges INTO v_control , v_partition_type , v_partition_interval , v_inherit_fk , v_jobmon , v_template_table , v_publications , v_inherit_privileges FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT n.nspname, c.relname, t.spcname INTO v_parent_schema, v_parent_tablename, v_parent_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 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; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'id' THEN RAISE EXCEPTION 'ERROR: Given parent table is not set up for id/serial partitioning'; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'id'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; FOREACH v_id IN ARRAY p_partition_ids LOOP -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_id_min IS NOT NULL THEN IF v_id < v_sub_id_min OR v_id > v_sub_id_max THEN CONTINUE; END IF; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_id::text, TRUE); -- If child table already exists, skip creation -- Have to check pg_class because if subpartitioned, table will not be in pg_tables SELECT c.relname INTO v_exists 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_partition_name::name; IF v_exists IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_id||' to '||(v_id + v_partition_interval)-1); END IF; v_sql := 'CREATE'; -- As of PG12, the unlogged/logged status of a native parent table cannot be changed via an ALTER TABLE in order to affect its children. -- As of v4.2x, the unlogged state will be managed via the template table SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c 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; IF v_unlogged = 'u' and v_partition_type != 'native' THEN v_sql := v_sql || ' UNLOGGED'; END IF; -- Close parentheses on LIKE are below due to differing requirements of native subpartitioning -- Same INCLUDING list is used in create_parent() v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING STORAGE INCLUDING COMMENTS ' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); IF current_setting('server_version_num')::int >= 120000 THEN v_sql := v_sql || ' INCLUDING GENERATED '; END IF; SELECT sub_partition_type, sub_control INTO v_sub_partition_type, v_sub_control FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; IF v_sub_partition_type = 'native' THEN -- INCLUDING INDEXES isn't necessary for native partitioning. It isn't supported in v10 and -- for v11+ index inheritance is automatically handled when the partition is attached v_sql := v_sql || format(') PARTITION BY RANGE (%I) ', v_sub_control); ELSE v_sql := v_sql || format(' INCLUDING INDEXES) ', v_sub_control); END IF; IF current_setting('server_version_num')::int < 120000 THEN -- column removed from pgclass in pg12 SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c 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; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; END IF; IF p_debug THEN RAISE NOTICE 'create_partition_id v_sql: %', v_sql; END IF; EXECUTE v_sql; IF v_partition_type = 'native' THEN IF current_setting('server_version_num')::int >= 120000 THEN -- PG12 fixed tablespace marking on the parent of a native partition set -- Versions older than 12 handle tablespace setting via inherit_template_properties() call below IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; END IF; IF v_template_table IS NOT NULL THEN PERFORM @extschema@.inherit_template_properties(p_parent_table, v_parent_schema, v_partition_name); END IF; EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , v_id , v_id + v_partition_interval); ELSE -- non-native IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %s AND %I < %s )' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_control , v_id , v_control , v_id + v_partition_interval); EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I', v_parent_schema, v_partition_name, v_parent_schema, v_parent_tablename); -- Indexes cannot be created on the parent, so clustering cannot be used for native yet. PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); -- Foreign keys to other tables not supported on native parent tables IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; END IF; -- NOTE: Privileges not automatically inherited for native. Only do so if config flag is set IF v_partition_type != 'native' OR (v_partition_type = 'native' AND v_inherit_privileges = TRUE) THEN PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_automatic_maintenance , sub_infinite_time_partitions , sub_jobmon , sub_trigger_exception_handling , sub_template_table , sub_inherit_privileges , sub_constraint_valid FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Subpartitioning '||v_partition_name); END IF; 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_inherit_fk := %L , p_epoch := %L , p_template_table := %L , p_jobmon := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_automatic_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_template_table , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions , trigger_exception_handling = v_row.sub_trigger_exception_handling , inherit_privileges = v_row.sub_inherit_privileges , constraint_valid = v_row.sub_constraint_valid WHERE parent_table = v_parent_schema||'.'||v_partition_name; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; -- end sub partitioning LOOP -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table, p_job_id := v_job_id, p_debug := p_debug); IF v_publications IS NOT NULL THEN -- NOTE: Publications currently not supported on parent table, but are supported on the table partitions if individually assigned. PERFORM @extschema@.apply_publications(p_parent_table, v_parent_schema, v_partition_name); END IF; v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s', p_parent_table)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE OR REPLACE FUNCTION @extschema@.create_partition_time(p_parent_table text, p_partition_times timestamptz[], p_analyze boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_control_type text; v_datetime_string text; v_epoch text; v_exists smallint; v_grantees text[]; v_hasoids boolean; v_inherit_privileges boolean; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_grant record; v_parent_schema text; v_parent_tablename text; v_part_col text; v_partition_created boolean := false; v_partition_name text; v_partition_suffix text; v_parent_tablespace text; v_partition_expression text; v_partition_interval interval; v_partition_timestamp_end timestamptz; v_partition_timestamp_start timestamptz; v_publications text[]; v_quarter text; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_sub_control text; v_sub_parent text; v_sub_partition_type text; v_sub_timestamp_max timestamptz; v_sub_timestamp_min timestamptz; v_template_table text; v_trunc_value text; v_time timestamptz; v_partition_type text; v_unlogged char; v_year text; BEGIN /* * Function to create a child table in a time-based partition set */ SELECT partition_type , control , partition_interval , epoch , inherit_fk , jobmon , datetime_string , template_table , publications , inherit_privileges INTO v_partition_type , v_control , v_partition_interval , v_epoch , v_inherit_fk , v_jobmon , v_datetime_string , v_template_table , v_publications , v_inherit_privileges FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT n.nspname, c.relname, t.spcname INTO v_parent_schema, v_parent_tablename, v_parent_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 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; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'time' THEN IF (v_control_type = 'id' AND v_epoch = 'none') OR v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run on partition set without time based control column or epoch flag set with an id column. Found control: %, epoch: %', v_control_type, v_epoch; END IF; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::timestamptz, sub_max::timestamptz INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'time'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) END; IF p_debug THEN RAISE NOTICE 'create_partition_time: v_partition_expression: %', v_partition_expression; END IF; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_timestamp_start := v_time; BEGIN v_partition_timestamp_end := v_time + v_partition_interval; 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_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_time||' skipped'); CONTINUE; END; -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_timestamp_min IS NOT NULL THEN IF v_time < v_sub_timestamp_min OR v_time > v_sub_timestamp_max THEN CONTINUE; END IF; END IF; -- This suffix generation code is in partition_data_time() as well v_partition_suffix := to_char(v_time, v_datetime_string); v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); -- Check if child exists. SELECT count(*) INTO v_exists 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_partition_name::name; IF v_exists > 0 THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Creating new partition %s.%s with interval from %s to %s' , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end-'1sec'::interval)); END IF; v_sql := 'CREATE'; -- As of PG12, the unlogged/logged status of a native parent table cannot be changed via an ALTER TABLE in order to affect its children. -- As of v4.2x, the unlogged state will be managed via the template table SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c 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; IF v_unlogged = 'u' and v_partition_type != 'native' THEN v_sql := v_sql || ' UNLOGGED'; END IF; -- Close parentheses on LIKE are below due to differing requirements of native subpartitioning -- Same INCLUDING list is used in create_parent() v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING STORAGE INCLUDING COMMENTS ' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); IF current_setting('server_version_num')::int >= 120000 THEN v_sql := v_sql || ' INCLUDING GENERATED '; END IF; SELECT sub_partition_type, sub_control INTO v_sub_partition_type, v_sub_control FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; IF v_sub_partition_type = 'native' THEN -- INCLUDING INDEXES isn't necessary for native partitioning. It isn't supported in v10 and -- for v11+ index inheritance is automatically handled when the partition is attached v_sql := v_sql || format(') PARTITION BY RANGE (%I) ', v_sub_control); ELSE v_sql := v_sql || format(' INCLUDING INDEXES) ', v_sub_control); END IF; IF current_setting('server_version_num')::int < 120000 THEN -- column removed from pgclass in pg12 SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c 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; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; END IF; IF p_debug THEN RAISE NOTICE 'create_partition_time v_sql: %', v_sql; END IF; EXECUTE v_sql; IF v_partition_type = 'native' THEN IF current_setting('server_version_num')::int >= 120000 THEN -- PG12 fixed tablespace marking on the parent of a native partition set -- Versions older than 12 handle tablespace setting via inherit_template_properties() call below IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; END IF; IF v_template_table IS NOT NULL THEN PERFORM @extschema@.inherit_template_properties(p_parent_table, v_parent_schema, v_partition_name); END IF; IF v_epoch = 'none' THEN -- Attach with normal, time-based values for native constraint EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end); ELSE -- Must attach with integer based values for native constraint and epoch IF v_epoch = 'seconds' THEN EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , EXTRACT('epoch' FROM v_partition_timestamp_start) , EXTRACT('epoch' FROM v_partition_timestamp_end)); ELSIF v_epoch = 'milliseconds' THEN EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , EXTRACT('epoch' FROM v_partition_timestamp_start) * 1000 , EXTRACT('epoch' FROM v_partition_timestamp_end) * 1000); END IF; -- Create secondary, time-based constraint since native's constraint is already integer based EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%s >= %L AND %4$s < %6$L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_partition_expression , v_partition_timestamp_start , v_partition_timestamp_end); END IF; ELSE -- non-native IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; -- Non-native always gets time-based constraint EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%s >= %L AND %4$s < %6$L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_partition_expression , v_partition_timestamp_start , v_partition_timestamp_end); IF v_epoch = 'seconds' THEN -- Non-native needs secondary, integer based constraint for epoch EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_int_check' , v_control , EXTRACT('epoch' from v_partition_timestamp_start) , v_control , EXTRACT('epoch' from v_partition_timestamp_end) ); ELSIF v_epoch = 'milliseconds' THEN EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_int_check' , v_control , EXTRACT('epoch' from v_partition_timestamp_start) * 1000 , v_control , EXTRACT('epoch' from v_partition_timestamp_end) * 1000); END IF; EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); -- If custom time, set extra config options. IF v_partition_type = 'time-custom' THEN INSERT INTO @extschema@.custom_time_partitions (parent_table, child_table, partition_range) VALUES ( p_parent_table, v_parent_schema||'.'||v_partition_name, tstzrange(v_partition_timestamp_start, v_partition_timestamp_end, '[)') ); END IF; -- Indexes cannot be created on the parent, so clustering cannot be used for native yet. PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); -- Foreign keys to other tables not supported in native IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; END IF; -- end native check -- NOTE: Privileges not automatically inherited for native. Only do so if config flag is set IF v_partition_type != 'native' OR (v_partition_type = 'native' AND v_inherit_privileges = TRUE) THEN PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_automatic_maintenance , sub_infinite_time_partitions , sub_jobmon , sub_trigger_exception_handling , sub_template_table , sub_inherit_privileges , sub_constraint_valid FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Subpartitioning %s.%s', v_parent_schema, v_partition_name)); END IF; 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_inherit_fk := %L , p_epoch := %L , p_template_table := %L , p_jobmon := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_automatic_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_template_table , v_row.sub_jobmon); IF p_debug THEN RAISE NOTICE 'create_partition_time (create_parent loop): %', v_sql; END IF; EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions , trigger_exception_handling = v_row.sub_trigger_exception_handling , inherit_privileges = v_row.sub_inherit_privileges , constraint_valid = v_row.sub_constraint_valid WHERE parent_table = v_parent_schema||'.'||v_partition_name; END LOOP; -- end sub partitioning LOOP -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table, p_job_id := v_job_id, p_debug := p_debug); IF v_publications IS NOT NULL THEN -- NOTE: Publications currently not supported on parent table, but are supported on the table partitions if individually assigned. PERFORM @extschema@.apply_publications(p_parent_table, v_parent_schema, v_partition_name); END IF; v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s. Attempted intervals: %s', p_parent_table, p_partition_times)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; 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_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; 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 , p_debug boolean DEFAULT false) 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_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 ... PARITIONED 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 ) 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 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) 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); -- 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 INTO v_higher_parent_schema, v_higher_parent_table, v_higher_parent_control 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' 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 $$; CREATE OR REPLACE FUNCTION @extschema@.run_maintenance(p_parent_table text DEFAULT NULL, p_analyze boolean DEFAULT NULL, p_jobmon boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_analyze boolean; v_check_subpart int; v_control_type text; v_create_count int := 0; v_current_partition text; v_current_partition_id bigint; v_current_partition_timestamp timestamptz; v_default_tablename text; v_drop_count int := 0; v_is_default text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_created boolean; v_last_partition_id bigint; v_last_partition_timestamp timestamptz; v_max_id_parent bigint; v_max_time_default timestamptz; v_new_search_path text := '@extschema@,pg_temp'; v_next_partition_id bigint; v_next_partition_timestamp timestamptz; v_old_search_path text; v_parent_exists text; v_parent_schema text; v_parent_tablename text; v_partition_expression text; v_premade_count int; v_premake_id_max bigint; v_premake_id_min bigint; v_premake_timestamp_min timestamptz; v_premake_timestamp_max timestamptz; v_row record; v_row_max_id record; v_row_max_time record; v_row_sub record; v_step_id bigint; v_step_overflow_id bigint; v_sub_id_max bigint; v_sub_id_max_suffix bigint; v_sub_id_min bigint; v_sub_parent text; v_sub_timestamp_max timestamptz; v_sub_timestamp_max_suffix timestamptz; v_sub_timestamp_min timestamptz; v_tablename text; v_tables_list_sql text; BEGIN /* * Function to manage pre-creation of the next partitions in a set. * Also manages dropping old partitions if the retention option is set. * If p_parent_table is passed, will only run run_maintenance() on that one table (no matter what the configuration table may have set for it) * Otherwise, will run on all tables in the config table with p_automatic_maintenance() set to true. * For large partition sets, running analyze can cause maintenance to take longer than expected. Can set p_analyze to false to avoid a forced analyze run on PG versions before 11. 11+ does not analyze by default anymore. * Be aware that constraint exclusion may not work properly until an analyze on the partition set is run. */ v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman maintenance already running.'; RETURN; 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'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN RUN MAINTENANCE'); v_step_id := add_step(v_job_id, 'Running maintenance loop'); END IF; v_row := NULL; -- Ensure it's reset v_tables_list_sql := 'SELECT parent_table , partition_type , partition_interval , control , premake , undo_in_progress , sub_partition_set_full , epoch , infinite_time_partitions , retention FROM @extschema@.part_config WHERE undo_in_progress = false'; IF p_parent_table IS NULL THEN v_tables_list_sql := v_tables_list_sql || ' AND automatic_maintenance = ''on'''; ELSE v_tables_list_sql := v_tables_list_sql || format(' AND parent_table = %L', p_parent_table); END IF; FOR v_row IN EXECUTE v_tables_list_sql LOOP CONTINUE WHEN v_row.undo_in_progress; -- When sub-partitioning, retention may drop tables that were already put into the query loop values. -- Check if they still exist in part_config before continuing v_parent_exists := NULL; SELECT parent_table INTO v_parent_exists FROM @extschema@.part_config WHERE parent_table = v_row.parent_table; RAISE DEBUG 'Parent table possibly removed from part_config by retenion'; CONTINUE WHEN v_parent_exists IS NULL; -- Check for consistent data in part_config_sub table. Was unable to get this working properly as either a constraint or trigger. -- Would either delay raising an error until the next write (which I cannot predict) or disallow future edits to update a sub-partition set's configuration. -- This way at least provides a consistent way to check that I know will run. If anyone can get a working constraint/trigger, please help! SELECT sub_parent INTO v_sub_parent FROM @extschema@.part_config_sub WHERE sub_parent = v_row.parent_table; IF v_sub_parent IS NOT NULL THEN SELECT count(*) INTO v_check_subpart FROM @extschema@.check_subpart_sameconfig(v_row.parent_table); IF v_check_subpart > 1 THEN RAISE EXCEPTION 'Inconsistent data in part_config_sub table. Sub-partition tables that are themselves sub-partitions cannot have differing configuration values among their siblings. Run this query: "SELECT * FROM @extschema@.check_subpart_sameconfig(''%'');" This should only return a single row or nothing. If multiple rows are returned, results are all children of the given parent. Update the differing values to be consistent for your desired values.', v_row.sub_parent; END IF; END IF; -- Shouldn't need to analyze tables for most statistics for native sets on PG11+ by default anymore IF p_analyze IS NULL THEN IF v_row.partition_type = 'native' AND current_setting('server_version_num')::int >= 110000 THEN v_analyze := false; ELSE v_analyze := true; END IF; END IF; SELECT n.nspname, c.relname INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = split_part(v_row.parent_table, '.', 1)::name AND c.relname = split_part(v_row.parent_table, '.', 2)::name; -- Used below to see if there's any data in the parent (<=PG10) or default (PG11+) child table. IF v_row.partition_type = 'native' AND current_setting('server_version_num')::int >= 110000 THEN -- Always returns the default partition first if it exists SELECT partition_tablename INTO v_default_tablename FROM @extschema@.show_partitions(v_row.parent_table, p_include_default := true) LIMIT 1; SELECT pg_get_expr(relpartbound, v_row.parent_table::regclass) INTO v_is_default FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n on c.relnamespace = n.oid WHERE n.nspname = v_parent_schema AND c.relname = v_default_tablename; IF v_is_default != 'DEFAULT' THEN v_default_tablename := v_parent_tablename; END IF; ELSE v_default_tablename := v_parent_tablename; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_row.control); v_partition_expression := CASE WHEN v_row.epoch = 'seconds' THEN format('to_timestamp(%I)', v_row.control) WHEN v_row.epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_row.control) ELSE format('%I', v_row.control) END; IF p_debug THEN RAISE NOTICE 'run_maint: v_partition_expression: %', v_partition_expression; END IF; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LIMIT 1; IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_last_partition: %', v_row.parent_table, v_last_partition; END IF; IF v_control_type = 'time' OR (v_control_type = 'id' AND v_row.epoch <> 'none') THEN -- Run retention if needed IF v_row.retention IS NOT NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); IF v_drop_count > 0 AND v_row.partition_type <> 'native' THEN PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); END IF; END IF; IF v_row.sub_partition_set_full THEN CONTINUE; END IF; SELECT child_start_time INTO v_last_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_row.partition_interval, v_row.parent_table); -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. IF v_row.infinite_time_partitions IS TRUE THEN -- Set it to "now" so new partitions continue to be created -- For infinite_time_partitions, don't bother getting the max value in the partitions v_current_partition_timestamp = CURRENT_TIMESTAMP; ELSE FOR v_row_max_time IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%s)::text FROM %I.%I' , v_partition_expression , v_row_max_time.partition_schemaname , v_row_max_time.partition_tablename ) INTO v_current_partition_timestamp; IF v_current_partition_timestamp IS NOT NULL THEN SELECT suffix_timestamp INTO v_current_partition_timestamp FROM @extschema@.show_partition_name(v_row.parent_table, v_current_partition_timestamp::text); EXIT; END IF; END LOOP; END IF; -- end infinite time check -- Check for values in the parent/default table. If they are there and greater than all child values, use that instead -- This allows maintenance to continue working properly if there is a large gap in data insertion. Data will remain in default, but new tables will be created EXECUTE format('SELECT max(%s) FROM ONLY %I.%I', v_partition_expression, v_parent_schema, v_default_tablename) INTO v_max_time_default; IF p_debug THEN RAISE NOTICE 'run_maint: v_current_partition_timestamp: %, v_max_time_default: %', v_current_partition_timestamp, v_max_time_default; END IF; IF v_current_partition_timestamp IS NULL AND v_max_time_default IS NULL THEN -- Partition set is completely empty and infinite time partitions not set -- Nothing to do CONTINUE; END IF; IF v_current_partition_timestamp IS NULL OR (v_max_time_default > v_current_partition_timestamp) THEN SELECT suffix_timestamp INTO v_current_partition_timestamp FROM @extschema@.show_partition_name(v_row.parent_table, v_max_time_default::text); END IF; -- If this is a subpartition, determine if the last child table has been made. If so, mark it as full so future maintenance runs can skip it SELECT sub_min::timestamptz, sub_max::timestamptz INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'time'); IF v_sub_timestamp_max IS NOT NULL THEN SELECT suffix_timestamp INTO v_sub_timestamp_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_timestamp_max::text); IF v_sub_timestamp_max_suffix = v_last_partition_timestamp THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; -- Check and see how many premade partitions there are. v_premade_count = round(EXTRACT('epoch' FROM age(v_last_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); v_next_partition_timestamp := v_last_partition_timestamp; IF p_debug THEN RAISE NOTICE 'run_maint before loop: current_partition_timestamp: %, v_premade_count: %, v_sub_timestamp_min: %, v_sub_timestamp_max: %' , v_current_partition_timestamp , v_premade_count , v_sub_timestamp_min , v_sub_timestamp_max; END IF; -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed WHILE (v_premade_count < v_row.premake) LOOP IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_premade_count: %, v_next_partition_timestamp: %', v_row.parent_table, v_premade_count, v_next_partition_timestamp; END IF; IF v_next_partition_timestamp < v_sub_timestamp_min OR v_next_partition_timestamp > v_sub_timestamp_max THEN -- With subpartitioning, no need to run if the timestamp is not in the parent table's range EXIT; END IF; BEGIN v_next_partition_timestamp := v_next_partition_timestamp + v_row.partition_interval::interval; EXCEPTION WHEN datetime_field_overflow THEN v_premade_count := v_row.premake; -- do this so it can exit the premake check loop and continue in the outer for loop IF v_jobmon_schema IS NOT NULL THEN 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', format('Child partition creation skipped for parent table: %s', v_partition_time)); END IF; RAISE WARNING 'Attempted partition time interval is outside PostgreSQL''s supported time range. Child partition creation skipped for parent table %', v_row.parent_table; CONTINUE; END; v_last_partition_created := @extschema@.create_partition_time(v_row.parent_table , ARRAY[v_next_partition_timestamp] , v_analyze , p_debug := p_debug); IF v_last_partition_created THEN v_create_count := v_create_count + 1; IF v_row.partition_type <> 'native' THEN PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); END IF; END IF; v_premade_count = round(EXTRACT('epoch' FROM age(v_next_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); END LOOP; ELSIF v_control_type = 'id' THEN -- Run retention if needed IF v_row.retention IS NOT NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); IF v_drop_count > 0 AND v_row.partition_type <> 'native' THEN PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); END IF; END IF; IF v_row.sub_partition_set_full THEN CONTINUE; END IF; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%I)::text FROM %I.%I' , v_row.control , v_row_max_id.partition_schemaname , v_row_max_id.partition_tablename) INTO v_current_partition_id; IF v_current_partition_id IS NOT NULL THEN SELECT suffix_id INTO v_current_partition_id FROM @extschema@.show_partition_name(v_row.parent_table, v_current_partition_id::text); EXIT; END IF; END LOOP; -- Check for values in the parent/default table. If they are there and greater than all child values, use that instead -- This allows maintenance to continue working properly if there is a large gap in data insertion. Data will remain in parent, but new tables will be created EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_row.control, v_parent_schema, v_default_tablename) INTO v_max_id_parent; IF v_max_id_parent > v_current_partition_id THEN SELECT suffix_id INTO v_current_partition_id FROM @extschema@.show_partition_name(v_row.parent_table, v_max_id_parent::text); END IF; IF v_current_partition_id IS NULL THEN -- Partition set is completely empty. Nothing to do CONTINUE; END IF; SELECT child_start_id INTO v_last_partition_id FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_row.partition_interval, v_row.parent_table); -- Determine if this table is a child of a subpartition parent. If so, get limits to see if run_maintenance even needs to run for it. SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'id'); IF v_sub_id_max IS NOT NULL THEN SELECT suffix_id INTO v_sub_id_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_id_max::text); IF v_sub_id_max_suffix = v_last_partition_id THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; v_next_partition_id := v_last_partition_id; v_premade_count := ((v_last_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed. WHILE (v_premade_count < v_row.premake) LOOP IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_premade_count: %, v_next_partition_id: %', v_row.parent_table, v_premade_count, v_next_partition_id; END IF; IF v_next_partition_id < v_sub_id_min OR v_next_partition_id > v_sub_id_max THEN -- With subpartitioning, no need to run if the id is not in the parent table's range EXIT; END IF; v_next_partition_id := v_next_partition_id + v_row.partition_interval::bigint; v_last_partition_created := @extschema@.create_partition_id(v_row.parent_table, ARRAY[v_next_partition_id], v_analyze); IF v_last_partition_created THEN v_create_count := v_create_count + 1; IF v_row.partition_type <> 'native' THEN PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); END IF; END IF; v_premade_count := ((v_next_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); END LOOP; END IF; -- end main IF check for time or id END LOOP; -- end of creation loop IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Partition maintenance finished. %s partitions made. %s partitions dropped.', v_create_count, v_drop_count)); 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'); 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 RUN MAINTENANCE'')', v_jobmon_schema) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; pg_partman-4.6.0/updates/pg_partman--4.3.1--4.4.0.sql000066400000000000000000003777341412761706100214550ustar00rootroot00000000000000-- NEW FEATURES -- ============ -- No longer requires superuser in order to grant privileges directly to child tables (setting inherit_privileges to true or running apply_privileges() directly). Thanks to Stephen Frost for suggesting this fix. This now makes pg_partman no longer require superuser for any operation other than initial installation. -- BUG FIXES -- ========= -- Added new config option to allow maintenance to refresh logical replication subscriptions. This is to fix a shortcoming where logical replication is used to replicate partition sets between databases, but not having a way to automatically account for new tables being added on the publisher side. Set the subscription name via the part_config.subscription_refresh column by just setting the name of the subscription. For sub-partitioning, part_config_sub.sub_subscription_refresh is also available. (Github Issue #307) -- Fix partition_data_time() function (called by partition_data_proc()) to work with epoch time partitioning properly when moving data out of the default partition with native partitioning. Thanks to @sheeju on Github for reporting the issue and providing fix. (Github PR #309) -- Fixed handling of mixed case/non-standard character object names during maintenance runs with native partitioning on PG11 or greater. Thanks to @sheeju on Github for reporting the issue and providing fix. (Github PR #309) -- Fixed columns in sub_part_config that were not being kept consistent between configurations for sub-partition tables that were themselves further sub-partitioned. You may possibly have some configurations that are inconsistent due to these columns being missed during previous maintenance runs. The following fix for the consistency check should find this your next maintenance run and throw an error. -- Fixed consistency check to ensure all parent tables that are part of the same sub-partition set have the same configuration in the sub_part_config table. If you encounter an error about inconsistent data in sub-partition configs after updating to this version, please carefully read the error message that is produced on how to fix it. -- Fixed broken error message from maintenance runs (record "v_row" has no field "sub_parent") when the above consistency check tried to produce an error message to indicate that the sub-partition configurations had an issue. ALTER TABLE @extschema@.part_config ADD COLUMN subscription_refresh text; ALTER TABLE @extschema@.part_config_sub ADD COLUMN sub_subscription_refresh text; CREATE TEMP TABLE partman_preserve_privs_temp (statement text); INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.check_subpart_sameconfig(text) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'check_subpart_sameconfig'; DROP FUNCTION @extschema@.check_subpart_sameconfig(text); CREATE OR REPLACE VIEW @extschema@.table_privs AS SELECT u_grantor.rolname AS grantor, grantee.rolname AS grantee, nc.nspname AS table_schema, c.relname AS table_name, c.prtype AS privilege_type FROM ( SELECT oid, relname, relnamespace, relkind, relowner, (aclexplode(coalesce(relacl, acldefault('r', relowner)))).* FROM pg_class ) AS c (oid, relname, relnamespace, relkind, relowner, grantor, grantee, prtype, grantable), pg_namespace nc, pg_roles u_grantor, ( SELECT oid, rolname FROM pg_roles UNION ALL SELECT 0::oid, 'PUBLIC' ) AS grantee (oid, rolname) WHERE c.relnamespace = nc.oid AND c.relkind IN ('r', 'v', 'p') AND c.grantee = grantee.oid AND c.grantor = u_grantor.oid AND c.prtype IN ('INSERT', 'SELECT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER') AND (pg_has_role(u_grantor.oid, 'USAGE') OR pg_has_role(grantee.oid, 'USAGE') OR grantee.rolname = 'PUBLIC' ); CREATE OR REPLACE FUNCTION @extschema@.partition_data_time( p_parent_table text , p_batch_count int DEFAULT 1 , p_batch_interval interval DEFAULT NULL , p_lock_wait numeric DEFAULT 0 , p_order text DEFAULT 'ASC' , p_analyze boolean DEFAULT true , p_source_table text DEFAULT NULL) RETURNS bigint LANGUAGE plpgsql AS $$ DECLARE v_control text; v_control_type text; v_datetime_string text; v_current_partition_name text; v_default_exists boolean; v_default_schemaname text; v_default_tablename text; v_epoch text; v_last_partition text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_timestamp timestamptz; v_min_partition_timestamp timestamptz; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_tablename text; v_parent_tablename_real text; v_partition_expression text; v_partition_interval interval; v_partition_suffix text; v_partition_timestamp timestamptz[]; v_partition_type text; v_source_schemaname text; v_source_tablename text; v_rowcount bigint; v_sql text; v_start_control timestamptz; v_total_rows bigint := 0; BEGIN /* * Populate the child table(s) of a time-based partition set with old data from the original parent */ SELECT partition_type , partition_interval::interval , control , datetime_string , epoch INTO v_partition_type , v_partition_interval , v_control , v_datetime_string , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: No entry in part_config found for given table: %', p_parent_table; END IF; SELECT schemaname, tablename INTO v_source_schemaname, v_source_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- Preserve real parent tablename for use below v_parent_tablename := v_source_tablename; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_source_schemaname, v_source_tablename, v_control); IF v_control_type <> 'time' THEN IF (v_control_type = 'id' AND v_epoch = 'none') OR v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run on partition set without time based control column or epoch flag set with an id column. Found control: %, epoch: %', v_control_type, v_epoch; END IF; END IF; -- Replace the parent variables with the source variables if using source table for child table data IF p_source_table IS NOT NULL THEN -- Set source table to user given source table instead of parent table v_source_schemaname := NULL; v_source_tablename := NULL; SELECT schemaname, tablename INTO v_source_schemaname, v_source_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_source_table, '.', 1)::name AND tablename = split_part(p_source_table, '.', 2)::name; IF v_source_tablename IS NULL THEN RAISE EXCEPTION 'Given source table does not exist in system catalogs: %', p_source_table; END IF; ELSIF v_partition_type = 'native' AND current_setting('server_version_num')::int >= 110000 THEN IF p_batch_interval IS NOT NULL AND p_batch_interval != v_partition_interval THEN -- This is true because all data for a given child table must be moved out of the default partition before the child table can be created. -- So cannot create the child table when only some of the data has been moved out of the default partition. RAISE EXCEPTION 'Custom intervals are not allowed when moving data out of the DEFAULT partition in a native set. Please leave p_interval/p_batch_interval parameters unset or NULL to allow use of partition set''s default partitioning interval.'; END IF; -- Set source table to default table if PG11+, p_source_table is not set, and it exists -- Otherwise just return with a DEBUG that no data source exists v_sql := format('SELECT n.nspname::text, c.relname::text FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = ''%I.%I''::regclass AND pg_get_expr(relpartbound, c.oid) = ''DEFAULT''' , v_source_schemaname , v_source_tablename); EXECUTE v_sql INTO v_default_schemaname, v_default_tablename; IF v_default_tablename IS NOT NULL THEN v_source_schemaname := v_default_schemaname; v_source_tablename := v_default_tablename; v_default_exists := true; EXECUTE format ('CREATE TEMP TABLE IF NOT EXISTS partman_temp_data_storage (LIKE %I.%I INCLUDING INDEXES) ON COMMIT DROP', v_source_schemaname, v_source_tablename); ELSE RAISE DEBUG 'No default table found when partition_data_id() was called'; RETURN v_total_rows; END IF; END IF; 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'); IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) END; FOR i IN 1..p_batch_count LOOP IF p_order = 'ASC' THEN EXECUTE format('SELECT min(%s) FROM ONLY %I.%I', v_partition_expression, v_source_schemaname, v_source_tablename) INTO v_start_control; ELSIF p_order = 'DESC' THEN EXECUTE format('SELECT max(%s) FROM ONLY %I.%I', v_partition_expression, v_source_schemaname, v_source_tablename) INTO v_start_control; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; IF v_start_control IS NULL THEN EXIT; END IF; IF v_partition_type = 'partman' THEN CASE WHEN v_partition_interval = '15 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '15min'::interval * floor(date_part('minute', v_start_control) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '30min'::interval * floor(date_part('minute', v_start_control) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control); WHEN v_partition_interval = '1 day' THEN v_min_partition_timestamp := date_trunc('day', v_start_control); WHEN v_partition_interval = '1 week' THEN v_min_partition_timestamp := date_trunc('week', v_start_control); WHEN v_partition_interval = '1 month' THEN v_min_partition_timestamp := date_trunc('month', v_start_control); WHEN v_partition_interval = '3 months' THEN v_min_partition_timestamp := date_trunc('quarter', v_start_control); WHEN v_partition_interval = '1 year' THEN v_min_partition_timestamp := date_trunc('year', v_start_control); END CASE; ELSIF v_partition_type IN ('time-custom', 'native') THEN SELECT child_start_time INTO v_min_partition_timestamp FROM @extschema@.show_partition_info(v_source_schemaname||'.'||v_last_partition , v_partition_interval::text , p_parent_table); v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; LOOP IF v_start_control >= v_min_partition_timestamp AND v_start_control < v_max_partition_timestamp THEN EXIT; ELSE BEGIN IF v_start_control >= v_max_partition_timestamp THEN -- Keep going forward in time, checking if child partition time interval encompasses the current v_start_control value v_min_partition_timestamp := v_max_partition_timestamp; v_max_partition_timestamp := v_max_partition_timestamp + v_partition_interval; ELSE -- Keep going backwards in time, checking if child partition time interval encompasses the current v_start_control value v_max_partition_timestamp := v_min_partition_timestamp; v_min_partition_timestamp := v_min_partition_timestamp - v_partition_interval; END IF; EXCEPTION WHEN datetime_field_overflow THEN RAISE EXCEPTION 'Attempted partition time interval is outside PostgreSQL''s supported time range. Unable to create partition with interval before timestamp % ', v_min_partition_timestamp; END; END IF; END LOOP; END IF; v_partition_timestamp := ARRAY[v_min_partition_timestamp]; IF p_order = 'ASC' THEN -- Ensure batch interval given as parameter doesn't cause maximum to overflow the current partition maximum IF (v_start_control + p_batch_interval) >= (v_min_partition_timestamp + v_partition_interval) THEN v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; ELSE v_max_partition_timestamp := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; -- Ensure batch interval given as parameter doesn't cause minimum to underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_timestamp THEN v_min_partition_timestamp = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM ONLY %I.%I WHERE %s >= %L AND %3$s < %5$L FOR UPDATE NOWAIT' , v_source_schemaname , v_source_tablename , v_partition_expression , v_min_partition_timestamp , v_max_partition_timestamp); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; -- This suffix generation code is in create_partition_time() as well v_partition_suffix := to_char(v_min_partition_timestamp, v_datetime_string); v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); IF v_default_exists THEN -- Child tables cannot be created in native partitioning if data that belongs to it exists in the default -- Have to move data out to temporary location, create child table, then move it back -- Temp table created above to avoid excessive temp creation in loop EXECUTE format('WITH partition_data AS ( DELETE FROM %1$I.%2$I WHERE %3$s >= %4$L AND %3$s < %5$L RETURNING *) INSERT INTO partman_temp_data_storage SELECT * FROM partition_data' , v_source_schemaname , v_source_tablename , v_partition_expression , v_min_partition_timestamp , v_max_partition_timestamp); PERFORM @extschema@.create_partition_time(p_parent_table, v_partition_timestamp, p_analyze); EXECUTE format('WITH partition_data AS ( DELETE FROM partman_temp_data_storage RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_source_schemaname , v_current_partition_name); ELSE PERFORM @extschema@.create_partition_time(p_parent_table, v_partition_timestamp, p_analyze); EXECUTE format('WITH partition_data AS ( DELETE FROM ONLY %I.%I WHERE %s >= %L AND %3$s < %5$L RETURNING *) INSERT INTO %I.%I SELECT * FROM partition_data' , v_source_schemaname , v_source_tablename , v_partition_expression , v_min_partition_timestamp , v_max_partition_timestamp , v_source_schemaname , v_current_partition_name); END IF; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; IF v_partition_type IN ('partman', 'time-custom') THEN PERFORM @extschema@.create_function_time(p_parent_table); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_total_rows; END $$; CREATE OR REPLACE FUNCTION @extschema@.run_maintenance(p_parent_table text DEFAULT NULL, p_analyze boolean DEFAULT NULL, p_jobmon boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_analyze boolean; v_check_subpart int; v_control_type text; v_create_count int := 0; v_current_partition text; v_current_partition_id bigint; v_current_partition_timestamp timestamptz; v_default_tablename text; v_drop_count int := 0; v_is_default text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_created boolean; v_last_partition_id bigint; v_last_partition_timestamp timestamptz; v_max_id_parent bigint; v_max_time_default timestamptz; v_new_search_path text := '@extschema@,pg_temp'; v_next_partition_id bigint; v_next_partition_timestamp timestamptz; v_old_search_path text; v_parent_exists text; v_parent_oid oid; v_parent_schema text; v_parent_tablename text; v_partition_expression text; v_premade_count int; v_premake_id_max bigint; v_premake_id_min bigint; v_premake_timestamp_min timestamptz; v_premake_timestamp_max timestamptz; v_row record; v_row_max_id record; v_row_max_time record; v_row_sub record; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_sub_id_max bigint; v_sub_id_max_suffix bigint; v_sub_id_min bigint; v_sub_parent text; v_sub_refresh_done text[]; v_sub_timestamp_max timestamptz; v_sub_timestamp_max_suffix timestamptz; v_sub_timestamp_min timestamptz; v_tablename text; v_tables_list_sql text; BEGIN /* * Function to manage pre-creation of the next partitions in a set. * Also manages dropping old partitions if the retention option is set. * If p_parent_table is passed, will only run run_maintenance() on that one table (no matter what the configuration table may have set for it) * Otherwise, will run on all tables in the config table with p_automatic_maintenance() set to true. * For large partition sets, running analyze can cause maintenance to take longer than expected. Can set p_analyze to false to avoid a forced analyze run on PG versions before 11. 11+ does not analyze by default anymore. * Be aware that constraint exclusion may not work properly until an analyze on the partition set is run. */ v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman maintenance already running.'; RETURN; 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'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN RUN MAINTENANCE'); v_step_id := add_step(v_job_id, 'Running maintenance loop'); END IF; v_tables_list_sql := 'SELECT parent_table , partition_type , partition_interval , control , premake , undo_in_progress , sub_partition_set_full , epoch , infinite_time_partitions , retention , subscription_refresh FROM @extschema@.part_config WHERE undo_in_progress = false'; IF p_parent_table IS NULL THEN v_tables_list_sql := v_tables_list_sql || ' AND automatic_maintenance = ''on'''; ELSE v_tables_list_sql := v_tables_list_sql || format(' AND parent_table = %L', p_parent_table); END IF; FOR v_row IN EXECUTE v_tables_list_sql LOOP CONTINUE WHEN v_row.undo_in_progress; -- When sub-partitioning, retention may drop tables that were already put into the query loop values. -- Check if they still exist in part_config before continuing v_parent_exists := NULL; SELECT parent_table INTO v_parent_exists FROM @extschema@.part_config WHERE parent_table = v_row.parent_table; RAISE DEBUG 'Parent table possibly removed from part_config by retenion'; CONTINUE WHEN v_parent_exists IS NULL; -- Check for consistent data in part_config_sub table. Was unable to get this working properly as either a constraint or trigger. -- Would either delay raising an error until the next write (which I cannot predict) or disallow future edits to update a sub-partition set's configuration. -- This way at least provides a consistent way to check that I know will run. If anyone can get a working constraint/trigger, please help! SELECT sub_parent INTO v_sub_parent FROM @extschema@.part_config_sub WHERE sub_parent = v_row.parent_table; IF v_sub_parent IS NOT NULL THEN SELECT count(*) INTO v_check_subpart FROM @extschema@.check_subpart_sameconfig(v_row.parent_table); IF v_check_subpart > 1 THEN RAISE EXCEPTION 'Inconsistent data in part_config_sub table. Sub-partition tables that are themselves sub-partitions cannot have differing configuration values among their siblings. Run this query: "SELECT * FROM @extschema@.check_subpart_sameconfig(''%'');" This should only return a single row or nothing. If multiple rows are returned, the results are differing configurations in the part_config_sub table for children of the given parent. Determine the child tables of the given parent and look up their entries based on the "part_config_sub.sub_parent" column. Update the differing values to be consistent for your desired values.', v_row.parent_table; END IF; END IF; -- Shouldn't need to analyze tables for most statistics for native sets on PG11+ by default anymore IF p_analyze IS NULL THEN IF v_row.partition_type = 'native' AND current_setting('server_version_num')::int >= 110000 THEN v_analyze := false; ELSE v_analyze := true; END IF; END IF; SELECT n.nspname, c.relname, c.oid INTO v_parent_schema, v_parent_tablename, 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(v_row.parent_table, '.', 1)::name AND c.relname = split_part(v_row.parent_table, '.', 2)::name; -- Used below to see if there's any data in the parent (<=PG10) or default (PG11+) child table. IF v_row.partition_type = 'native' AND current_setting('server_version_num')::int >= 110000 THEN -- Always returns the default partition first if it exists SELECT partition_tablename INTO v_default_tablename FROM @extschema@.show_partitions(v_row.parent_table, p_include_default := true) LIMIT 1; SELECT pg_get_expr(relpartbound, v_parent_oid) INTO v_is_default FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n on c.relnamespace = n.oid WHERE n.nspname = v_parent_schema AND c.relname = v_default_tablename; IF v_is_default != 'DEFAULT' THEN v_default_tablename := v_parent_tablename; END IF; ELSE v_default_tablename := v_parent_tablename; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_row.control); v_partition_expression := CASE WHEN v_row.epoch = 'seconds' THEN format('to_timestamp(%I)', v_row.control) WHEN v_row.epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_row.control) ELSE format('%I', v_row.control) END; IF p_debug THEN RAISE NOTICE 'run_maint: v_partition_expression: %', v_partition_expression; END IF; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LIMIT 1; IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_last_partition: %', v_row.parent_table, v_last_partition; END IF; IF v_control_type = 'time' OR (v_control_type = 'id' AND v_row.epoch <> 'none') THEN -- Run retention if needed IF v_row.retention IS NOT NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); IF v_drop_count > 0 AND v_row.partition_type <> 'native' THEN PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); END IF; END IF; IF v_row.sub_partition_set_full THEN CONTINUE; END IF; SELECT child_start_time INTO v_last_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_row.partition_interval, v_row.parent_table); -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. IF v_row.infinite_time_partitions IS TRUE THEN -- Set it to "now" so new partitions continue to be created -- For infinite_time_partitions, don't bother getting the max value in the partitions v_current_partition_timestamp = CURRENT_TIMESTAMP; ELSE FOR v_row_max_time IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%s)::text FROM %I.%I' , v_partition_expression , v_row_max_time.partition_schemaname , v_row_max_time.partition_tablename ) INTO v_current_partition_timestamp; IF v_current_partition_timestamp IS NOT NULL THEN SELECT suffix_timestamp INTO v_current_partition_timestamp FROM @extschema@.show_partition_name(v_row.parent_table, v_current_partition_timestamp::text); EXIT; END IF; END LOOP; END IF; -- end infinite time check -- Check for values in the parent/default table. If they are there and greater than all child values, use that instead -- This allows maintenance to continue working properly if there is a large gap in data insertion. Data will remain in default, but new tables will be created EXECUTE format('SELECT max(%s) FROM ONLY %I.%I', v_partition_expression, v_parent_schema, v_default_tablename) INTO v_max_time_default; IF p_debug THEN RAISE NOTICE 'run_maint: v_current_partition_timestamp: %, v_max_time_default: %', v_current_partition_timestamp, v_max_time_default; END IF; IF v_current_partition_timestamp IS NULL AND v_max_time_default IS NULL THEN -- Partition set is completely empty and infinite time partitions not set -- Nothing to do CONTINUE; END IF; IF v_current_partition_timestamp IS NULL OR (v_max_time_default > v_current_partition_timestamp) THEN SELECT suffix_timestamp INTO v_current_partition_timestamp FROM @extschema@.show_partition_name(v_row.parent_table, v_max_time_default::text); END IF; -- If this is a subpartition, determine if the last child table has been made. If so, mark it as full so future maintenance runs can skip it SELECT sub_min::timestamptz, sub_max::timestamptz INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'time'); IF v_sub_timestamp_max IS NOT NULL THEN SELECT suffix_timestamp INTO v_sub_timestamp_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_timestamp_max::text); IF v_sub_timestamp_max_suffix = v_last_partition_timestamp THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; -- Check and see how many premade partitions there are. v_premade_count = round(EXTRACT('epoch' FROM age(v_last_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); v_next_partition_timestamp := v_last_partition_timestamp; IF p_debug THEN RAISE NOTICE 'run_maint before loop: current_partition_timestamp: %, v_premade_count: %, v_sub_timestamp_min: %, v_sub_timestamp_max: %' , v_current_partition_timestamp , v_premade_count , v_sub_timestamp_min , v_sub_timestamp_max; END IF; -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed WHILE (v_premade_count < v_row.premake) LOOP IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_premade_count: %, v_next_partition_timestamp: %', v_row.parent_table, v_premade_count, v_next_partition_timestamp; END IF; IF v_next_partition_timestamp < v_sub_timestamp_min OR v_next_partition_timestamp > v_sub_timestamp_max THEN -- With subpartitioning, no need to run if the timestamp is not in the parent table's range EXIT; END IF; BEGIN v_next_partition_timestamp := v_next_partition_timestamp + v_row.partition_interval::interval; EXCEPTION WHEN datetime_field_overflow THEN v_premade_count := v_row.premake; -- do this so it can exit the premake check loop and continue in the outer for loop IF v_jobmon_schema IS NOT NULL THEN 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', format('Child partition creation skipped for parent table: %s', v_partition_time)); END IF; RAISE WARNING 'Attempted partition time interval is outside PostgreSQL''s supported time range. Child partition creation skipped for parent table %', v_row.parent_table; CONTINUE; END; v_last_partition_created := @extschema@.create_partition_time(v_row.parent_table , ARRAY[v_next_partition_timestamp] , v_analyze , p_debug := p_debug); IF v_last_partition_created THEN v_create_count := v_create_count + 1; IF v_row.partition_type <> 'native' THEN PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); END IF; END IF; v_premade_count = round(EXTRACT('epoch' FROM age(v_next_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); END LOOP; ELSIF v_control_type = 'id' THEN -- Run retention if needed IF v_row.retention IS NOT NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); IF v_drop_count > 0 AND v_row.partition_type <> 'native' THEN PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); END IF; END IF; IF v_row.sub_partition_set_full THEN CONTINUE; END IF; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%I)::text FROM %I.%I' , v_row.control , v_row_max_id.partition_schemaname , v_row_max_id.partition_tablename) INTO v_current_partition_id; IF v_current_partition_id IS NOT NULL THEN SELECT suffix_id INTO v_current_partition_id FROM @extschema@.show_partition_name(v_row.parent_table, v_current_partition_id::text); EXIT; END IF; END LOOP; -- Check for values in the parent/default table. If they are there and greater than all child values, use that instead -- This allows maintenance to continue working properly if there is a large gap in data insertion. Data will remain in parent, but new tables will be created EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_row.control, v_parent_schema, v_default_tablename) INTO v_max_id_parent; IF v_max_id_parent > v_current_partition_id THEN SELECT suffix_id INTO v_current_partition_id FROM @extschema@.show_partition_name(v_row.parent_table, v_max_id_parent::text); END IF; IF v_current_partition_id IS NULL THEN -- Partition set is completely empty. Nothing to do CONTINUE; END IF; SELECT child_start_id INTO v_last_partition_id FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_row.partition_interval, v_row.parent_table); -- Determine if this table is a child of a subpartition parent. If so, get limits to see if run_maintenance even needs to run for it. SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'id'); IF v_sub_id_max IS NOT NULL THEN SELECT suffix_id INTO v_sub_id_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_id_max::text); IF v_sub_id_max_suffix = v_last_partition_id THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; v_next_partition_id := v_last_partition_id; v_premade_count := ((v_last_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed. WHILE (v_premade_count < v_row.premake) LOOP IF p_debug THEN RAISE NOTICE 'run_maint: parent_table: %, v_premade_count: %, v_next_partition_id: %', v_row.parent_table, v_premade_count, v_next_partition_id; END IF; IF v_next_partition_id < v_sub_id_min OR v_next_partition_id > v_sub_id_max THEN -- With subpartitioning, no need to run if the id is not in the parent table's range EXIT; END IF; v_next_partition_id := v_next_partition_id + v_row.partition_interval::bigint; v_last_partition_created := @extschema@.create_partition_id(v_row.parent_table, ARRAY[v_next_partition_id], v_analyze); IF v_last_partition_created THEN v_create_count := v_create_count + 1; IF v_row.partition_type <> 'native' THEN PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); END IF; END IF; v_premade_count := ((v_next_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); END LOOP; END IF; -- end main IF check for time or id -- Refresh subscriptions in order to catch new tables that may have been created in the publication -- Keep track of which ones have been refreshed so it doesn't needlessly run more than once -- in a single maintenance run IF v_row.subscription_refresh IS NOT NULL THEN IF v_sub_refresh_done @> ARRAY[v_row.subscription_refresh] THEN CONTINUE; ELSE v_sql = format('ALTER SUBSCRIPTION %I REFRESH PUBLICATION', v_row.subscription_refresh); RAISE DEBUG '%', v_sql; EXECUTE v_sql; PERFORM array_append(v_sub_refresh_done, v_row.subscription_refresh); END IF; END IF; END LOOP; -- end of main loop through part_config IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Partition maintenance finished. %s partitons made. %s partitions dropped.', v_create_count, v_drop_count)); 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'); 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 RUN MAINTENANCE'')', v_jobmon_schema) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; 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 , p_debug boolean DEFAULT false) 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_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 ... PARITIONED 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 INTO v_higher_parent_schema, v_higher_parent_table, v_higher_parent_control 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' 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 $$; CREATE FUNCTION @extschema@.check_subpart_sameconfig(p_parent_table text) RETURNS TABLE (sub_partition_type text , sub_control text , sub_partition_interval text , sub_constraint_cols text[] , sub_premake int , sub_optimize_trigger int , sub_optimize_constraint int , sub_epoch text , sub_inherit_fk boolean , sub_retention text , sub_retention_schema text , sub_retention_keep_table boolean , sub_retention_keep_index boolean , sub_infinite_time_partitions boolean , sub_automatic_maintenance text , sub_jobmon boolean , sub_trigger_exception_handling boolean , sub_upsert text , sub_trigger_return_null boolean , sub_template_table text , sub_inherit_privileges boolean , sub_constraint_valid boolean , sub_subscription_refresh text) LANGUAGE sql STABLE SET search_path = @extschema@,pg_temp AS $$ /* * Check for consistent data in part_config_sub table. Was unable to get this working properly as either a constraint or trigger. * Would either delay raising an error until the next write (which I cannot predict) or disallow future edits to update a sub-partition set's configuration. * This is called by run_maintainance() and at least provides a consistent way to check that I know will run. * If anyone can get a working constraint/trigger, please help! */ WITH parent_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE n1.nspname = split_part(p_parent_table, '.', 1)::name AND c1.relname = split_part(p_parent_table, '.', 2)::name ) , child_tables AS ( SELECT n.nspname||'.'||c.relname AS tablename FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN parent_info pi ON h.inhparent = pi.oid ) -- Column order here must match the RETURNS TABLE definition -- 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 a.sub_partition_type , a.sub_control , a.sub_partition_interval , a.sub_constraint_cols , a.sub_premake , a.sub_optimize_trigger , a.sub_optimize_constraint , a.sub_epoch , a.sub_inherit_fk , a.sub_retention , a.sub_retention_schema , a.sub_retention_keep_table , a.sub_retention_keep_index , a.sub_infinite_time_partitions , a.sub_automatic_maintenance , a.sub_jobmon , a.sub_trigger_exception_handling , a.sub_upsert , a.sub_trigger_return_null , a.sub_template_table , a.sub_inherit_privileges , a.sub_constraint_valid , a.sub_subscription_refresh FROM @extschema@.part_config_sub a JOIN child_tables b on a.sub_parent = b.tablename; $$; CREATE OR REPLACE FUNCTION @extschema@.create_partition_id(p_parent_table text, p_partition_ids bigint[], p_analyze boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_control_type text; v_exists text; v_grantees text[]; v_hasoids boolean; v_id bigint; v_inherit_fk boolean; v_inherit_privileges boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_grant record; v_parent_schema text; v_parent_tablename text; v_parent_tablespace text; v_partition_interval bigint; v_partition_created boolean := false; v_partition_name text; v_partition_type text; v_publications text[]; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_sub_control text; v_sub_partition_type text; v_sub_id_max bigint; v_sub_id_min bigint; v_template_table text; v_unlogged char; BEGIN /* * Function to create id partitions */ SELECT control , partition_type , partition_interval , inherit_fk , jobmon , template_table , publications , inherit_privileges INTO v_control , v_partition_type , v_partition_interval , v_inherit_fk , v_jobmon , v_template_table , v_publications , v_inherit_privileges FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT n.nspname, c.relname, t.spcname INTO v_parent_schema, v_parent_tablename, v_parent_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 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; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'id' THEN RAISE EXCEPTION 'ERROR: Given parent table is not set up for id/serial partitioning'; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'id'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; FOREACH v_id IN ARRAY p_partition_ids LOOP -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_id_min IS NOT NULL THEN IF v_id < v_sub_id_min OR v_id > v_sub_id_max THEN CONTINUE; END IF; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_id::text, TRUE); -- If child table already exists, skip creation -- Have to check pg_class because if subpartitioned, table will not be in pg_tables SELECT c.relname INTO v_exists 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_partition_name::name; IF v_exists IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_id||' to '||(v_id + v_partition_interval)-1); END IF; v_sql := 'CREATE'; -- As of PG12, the unlogged/logged status of a native parent table cannot be changed via an ALTER TABLE in order to affect its children. -- As of v4.2x, the unlogged state will be managed via the template table SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c 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; IF v_unlogged = 'u' and v_partition_type != 'native' THEN v_sql := v_sql || ' UNLOGGED'; END IF; -- Close parentheses on LIKE are below due to differing requirements of native subpartitioning -- Same INCLUDING list is used in create_parent() v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING STORAGE INCLUDING COMMENTS ' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); IF current_setting('server_version_num')::int >= 120000 THEN v_sql := v_sql || ' INCLUDING GENERATED '; END IF; SELECT sub_partition_type, sub_control INTO v_sub_partition_type, v_sub_control FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; IF v_sub_partition_type = 'native' THEN -- INCLUDING INDEXES isn't necessary for native partitioning. It isn't supported in v10 and -- for v11+ index inheritance is automatically handled when the partition is attached v_sql := v_sql || format(') PARTITION BY RANGE (%I) ', v_sub_control); ELSE v_sql := v_sql || format(' INCLUDING INDEXES) ', v_sub_control); END IF; IF current_setting('server_version_num')::int < 120000 THEN -- column removed from pgclass in pg12 SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c 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; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; END IF; IF p_debug THEN RAISE NOTICE 'create_partition_id v_sql: %', v_sql; END IF; EXECUTE v_sql; IF v_partition_type = 'native' THEN IF current_setting('server_version_num')::int >= 120000 THEN -- PG12 fixed tablespace marking on the parent of a native partition set -- Versions older than 12 handle tablespace setting via inherit_template_properties() call below IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; END IF; IF v_template_table IS NOT NULL THEN PERFORM @extschema@.inherit_template_properties(p_parent_table, v_parent_schema, v_partition_name); END IF; EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , v_id , v_id + v_partition_interval); ELSE -- non-native IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %s AND %I < %s )' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_control , v_id , v_control , v_id + v_partition_interval); EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I', v_parent_schema, v_partition_name, v_parent_schema, v_parent_tablename); -- Indexes cannot be created on the parent, so clustering cannot be used for native yet. PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); -- Foreign keys to other tables not supported on native parent tables IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; END IF; -- NOTE: Privileges not automatically inherited for native. Only do so if config flag is set IF v_partition_type != 'native' OR (v_partition_type = 'native' AND v_inherit_privileges = TRUE) THEN PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition -- 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 FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_infinite_time_partitions , sub_automatic_maintenance , 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 WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Subpartitioning '||v_partition_name); END IF; 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_inherit_fk := %L , p_epoch := %L , p_template_table := %L , p_jobmon := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_automatic_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_template_table , v_row.sub_jobmon); EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions , trigger_exception_handling = v_row.sub_trigger_exception_handling , upsert = v_row.sub_upsert , inherit_privileges = v_row.sub_inherit_privileges , trigger_return_null = v_row.sub_trigger_return_null , constraint_valid = v_row.sub_constraint_valid , subscription_refresh = v_row.sub_subscription_refresh WHERE parent_table = v_parent_schema||'.'||v_partition_name; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; -- end sub partitioning LOOP -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table, p_job_id := v_job_id, p_debug := p_debug); IF v_publications IS NOT NULL THEN -- NOTE: Publications currently not supported on parent table, but are supported on the table partitions if individually assigned. PERFORM @extschema@.apply_publications(p_parent_table, v_parent_schema, v_partition_name); END IF; v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s', p_parent_table)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE OR REPLACE FUNCTION @extschema@.create_partition_time(p_parent_table text, p_partition_times timestamptz[], p_analyze boolean DEFAULT true, p_debug boolean DEFAULT false) RETURNS boolean LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_control_type text; v_datetime_string text; v_epoch text; v_exists smallint; v_grantees text[]; v_hasoids boolean; v_inherit_privileges boolean; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_grant record; v_parent_schema text; v_parent_tablename text; v_part_col text; v_partition_created boolean := false; v_partition_name text; v_partition_suffix text; v_parent_tablespace text; v_partition_expression text; v_partition_interval interval; v_partition_timestamp_end timestamptz; v_partition_timestamp_start timestamptz; v_publications text[]; v_quarter text; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_sub_control text; v_sub_parent text; v_sub_partition_type text; v_sub_timestamp_max timestamptz; v_sub_timestamp_min timestamptz; v_template_table text; v_trunc_value text; v_time timestamptz; v_partition_type text; v_unlogged char; v_year text; BEGIN /* * Function to create a child table in a time-based partition set */ SELECT partition_type , control , partition_interval , epoch , inherit_fk , jobmon , datetime_string , template_table , publications , inherit_privileges INTO v_partition_type , v_control , v_partition_interval , v_epoch , v_inherit_fk , v_jobmon , v_datetime_string , v_template_table , v_publications , v_inherit_privileges FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT n.nspname, c.relname, t.spcname INTO v_parent_schema, v_parent_tablename, v_parent_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 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; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'time' THEN IF (v_control_type = 'id' AND v_epoch = 'none') OR v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run on partition set without time based control column or epoch flag set with an id column. Found control: %, epoch: %', v_control_type, v_epoch; END IF; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::timestamptz, sub_max::timestamptz INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'time'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) END; IF p_debug THEN RAISE NOTICE 'create_partition_time: v_partition_expression: %', v_partition_expression; END IF; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_timestamp_start := v_time; BEGIN v_partition_timestamp_end := v_time + v_partition_interval; 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_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_time||' skipped'); CONTINUE; END; -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_timestamp_min IS NOT NULL THEN IF v_time < v_sub_timestamp_min OR v_time > v_sub_timestamp_max THEN CONTINUE; END IF; END IF; -- This suffix generation code is in partition_data_time() as well v_partition_suffix := to_char(v_time, v_datetime_string); v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); -- Check if child exists. SELECT count(*) INTO v_exists 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_partition_name::name; IF v_exists > 0 THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Creating new partition %s.%s with interval from %s to %s' , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end-'1sec'::interval)); END IF; v_sql := 'CREATE'; -- As of PG12, the unlogged/logged status of a native parent table cannot be changed via an ALTER TABLE in order to affect its children. -- As of v4.2x, the unlogged state will be managed via the template table SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c 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; IF v_unlogged = 'u' and v_partition_type != 'native' THEN v_sql := v_sql || ' UNLOGGED'; END IF; -- Close parentheses on LIKE are below due to differing requirements of native subpartitioning -- Same INCLUDING list is used in create_parent() v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING STORAGE INCLUDING COMMENTS ' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); IF current_setting('server_version_num')::int >= 120000 THEN v_sql := v_sql || ' INCLUDING GENERATED '; END IF; SELECT sub_partition_type, sub_control INTO v_sub_partition_type, v_sub_control FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; IF v_sub_partition_type = 'native' THEN -- INCLUDING INDEXES isn't necessary for native partitioning. It isn't supported in v10 and -- for v11+ index inheritance is automatically handled when the partition is attached v_sql := v_sql || format(') PARTITION BY RANGE (%I) ', v_sub_control); ELSE v_sql := v_sql || format(' INCLUDING INDEXES) ', v_sub_control); END IF; IF current_setting('server_version_num')::int < 120000 THEN -- column removed from pgclass in pg12 SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c 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; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; END IF; IF p_debug THEN RAISE NOTICE 'create_partition_time v_sql: %', v_sql; END IF; EXECUTE v_sql; IF v_partition_type = 'native' THEN IF current_setting('server_version_num')::int >= 120000 THEN -- PG12 fixed tablespace marking on the parent of a native partition set -- Versions older than 12 handle tablespace setting via inherit_template_properties() call below IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; END IF; IF v_template_table IS NOT NULL THEN PERFORM @extschema@.inherit_template_properties(p_parent_table, v_parent_schema, v_partition_name); END IF; IF v_epoch = 'none' THEN -- Attach with normal, time-based values for native constraint EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end); ELSE -- Must attach with integer based values for native constraint and epoch IF v_epoch = 'seconds' THEN EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , EXTRACT('epoch' FROM v_partition_timestamp_start) , EXTRACT('epoch' FROM v_partition_timestamp_end)); ELSIF v_epoch = 'milliseconds' THEN EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , EXTRACT('epoch' FROM v_partition_timestamp_start) * 1000 , EXTRACT('epoch' FROM v_partition_timestamp_end) * 1000); END IF; -- Create secondary, time-based constraint since native's constraint is already integer based EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%s >= %L AND %4$s < %6$L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_partition_expression , v_partition_timestamp_start , v_partition_timestamp_end); END IF; ELSE -- non-native IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; -- Non-native always gets time-based constraint EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%s >= %L AND %4$s < %6$L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_partition_expression , v_partition_timestamp_start , v_partition_timestamp_end); IF v_epoch = 'seconds' THEN -- Non-native needs secondary, integer based constraint for epoch EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_int_check' , v_control , EXTRACT('epoch' from v_partition_timestamp_start) , v_control , EXTRACT('epoch' from v_partition_timestamp_end) ); ELSIF v_epoch = 'milliseconds' THEN EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_int_check' , v_control , EXTRACT('epoch' from v_partition_timestamp_start) * 1000 , v_control , EXTRACT('epoch' from v_partition_timestamp_end) * 1000); END IF; EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); -- If custom time, set extra config options. IF v_partition_type = 'time-custom' THEN INSERT INTO @extschema@.custom_time_partitions (parent_table, child_table, partition_range) VALUES ( p_parent_table, v_parent_schema||'.'||v_partition_name, tstzrange(v_partition_timestamp_start, v_partition_timestamp_end, '[)') ); END IF; -- Indexes cannot be created on the parent, so clustering cannot be used for native yet. PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); -- Foreign keys to other tables not supported in native IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; END IF; -- end native check -- NOTE: Privileges not automatically inherited for native. Only do so if config flag is set IF v_partition_type != 'native' OR (v_partition_type = 'native' AND v_inherit_privileges = TRUE) THEN PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition -- 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 FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_infinite_time_partitions , sub_automatic_maintenance , 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 WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Subpartitioning %s.%s', v_parent_schema, v_partition_name)); END IF; 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_inherit_fk := %L , p_epoch := %L , p_template_table := %L , p_jobmon := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_automatic_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_template_table , v_row.sub_jobmon); IF p_debug THEN RAISE NOTICE 'create_partition_time (create_parent loop): %', v_sql; END IF; EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions , trigger_exception_handling = v_row.sub_trigger_exception_handling , upsert = v_row.sub_upsert , inherit_privileges = v_row.sub_inherit_privileges , trigger_return_null = v_row.sub_trigger_return_null , constraint_valid = v_row.sub_constraint_valid , subscription_refresh = v_row.sub_subscription_refresh WHERE parent_table = v_parent_schema||'.'||v_partition_name; END LOOP; -- end sub partitioning LOOP -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table, p_job_id := v_job_id, p_debug := p_debug); IF v_publications IS NOT NULL THEN -- NOTE: Publications currently not supported on parent table, but are supported on the table partitions if individually assigned. PERFORM @extschema@.apply_publications(p_parent_table, v_parent_schema, v_partition_name); END IF; v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s. Attempted intervals: %s', p_parent_table, p_partition_times)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; 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_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE OR REPLACE FUNCTION @extschema@.dump_partitioned_table_definition( p_parent_table TEXT, p_ignore_template_table BOOLEAN DEFAULT false ) RETURNS TEXT LANGUAGE PLPGSQL STABLE AS $$ DECLARE v_create_parent_definition TEXT; v_update_part_config_definition TEXT; -- Columns from part_config table. v_parent_table TEXT; -- NOT NULL v_control TEXT; -- NOT NULL v_partition_type TEXT; -- NOT NULL v_partition_interval TEXT; -- NOT NULL v_constraint_cols TEXT[]; v_premake integer; -- NOT NULL v_optimize_trigger integer; -- NOT NULL v_optimize_constraint integer; -- NOT NULL v_epoch text; -- NOT NULL v_inherit_fk BOOLEAN; -- NOT NULL v_retention TEXT; v_retention_schema TEXT; v_retention_keep_table BOOLEAN; -- NOT NULL v_retention_keep_index BOOLEAN; -- NOT NULL v_infinite_time_partitions BOOLEAN; -- NOT NULL v_datetime_string TEXT; v_automatic_maintenance TEXT; -- NOT NULL v_jobmon BOOLEAN; -- NOT NULL v_sub_partition_set_full BOOLEAN; -- NOT NULL v_trigger_exception_handling BOOLEAN; v_upsert TEXT; -- NOT NULL v_trigger_return_null BOOLEAN; -- NOT NULL v_template_table TEXT; v_publications TEXT[]; v_inherit_privileges BOOLEAN; -- DEFAULT false v_constraint_valid BOOLEAN; -- DEFAULT true NOT NULL v_subscription_refresh text; BEGIN SELECT pc.parent_table, pc.control, pc.partition_type, pc.partition_interval, pc.constraint_cols, pc.premake, pc.optimize_trigger, pc.optimize_constraint, pc.epoch, pc.inherit_fk, pc.retention, pc.retention_schema, pc.retention_keep_table, pc.retention_keep_index, pc.infinite_time_partitions, pc.datetime_string, pc.automatic_maintenance, pc.jobmon, pc.sub_partition_set_full, pc.trigger_exception_handling, pc.upsert, pc.trigger_return_null, pc.template_table, pc.publications, pc.inherit_privileges, pc.constraint_valid, pc.subscription_refresh INTO v_parent_table, v_control, v_partition_type, v_partition_interval, v_constraint_cols, v_premake, v_optimize_trigger, v_optimize_constraint, v_epoch, v_inherit_fk, v_retention, v_retention_schema, v_retention_keep_table, v_retention_keep_index, v_infinite_time_partitions, v_datetime_string, v_automatic_maintenance, v_jobmon, v_sub_partition_set_full, v_trigger_exception_handling, v_upsert, v_trigger_return_null, v_template_table, v_publications, v_inherit_privileges, v_constraint_valid, v_subscription_refresh FROM @extschema@.part_config pc WHERE pc.parent_table = p_parent_table; IF v_partition_type = 'partman' THEN CASE WHEN v_partition_interval::INTERVAL = '1 year'::INTERVAL THEN v_partition_interval := 'yearly'; WHEN v_partition_interval::INTERVAL = '3 months'::INTERVAL THEN v_partition_interval := 'quarterly'; WHEN v_partition_interval::INTERVAL = '1 month'::INTERVAL THEN v_partition_interval := 'monthly'; WHEN v_partition_interval::INTERVAL = '1 week'::INTERVAL THEN v_partition_interval := 'weekly'; WHEN v_partition_interval::INTERVAL = '1 day'::INTERVAL THEN v_partition_interval := 'daily'; WHEN v_partition_interval::INTERVAL = '1 hour'::INTERVAL THEN v_partition_interval := 'hourly'; WHEN v_partition_interval::INTERVAL = '30 mins'::INTERVAL THEN v_partition_interval := 'half-hour'; WHEN v_partition_interval::INTERVAL = '15 mins'::INTERVAL THEN v_partition_interval := 'quarter-hour'; ELSE RAISE EXCEPTION 'Partitioning interval not recognized for "partman" partitioning type'; END CASE; END IF; IF v_partition_type = 'native' AND p_ignore_template_table THEN v_template_table := NULL; END IF; v_create_parent_definition := format( E'SELECT @extschema@.create_parent( \tp_parent_table := %L, \tp_control := %L, \tp_type := %L, \tp_interval := %L, \tp_constraint_cols := %L, \tp_premake := %s, \tp_automatic_maintenance := %L, \tp_inherit_fk := %L, \tp_epoch := %L, \tp_upsert := %L, \tp_publications := %L, \tp_trigger_return_null := %L, \tp_template_table := %L, \tp_jobmon := %L \t-- v_start_partition is intentionally ignored as there \t-- isn''t any obviously correct definition. );', v_parent_table, v_control, v_partition_type, v_partition_interval, v_constraint_cols, v_premake, v_automatic_maintenance, v_inherit_fk, v_epoch, v_upsert, v_publications, v_trigger_return_null, v_template_table, v_jobmon ); v_update_part_config_definition := format( E'UPDATE @extschema@.part_config SET \toptimize_trigger = %s, \toptimize_constraint = %s, \tretention = %L, \tretention_schema = %L, \tretention_keep_table = %L, \tretention_keep_index = %L, \tinfinite_time_partitions = %L, \tdatetime_string = %L, \tsub_partition_set_full = %L, \ttrigger_exception_handling = %L, \tinherit_privileges = %L, \tconstraint_valid = %L, \tsubscription_refresh = %L WHERE parent_table = %L;', v_optimize_trigger, v_optimize_constraint, v_retention, v_retention_schema, v_retention_keep_table, v_retention_keep_index, v_infinite_time_partitions, v_datetime_string, v_sub_partition_set_full, v_trigger_exception_handling, v_inherit_privileges, v_constraint_valid, v_subscription_refresh, v_parent_table ); RETURN concat_ws(E'\n', v_create_parent_definition, v_update_part_config_definition ); END $$; CREATE OR REPLACE FUNCTION @extschema@.apply_privileges(p_parent_schema text, p_parent_tablename text, p_child_schema text, p_child_tablename text, p_job_id bigint DEFAULT NULL) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_child_grant record; v_child_owner text; v_grantees text[]; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_match boolean; v_parent_grant record; v_parent_owner text; v_revoke text; v_row_revoke record; v_sql text; v_step_id bigint; BEGIN /* * Apply privileges and ownership that exist on a given parent to the given child table */ SELECT jobmon INTO v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_schema ||'.'|| p_parent_tablename; IF v_jobmon IS NULL THEN RAISE EXCEPTION 'Given table is not managed by this extention: %.%', p_parent_schema, p_parent_tablename; END IF; 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 = p_parent_schema::name AND c.relname = p_parent_tablename::name; SELECT pg_get_userbyid(c.relowner) INTO v_child_owner 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_parent_owner IS NULL THEN RAISE EXCEPTION 'Given parent table does not exist: %.%', p_parent_schema, p_parent_tablename; END IF; IF v_child_owner IS NULL THEN RAISE EXCEPTION 'Given child table does not exist: %.%', p_child_schema, p_child_tablename; END IF; IF v_jobmon THEN SELECT nspname INTO v_jobmon_schema FROM pg_namespace n, pg_extension e WHERE e.extname = 'pg_jobmon' AND e.extnamespace = n.oid; END IF; IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN EXECUTE format('SELECT %I.add_job(%L)', v_jobmon_schema, format('PARTMAN APPLYING PRIVILEGES TO CHILD TABLE: %s.%s', p_child_schema, p_child_tablename)) INTO v_job_id; ELSE v_job_id := p_job_id; END IF; EXECUTE format('SELECT %I.add_step(%L, %L)', v_jobmon_schema, v_job_id, format('Setting new child table privileges for %s.%s', p_child_schema, p_child_tablename)) INTO v_step_id; END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.update_step(%L, %L, %L)' , v_jobmon_schema , v_step_id , 'PENDING' , format('Applying privileges on child partition: %s.%s' , p_child_schema , p_child_tablename) ); END IF; FOR v_parent_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types , grantee FROM @extschema@.table_privs WHERE table_schema = p_parent_schema::name AND table_name = p_parent_tablename::name GROUP BY grantee LOOP -- Compare parent & child grants. Don't re-apply if it already exists v_match := false; v_sql := NULL; FOR v_child_grant IN SELECT array_agg(DISTINCT privilege_type::text ORDER BY privilege_type::text) AS types , grantee FROM @extschema@.table_privs WHERE table_schema = p_child_schema::name AND table_name = p_child_tablename::name GROUP BY grantee LOOP IF v_parent_grant.types = v_child_grant.types AND v_parent_grant.grantee = v_child_grant.grantee THEN v_match := true; END IF; END LOOP; IF v_match = false THEN IF v_parent_grant.grantee = 'PUBLIC' THEN v_sql := 'GRANT %s ON %I.%I TO %s'; ELSE v_sql := 'GRANT %s ON %I.%I TO %I'; END IF; EXECUTE format(v_sql , array_to_string(v_parent_grant.types, ',') , p_child_schema , p_child_tablename , v_parent_grant.grantee); v_sql := NULL; SELECT string_agg(r, ',') INTO v_revoke FROM (SELECT unnest(v_all) AS r EXCEPT SELECT unnest(v_parent_grant.types)) x; IF v_revoke IS NOT NULL THEN IF v_parent_grant.grantee = 'PUBLIC' THEN v_sql := 'REVOKE %s ON %I.%I FROM %s CASCADE'; ELSE v_sql := 'REVOKE %s ON %I.%I FROM %I CASCADE'; END IF; EXECUTE format(v_sql , v_revoke , p_child_schema , p_child_tablename , v_parent_grant.grantee); v_sql := NULL; END IF; END IF; v_grantees := array_append(v_grantees, v_parent_grant.grantee::text); END LOOP; -- Revoke all privileges from roles that have none on the parent IF v_grantees IS NOT NULL THEN FOR v_row_revoke IN SELECT role FROM ( SELECT DISTINCT grantee::text AS role FROM @extschema@.table_privs WHERE table_schema = p_child_schema::name AND table_name = p_child_tablename::name EXCEPT SELECT unnest(v_grantees)) x LOOP IF v_row_revoke.role IS NOT NULL THEN IF v_row_revoke.role = 'PUBLIC' THEN v_sql := 'REVOKE ALL ON %I.%I FROM %s'; ELSE v_sql := 'REVOKE ALL ON %I.%I FROM %I'; END IF; EXECUTE format(v_sql , p_child_schema , p_child_tablename , v_row_revoke.role); END IF; END LOOP; END IF; IF v_parent_owner <> v_child_owner THEN EXECUTE format('ALTER TABLE %I.%I OWNER TO %I' , p_child_schema , p_child_tablename , v_parent_owner); END IF; IF v_jobmon_schema IS NOT NULL THEN EXECUTE format('SELECT %I.update_step(%L, %L, %L)', v_jobmon_schema, v_step_id, 'OK', 'Done'); IF p_job_id IS NULL THEN EXECUTE format('SELECT %I.close_job(%L)', v_jobmon_schema, v_job_id); END IF; END IF; 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 RE-APPLYING PRIVILEGES TO ALL CHILD TABLES OF: %s'')', v_jobmon_schema, p_parent_tablename) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', v_jobmon_schema, v_job_id, p_parent_tablename) 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 $$; CREATE OR REPLACE FUNCTION @extschema@.check_automatic_maintenance_value (p_automatic_maintenance text) RETURNS boolean LANGUAGE plpgsql IMMUTABLE AS $$ DECLARE v_result boolean; BEGIN SELECT p_automatic_maintenance IN ('on', 'off') INTO v_result; RETURN v_result; END $$; -- Restore dropped object privileges DO $$ DECLARE v_row record; BEGIN FOR v_row IN SELECT statement FROM partman_preserve_privs_temp LOOP IF v_row.statement IS NOT NULL THEN EXECUTE v_row.statement; END IF; END LOOP; END $$; DROP TABLE IF EXISTS partman_preserve_privs_temp; pg_partman-4.6.0/updates/pg_partman--4.4.0--4.5.0.sql000066400000000000000000005620341412761706100214430ustar00rootroot00000000000000-- Update 4.4.1 is skipped due to being incompatible with PG10 and earlier. Version 4.5.0 touched all of the same functions/procedures as 4.4.1, so this update file contains the same content as update 4.4.1--4.5.0. -- 4.5.0 -- Officially supported minimal version is now PostgreSQL 9.6. PostgreSQL 9.6 support will be ending the next pg_partman release after PostgreSQL 14 is released. -- Fixed issue when upgrading from 4.4.0 to 4.4.1 on PG10 and below. Upgrade path now goes directly from 4.4.0 to 4.5.0 and skips 4.4.1. All changes in 4.4.1 have been included in 4.5.0 where necessary. (Github Issue #342) -- Added the parameter `p_ignored_columns` to partition_data_time(), partition_data_id(), partition_data_proc(), undo_partition() and undo_partition_proc(). The parameter is an array that can list one or more columns to ignore their values when moving the data. This allows for moving data out of the default/parent table when generated columns are in use. (Github Issue #336) -- Removed p_debug from functions changed in this update. Slowly migrating all functions away from parameter to using debug logging level instead. -- Fixed the p_start_partition parameter working properly with id->timestamp sub-partitioning. (Github Issue #341) -- 4.4.1 -- Added documentation for how to use pg_partman with native partitioning. -- Fix long present typos mostly in comments, error messages, & docs. (Github PR #315 & #319) -- Update description of the pg_partman_bgw.dbname GUC to state that it is not actually optional. -- Fix syntax error in reapply_indexes.py -- Fixed wrong variable usage in undo_partition_proc() exception. CREATE TEMP TABLE partman_preserve_privs_temp (statement text); INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_sub_parent(text, text, text, text, text, text[], int, text, boolean, text, text, boolean, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_sub_parent'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_partition_id(text, bigint[], boolean, text) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_partition_id'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_partition_time(text, timestamptz[], boolean, text) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_partition_time'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_parent(text, text, text, text, text[], int, text, text, boolean, text, text, text[], boolean, text, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_parent'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.apply_constraints(text, text, boolean, bigint) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'apply_constraints'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.partition_data_id(text, int, bigint, numeric, text, boolean, text, text[]) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'partition_data_id'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.partition_data_time(text, int, interval, numeric, text, boolean, text, text[]) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'partition_data_time'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.run_maintenance(text, boolean, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'run_maintenance'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.run_maintenance(text, boolean, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'run_maintenance'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.undo_partition(text, int, text, boolean, numeric, text, text[]) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'undo_partition'; DROP FUNCTION @extschema@.create_partition_id(text, bigint[], boolean, boolean); DROP FUNCTION @extschema@.create_partition_time(text, timestamptz[], boolean, boolean); DROP FUNCTION @extschema@.create_sub_parent(text, text, text, text, text, text[], int, text, boolean, text, text, boolean, boolean, boolean); DROP FUNCTION @extschema@.create_parent(text, text, text, text, text[], int, text, text, boolean, text, text, text[], boolean, text, boolean, boolean); DROP FUNCTION @extschema@.apply_constraints(text, text, boolean, bigint, boolean); DROP FUNCTION @extschema@.partition_data_id(text, int, bigint, numeric, text, boolean, text); DROP FUNCTION @extschema@.partition_data_time(text, int, interval, numeric, text, boolean, text); DROP FUNCTION @extschema@.run_maintenance(text, boolean, boolean, boolean); DROP FUNCTION @extschema@.undo_partition(text, int, text, boolean, numeric, text); CREATE 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' 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 FUNCTION @extschema@.create_partition_id(p_parent_table text, p_partition_ids bigint[], p_analyze boolean DEFAULT true, p_start_partition text DEFAULT NULL) RETURNS boolean LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_control_type text; v_exists text; v_grantees text[]; v_hasoids boolean; v_id bigint; v_inherit_fk boolean; v_inherit_privileges boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_grant record; v_parent_schema text; v_parent_tablename text; v_parent_tablespace text; v_partition_interval bigint; v_partition_created boolean := false; v_partition_name text; v_partition_type text; v_publications text[]; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_sub_control text; v_sub_partition_type text; v_sub_id_max bigint; v_sub_id_min bigint; v_template_table text; v_unlogged char; BEGIN /* * Function to create id partitions */ SELECT control , partition_type , partition_interval , inherit_fk , jobmon , template_table , publications , inherit_privileges INTO v_control , v_partition_type , v_partition_interval , v_inherit_fk , v_jobmon , v_template_table , v_publications , v_inherit_privileges FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT n.nspname, c.relname, t.spcname INTO v_parent_schema, v_parent_tablename, v_parent_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 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; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'id' THEN RAISE EXCEPTION 'ERROR: Given parent table is not set up for id/serial partitioning'; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'id'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; FOREACH v_id IN ARRAY p_partition_ids LOOP -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_id_min IS NOT NULL THEN IF v_id < v_sub_id_min OR v_id > v_sub_id_max THEN CONTINUE; END IF; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_id::text, TRUE); -- If child table already exists, skip creation -- Have to check pg_class because if subpartitioned, table will not be in pg_tables SELECT c.relname INTO v_exists 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_partition_name::name; IF v_exists IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_id||' to '||(v_id + v_partition_interval)-1); END IF; v_sql := 'CREATE'; -- As of PG12, the unlogged/logged status of a native parent table cannot be changed via an ALTER TABLE in order to affect its children. -- As of v4.2x, the unlogged state will be managed via the template table SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c 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; IF v_unlogged = 'u' and v_partition_type != 'native' THEN v_sql := v_sql || ' UNLOGGED'; END IF; -- Close parentheses on LIKE are below due to differing requirements of native subpartitioning -- Same INCLUDING list is used in create_parent() v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING STORAGE INCLUDING COMMENTS ' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); IF current_setting('server_version_num')::int >= 120000 THEN v_sql := v_sql || ' INCLUDING GENERATED '; END IF; SELECT sub_partition_type, sub_control INTO v_sub_partition_type, v_sub_control FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; IF v_sub_partition_type = 'native' THEN -- INCLUDING INDEXES isn't necessary for native partitioning. It isn't supported in v10 and -- for v11+ index inheritance is automatically handled when the partition is attached v_sql := v_sql || format(') PARTITION BY RANGE (%I) ', v_sub_control); ELSE v_sql := v_sql || format(' INCLUDING INDEXES) ', v_sub_control); END IF; IF current_setting('server_version_num')::int < 120000 THEN -- column removed from pgclass in pg12 SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c 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; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; END IF; RAISE DEBUG 'create_partition_id v_sql: %', v_sql; EXECUTE v_sql; IF v_partition_type = 'native' THEN IF current_setting('server_version_num')::int >= 120000 THEN -- PG12 fixed tablespace marking on the parent of a native partition set -- Versions older than 12 handle tablespace setting via inherit_template_properties() call below IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; END IF; IF v_template_table IS NOT NULL THEN PERFORM @extschema@.inherit_template_properties(p_parent_table, v_parent_schema, v_partition_name); END IF; EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , v_id , v_id + v_partition_interval); ELSE -- non-native IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %s AND %I < %s )' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_control , v_id , v_control , v_id + v_partition_interval); EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I', v_parent_schema, v_partition_name, v_parent_schema, v_parent_tablename); -- Indexes cannot be created on the parent, so clustering cannot be used for native yet. PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); -- Foreign keys to other tables not supported on native parent tables IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; END IF; -- NOTE: Privileges not automatically inherited for native. Only do so if config flag is set IF v_partition_type != 'native' OR (v_partition_type = 'native' AND v_inherit_privileges = TRUE) THEN PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition -- 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 FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_infinite_time_partitions , sub_automatic_maintenance , 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 WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Subpartitioning '||v_partition_name); END IF; 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_inherit_fk := %L , p_epoch := %L , p_template_table := %L , p_jobmon := %L , p_start_partition := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_automatic_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_template_table , v_row.sub_jobmon , p_start_partition); RAISE DEBUG 'create_partition_id (create_parent loop): %', v_sql; EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions , trigger_exception_handling = v_row.sub_trigger_exception_handling , upsert = v_row.sub_upsert , inherit_privileges = v_row.sub_inherit_privileges , trigger_return_null = v_row.sub_trigger_return_null , constraint_valid = v_row.sub_constraint_valid , subscription_refresh = v_row.sub_subscription_refresh WHERE parent_table = v_parent_schema||'.'||v_partition_name; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; -- end sub partitioning LOOP -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table, p_job_id := v_job_id); IF v_publications IS NOT NULL THEN -- NOTE: Publications currently not supported on parent table, but are supported on the table partitions if individually assigned. PERFORM @extschema@.apply_publications(p_parent_table, v_parent_schema, v_partition_name); END IF; v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s', p_parent_table)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE FUNCTION @extschema@.create_partition_time(p_parent_table text, p_partition_times timestamptz[], p_analyze boolean DEFAULT true, p_start_partition text DEFAULT NULL) RETURNS boolean LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_control_type text; v_datetime_string text; v_epoch text; v_exists smallint; v_grantees text[]; v_hasoids boolean; v_inherit_privileges boolean; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_grant record; v_parent_schema text; v_parent_tablename text; v_part_col text; v_partition_created boolean := false; v_partition_name text; v_partition_suffix text; v_parent_tablespace text; v_partition_expression text; v_partition_interval interval; v_partition_timestamp_end timestamptz; v_partition_timestamp_start timestamptz; v_publications text[]; v_quarter text; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_sub_control text; v_sub_parent text; v_sub_partition_type text; v_sub_timestamp_max timestamptz; v_sub_timestamp_min timestamptz; v_template_table text; v_trunc_value text; v_time timestamptz; v_partition_type text; v_unlogged char; v_year text; BEGIN /* * Function to create a child table in a time-based partition set */ SELECT partition_type , control , partition_interval , epoch , inherit_fk , jobmon , datetime_string , template_table , publications , inherit_privileges INTO v_partition_type , v_control , v_partition_interval , v_epoch , v_inherit_fk , v_jobmon , v_datetime_string , v_template_table , v_publications , v_inherit_privileges FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT n.nspname, c.relname, t.spcname INTO v_parent_schema, v_parent_tablename, v_parent_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 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; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'time' THEN IF (v_control_type = 'id' AND v_epoch = 'none') OR v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run on partition set without time based control column or epoch flag set with an id column. Found control: %, epoch: %', v_control_type, v_epoch; END IF; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::timestamptz, sub_max::timestamptz INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'time'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) END; RAISE DEBUG 'create_partition_time: v_partition_expression: %', v_partition_expression; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_timestamp_start := v_time; BEGIN v_partition_timestamp_end := v_time + v_partition_interval; 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_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_time||' skipped'); CONTINUE; END; -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_timestamp_min IS NOT NULL THEN IF v_time < v_sub_timestamp_min OR v_time > v_sub_timestamp_max THEN CONTINUE; END IF; END IF; -- This suffix generation code is in partition_data_time() as well v_partition_suffix := to_char(v_time, v_datetime_string); v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); -- Check if child exists. SELECT count(*) INTO v_exists 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_partition_name::name; IF v_exists > 0 THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Creating new partition %s.%s with interval from %s to %s' , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end-'1sec'::interval)); END IF; v_sql := 'CREATE'; -- As of PG12, the unlogged/logged status of a native parent table cannot be changed via an ALTER TABLE in order to affect its children. -- As of v4.2x, the unlogged state will be managed via the template table SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c 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; IF v_unlogged = 'u' and v_partition_type != 'native' THEN v_sql := v_sql || ' UNLOGGED'; END IF; -- Close parentheses on LIKE are below due to differing requirements of native subpartitioning -- Same INCLUDING list is used in create_parent() v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING STORAGE INCLUDING COMMENTS ' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); IF current_setting('server_version_num')::int >= 120000 THEN v_sql := v_sql || ' INCLUDING GENERATED '; END IF; SELECT sub_partition_type, sub_control INTO v_sub_partition_type, v_sub_control FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; IF v_sub_partition_type = 'native' THEN -- INCLUDING INDEXES isn't necessary for native partitioning. It isn't supported in v10 and -- for v11+ index inheritance is automatically handled when the partition is attached v_sql := v_sql || format(') PARTITION BY RANGE (%I) ', v_sub_control); ELSE v_sql := v_sql || format(' INCLUDING INDEXES) ', v_sub_control); END IF; IF current_setting('server_version_num')::int < 120000 THEN -- column removed from pgclass in pg12 SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c 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; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; END IF; RAISE DEBUG 'create_partition_time v_sql: %', v_sql; EXECUTE v_sql; IF v_partition_type = 'native' THEN IF current_setting('server_version_num')::int >= 120000 THEN -- PG12 fixed tablespace marking on the parent of a native partition set -- Versions older than 12 handle tablespace setting via inherit_template_properties() call below IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; END IF; IF v_template_table IS NOT NULL THEN PERFORM @extschema@.inherit_template_properties(p_parent_table, v_parent_schema, v_partition_name); END IF; IF v_epoch = 'none' THEN -- Attach with normal, time-based values for native constraint EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end); ELSE -- Must attach with integer based values for native constraint and epoch IF v_epoch = 'seconds' THEN EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , EXTRACT('epoch' FROM v_partition_timestamp_start) , EXTRACT('epoch' FROM v_partition_timestamp_end)); ELSIF v_epoch = 'milliseconds' THEN EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , EXTRACT('epoch' FROM v_partition_timestamp_start) * 1000 , EXTRACT('epoch' FROM v_partition_timestamp_end) * 1000); END IF; -- Create secondary, time-based constraint since native's constraint is already integer based EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%s >= %L AND %4$s < %6$L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_partition_expression , v_partition_timestamp_start , v_partition_timestamp_end); END IF; ELSE -- non-native IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; -- Non-native always gets time-based constraint EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%s >= %L AND %4$s < %6$L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_partition_expression , v_partition_timestamp_start , v_partition_timestamp_end); IF v_epoch = 'seconds' THEN -- Non-native needs secondary, integer based constraint for epoch EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_int_check' , v_control , EXTRACT('epoch' from v_partition_timestamp_start) , v_control , EXTRACT('epoch' from v_partition_timestamp_end) ); ELSIF v_epoch = 'milliseconds' THEN EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_int_check' , v_control , EXTRACT('epoch' from v_partition_timestamp_start) * 1000 , v_control , EXTRACT('epoch' from v_partition_timestamp_end) * 1000); END IF; EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); -- If custom time, set extra config options. IF v_partition_type = 'time-custom' THEN INSERT INTO @extschema@.custom_time_partitions (parent_table, child_table, partition_range) VALUES ( p_parent_table, v_parent_schema||'.'||v_partition_name, tstzrange(v_partition_timestamp_start, v_partition_timestamp_end, '[)') ); END IF; -- Indexes cannot be created on the parent, so clustering cannot be used for native yet. PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); -- Foreign keys to other tables not supported in native IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; END IF; -- end native check -- NOTE: Privileges not automatically inherited for native. Only do so if config flag is set IF v_partition_type != 'native' OR (v_partition_type = 'native' AND v_inherit_privileges = TRUE) THEN PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition -- 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 FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_infinite_time_partitions , sub_automatic_maintenance , 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 WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Subpartitioning %s.%s', v_parent_schema, v_partition_name)); END IF; 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_inherit_fk := %L , p_epoch := %L , p_template_table := %L , p_jobmon := %L , p_start_partition := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_automatic_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_template_table , v_row.sub_jobmon , p_start_partition); RAISE DEBUG 'create_partition_time (create_parent loop): %', v_sql; EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions , trigger_exception_handling = v_row.sub_trigger_exception_handling , upsert = v_row.sub_upsert , inherit_privileges = v_row.sub_inherit_privileges , trigger_return_null = v_row.sub_trigger_return_null , constraint_valid = v_row.sub_constraint_valid , subscription_refresh = v_row.sub_subscription_refresh WHERE parent_table = v_parent_schema||'.'||v_partition_name; END LOOP; -- end sub partitioning LOOP -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table, p_job_id := v_job_id); IF v_publications IS NOT NULL THEN -- NOTE: Publications currently not supported on parent table, but are supported on the table partitions if individually assigned. PERFORM @extschema@.apply_publications(p_parent_table, v_parent_schema, v_partition_name); END IF; v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s. Attempted intervals: %s', p_parent_table, p_partition_times)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; 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_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE 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_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 INTO v_higher_parent_schema, v_higher_parent_table, v_higher_parent_control 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' 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 $$; CREATE FUNCTION @extschema@.apply_constraints(p_parent_table text, p_child_table text DEFAULT NULL, p_analyze boolean DEFAULT FALSE, p_job_id bigint DEFAULT NULL) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_child_exists text; v_child_tablename text; v_col text; v_constraint_cols text[]; v_constraint_col_type text; v_constraint_name text; v_constraint_valid boolean; v_constraint_values record; v_control text; v_control_type text; v_datetime_string text; v_epoch text; v_existing_constraint_name text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_id bigint; v_last_partition_timestamp timestamptz; v_max_id bigint; v_max_timestamp timestamptz; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_optimize_constraint int; v_parent_schema text; v_parent_table text; v_parent_tablename text; v_partition_interval text; v_partition_suffix text; v_premake int; v_sql text; v_step_id bigint; v_suffix_position int; v_type text; BEGIN /* * Apply constraints managed by partman extension */ SELECT parent_table , partition_type , control , premake , partition_interval , optimize_constraint , epoch , datetime_string , constraint_cols , jobmon , constraint_valid INTO v_parent_table , v_type , v_control , v_premake , v_partition_interval , v_optimize_constraint , v_epoch , v_datetime_string , v_constraint_cols , v_jobmon , v_constraint_valid FROM @extschema@.part_config WHERE parent_table = p_parent_table AND constraint_cols IS NOT NULL; IF v_constraint_cols IS NULL THEN RAISE DEBUG 'Given parent table (%) not set up for constraint management (constraint_cols is NULL)', p_parent_table; -- Returns silently to allow this function to be simply called by maintenance processes without having to check if config options are set. RETURN; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(v_parent_table, '.', 1)::name AND tablename = split_part(v_parent_table, '.', 2)::name; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE CONSTRAINT: %s', v_parent_table)); ELSE v_job_id = p_job_id; END IF; END IF; -- If p_child_table is null, figure out the partition that is the one right before the optimize_constraint value backwards. IF p_child_table IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying additional constraints: Automatically determining most recent child on which to apply constraints'); END IF; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(v_parent_table, 'DESC') LIMIT 1; IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN SELECT child_start_time INTO v_last_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_partition_interval, v_parent_table); v_partition_suffix := to_char(v_last_partition_timestamp - (v_partition_interval::interval * (v_optimize_constraint + v_premake + 1) ), v_datetime_string); ELSIF v_control_type = 'id' THEN SELECT child_start_id INTO v_last_partition_id FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_partition_interval, v_parent_table); v_partition_suffix := (v_last_partition_id - (v_partition_interval::int * (v_optimize_constraint + v_premake + 1) ))::text; END IF; v_child_tablename := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); RAISE DEBUG 'apply_constraint: v_parent_tablename: % , v_partition_suffix: %', v_parent_tablename, v_partition_suffix; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Target child table: %s.%s', v_parent_schema, v_child_tablename)); END IF; ELSE v_child_tablename = split_part(p_child_table, '.', 2); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying additional constraints: Checking if target child table exists'); END IF; SELECT tablename FROM pg_catalog.pg_tables INTO v_child_exists WHERE schemaname = v_parent_schema::name AND tablename = v_child_tablename::name; IF v_child_exists IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Target child table (%s) does not exist. Skipping constraint creation.', v_child_tablename)); IF p_job_id IS NULL THEN PERFORM close_job(v_job_id); END IF; END IF; RAISE DEBUG 'Target child table (%) does not exist. Skipping constraint creation.', v_child_tablename; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; FOREACH v_col IN ARRAY v_constraint_cols LOOP SELECT con.conname INTO v_existing_constraint_name FROM pg_catalog.pg_constraint con JOIN pg_class c ON c.oid = con.conrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN pg_catalog.pg_attribute a ON con.conrelid = a.attrelid WHERE c.relname = v_child_tablename::name AND n.nspname = v_parent_schema::name AND con.conname LIKE 'partmanconstr_%' AND con.contype = 'c' AND a.attname = v_col::name AND ARRAY[a.attnum] OPERATOR(pg_catalog.<@) con.conkey AND a.attisdropped = false; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Applying additional constraints: Applying new constraint on column: %s', v_col)); END IF; IF v_existing_constraint_name IS NOT NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Partman managed constraint already exists on this table (%s) and column (%s). Skipping creation.', v_child_tablename, v_col)); END IF; RAISE DEBUG 'Partman managed constraint already exists on this table (%) and column (%). Skipping creation.', v_child_tablename, v_col ; CONTINUE; END IF; -- Ensure column name gets put on end of constraint name to help avoid naming conflicts v_constraint_name := @extschema@.check_name_length('partmanconstr_'||v_child_tablename, p_suffix := '_'||v_col); EXECUTE format('SELECT min(%I)::text AS min, max(%I)::text AS max FROM %I.%I', v_col, v_col, v_parent_schema, v_child_tablename) INTO v_constraint_values; IF v_constraint_values IS NOT NULL THEN v_sql := format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I <= %L)' , v_parent_schema , v_child_tablename , v_constraint_name , v_col , v_constraint_values.min , v_col , v_constraint_values.max); IF v_constraint_valid = false THEN v_sql := format('%s NOT VALID', v_sql); END IF; RAISE DEBUG 'Constraint creation query: %', v_sql; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('New constraint created: %s', v_sql)); END IF; ELSE RAISE DEBUG 'Given column (%) contains all NULLs. No constraint created', v_col; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Given column (%s) contains all NULLs. No constraint created', v_col)); END IF; END IF; END LOOP; IF p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Applying additional constraints: Running analyze on partition set: %s', v_parent_table)); END IF; RAISE DEBUG 'Running analyze on partition set: %', v_parent_table; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); 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 CONSTRAINT: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE FUNCTION @extschema@.partition_data_id(p_parent_table text , p_batch_count int DEFAULT 1 , p_batch_interval bigint DEFAULT NULL , p_lock_wait numeric DEFAULT 0 , p_order text DEFAULT 'ASC' , p_analyze boolean DEFAULT true , p_source_table text DEFAULT NULL , p_ignored_columns text[] DEFAULT NULL) RETURNS bigint LANGUAGE plpgsql AS $$ DECLARE v_col text; v_column_list text; v_control text; v_control_type text; v_current_partition_name text; v_default_exists boolean; v_default_schemaname text; v_default_tablename text; v_epoch text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_id bigint; v_min_partition_id bigint; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_tablename text; v_partition_interval bigint; v_partition_id bigint[]; v_partition_type text; v_rowcount bigint; v_source_schemaname text; v_source_tablename text; v_sql text; v_start_control bigint; v_total_rows bigint := 0; BEGIN /* * Populate the child table(s) of an id-based partition set with old data from the original parent */ SELECT partition_interval::bigint , partition_type , control , epoch INTO v_partition_interval , v_partition_type , v_control , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: No entry in part_config found for given table: %', p_parent_table; END IF; SELECT schemaname, tablename INTO v_source_schemaname, v_source_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- Preserve real parent tablename for use below v_parent_tablename := v_source_tablename; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_source_schemaname, v_source_tablename, v_control); IF v_control_type <> 'id' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN RAISE EXCEPTION 'Control column for given partition set is not id/serial based or epoch flag is set for time-based partitioning.'; END IF; IF p_source_table IS NOT NULL THEN -- Set source table to user given source table instead of parent table v_source_schemaname := NULL; v_source_tablename := NULL; SELECT schemaname, tablename INTO v_source_schemaname, v_source_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_source_table, '.', 1)::name AND tablename = split_part(p_source_table, '.', 2)::name; IF v_source_tablename IS NULL THEN RAISE EXCEPTION 'Given source table does not exist in system catalogs: %', p_source_table; END IF; ELSIF v_partition_type = 'native' AND current_setting('server_version_num')::int >= 110000 THEN IF p_batch_interval IS NOT NULL AND p_batch_interval != v_partition_interval THEN -- This is true because all data for a given child table must be moved out of the default partition before the child table can be created. -- So cannot create the child table when only some of the data has been moved out of the default partition. RAISE EXCEPTION 'Custom intervals are not allowed when moving data out of the DEFAULT partition in a native set. Please leave p_interval/p_batch_interval parameters unset or NULL to allow use of partition set''s default partitioning interval.'; END IF; -- Set source table to default table if PG11+, p_source_table is not set, and it exists -- Otherwise just return with a DEBUG that no data source exists v_sql := format('SELECT n.nspname::text, c.relname::text FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = ''%I.%I''::regclass AND pg_get_expr(relpartbound, c.oid) = ''DEFAULT''' , v_source_schemaname , v_source_tablename); EXECUTE v_sql INTO v_default_schemaname, v_default_tablename; IF v_default_tablename IS NOT NULL THEN v_source_schemaname := v_default_schemaname; v_source_tablename := v_default_tablename; v_default_exists := true; EXECUTE format ('CREATE TEMP TABLE IF NOT EXISTS partman_temp_data_storage (LIKE %I.%I INCLUDING INDEXES) ON COMMIT DROP', v_source_schemaname, v_source_tablename); ELSE RAISE DEBUG 'No default table found when partition_data_id() was called'; RETURN v_total_rows; END IF; END IF; 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'); IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; -- Generate column list to use in SELECT/INSERT statements below. Allows for exclusion of GENERATED (or any other desired) columns. v_sql := format ('SELECT ''"''||string_agg(attname, ''","'')||''"'' 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 n.nspname = %L AND c.relname = %L AND a.attnum > 0 AND a.attisdropped = false' , v_source_schemaname , v_source_tablename); IF p_ignored_columns IS NOT NULL THEN FOREACH v_col IN ARRAY p_ignored_columns LOOP v_sql := v_sql || format(' AND attname != %L ', v_col); END LOOP; END IF; EXECUTE v_sql INTO v_column_list; FOR i IN 1..p_batch_count LOOP IF p_order = 'ASC' THEN EXECUTE format('SELECT min(%I) FROM ONLY %I.%I', v_control, v_source_schemaname, v_source_tablename) INTO v_start_control; IF v_start_control IS NULL THEN EXIT; END IF; v_min_partition_id = v_start_control - (v_start_control % v_partition_interval); v_partition_id := ARRAY[v_min_partition_id]; -- Check if custom batch interval overflows current partition maximum IF (v_start_control + p_batch_interval) >= (v_min_partition_id + v_partition_interval) THEN v_max_partition_id := v_min_partition_id + v_partition_interval; ELSE v_max_partition_id := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_control, v_source_schemaname, v_source_tablename) INTO v_start_control; IF v_start_control IS NULL THEN EXIT; END IF; v_min_partition_id = v_start_control - (v_start_control % v_partition_interval); -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_id := v_min_partition_id + v_partition_interval; v_partition_id := ARRAY[v_min_partition_id]; -- Make sure minimum doesn't underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_id THEN v_min_partition_id = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN v_sql := format('SELECT %s FROM ONLY %I.%I WHERE %I >= %s AND %I < %s FOR UPDATE NOWAIT' , v_column_list , v_source_schemaname , v_source_tablename , v_control , v_min_partition_id , v_control , v_max_partition_id); EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; v_current_partition_name := @extschema@.check_name_length(COALESCE(v_parent_tablename), v_min_partition_id::text, TRUE); IF v_default_exists THEN -- Child tables cannot be created in native partitioning if data that belongs to it exists in the default -- Have to move data out to temporary location, create child table, then move it back -- Temp table created above to avoid excessive temp creation in loop EXECUTE format('WITH partition_data AS ( DELETE FROM %1$I.%2$I WHERE %3$I >= %4$s AND %3$I < %5$s RETURNING *) INSERT INTO partman_temp_data_storage (%6$s) SELECT %6$s FROM partition_data' , v_source_schemaname , v_source_tablename , v_control , v_min_partition_id , v_max_partition_id , v_column_list); PERFORM @extschema@.create_partition_id(p_parent_table, v_partition_id, p_analyze); EXECUTE format('WITH partition_data AS ( DELETE FROM partman_temp_data_storage RETURNING *) INSERT INTO %1$I.%2$I (%3$s) SELECT %3$s FROM partition_data' , v_source_schemaname , v_current_partition_name , v_column_list); ELSE PERFORM @extschema@.create_partition_id(p_parent_table, v_partition_id, p_analyze); EXECUTE format('WITH partition_data AS ( DELETE FROM ONLY %1$I.%2$I WHERE %3$I >= %4$s AND %3$I < %5$s RETURNING *) INSERT INTO %1$I.%6$I (%7$s) SELECT %7$s FROM partition_data' , v_source_schemaname , v_source_tablename , v_control , v_min_partition_id , v_max_partition_id , v_current_partition_name , v_column_list); END IF; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; IF v_partition_type = 'partman' THEN PERFORM @extschema@.create_function_id(p_parent_table); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_total_rows; END $$; CREATE FUNCTION @extschema@.partition_data_time( p_parent_table text , p_batch_count int DEFAULT 1 , p_batch_interval interval DEFAULT NULL , p_lock_wait numeric DEFAULT 0 , p_order text DEFAULT 'ASC' , p_analyze boolean DEFAULT true , p_source_table text DEFAULT NULL , p_ignored_columns text[] DEFAULT NULL) RETURNS bigint LANGUAGE plpgsql AS $$ DECLARE v_col text; v_column_list text; v_control text; v_control_type text; v_datetime_string text; v_current_partition_name text; v_default_exists boolean; v_default_schemaname text; v_default_tablename text; v_epoch text; v_last_partition text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_timestamp timestamptz; v_min_partition_timestamp timestamptz; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_tablename text; v_parent_tablename_real text; v_partition_expression text; v_partition_interval interval; v_partition_suffix text; v_partition_timestamp timestamptz[]; v_partition_type text; v_source_schemaname text; v_source_tablename text; v_rowcount bigint; v_sql text; v_start_control timestamptz; v_total_rows bigint := 0; BEGIN /* * Populate the child table(s) of a time-based partition set with old data from the original parent */ SELECT partition_type , partition_interval::interval , control , datetime_string , epoch INTO v_partition_type , v_partition_interval , v_control , v_datetime_string , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: No entry in part_config found for given table: %', p_parent_table; END IF; SELECT schemaname, tablename INTO v_source_schemaname, v_source_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- Preserve real parent tablename for use below v_parent_tablename := v_source_tablename; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_source_schemaname, v_source_tablename, v_control); IF v_control_type <> 'time' THEN IF (v_control_type = 'id' AND v_epoch = 'none') OR v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run on partition set without time based control column or epoch flag set with an id column. Found control: %, epoch: %', v_control_type, v_epoch; END IF; END IF; -- Replace the parent variables with the source variables if using source table for child table data IF p_source_table IS NOT NULL THEN -- Set source table to user given source table instead of parent table v_source_schemaname := NULL; v_source_tablename := NULL; SELECT schemaname, tablename INTO v_source_schemaname, v_source_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_source_table, '.', 1)::name AND tablename = split_part(p_source_table, '.', 2)::name; IF v_source_tablename IS NULL THEN RAISE EXCEPTION 'Given source table does not exist in system catalogs: %', p_source_table; END IF; ELSIF v_partition_type = 'native' AND current_setting('server_version_num')::int >= 110000 THEN IF p_batch_interval IS NOT NULL AND p_batch_interval != v_partition_interval THEN -- This is true because all data for a given child table must be moved out of the default partition before the child table can be created. -- So cannot create the child table when only some of the data has been moved out of the default partition. RAISE EXCEPTION 'Custom intervals are not allowed when moving data out of the DEFAULT partition in a native set. Please leave p_interval/p_batch_interval parameters unset or NULL to allow use of partition set''s default partitioning interval.'; END IF; -- Set source table to default table if PG11+, p_source_table is not set, and it exists -- Otherwise just return with a DEBUG that no data source exists v_sql := format('SELECT n.nspname::text, c.relname::text FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = ''%I.%I''::regclass AND pg_get_expr(relpartbound, c.oid) = ''DEFAULT''' , v_source_schemaname , v_source_tablename); EXECUTE v_sql INTO v_default_schemaname, v_default_tablename; IF v_default_tablename IS NOT NULL THEN v_source_schemaname := v_default_schemaname; v_source_tablename := v_default_tablename; v_default_exists := true; EXECUTE format ('CREATE TEMP TABLE IF NOT EXISTS partman_temp_data_storage (LIKE %I.%I INCLUDING INDEXES) ON COMMIT DROP', v_source_schemaname, v_source_tablename); ELSE RAISE DEBUG 'No default table found when partition_data_id() was called'; RETURN v_total_rows; END IF; END IF; 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'); IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) END; -- Generate column list to use in SELECT/INSERT statements below. Allows for exclusion of GENERATED (or any other desired) columns. v_sql := format ('SELECT ''"''||string_agg(attname, ''","'')||''"'' 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 n.nspname = %L AND c.relname = %L AND a.attnum > 0 AND a.attisdropped = false' , v_source_schemaname , v_source_tablename); IF p_ignored_columns IS NOT NULL THEN FOREACH v_col IN ARRAY p_ignored_columns LOOP v_sql := v_sql || format(' AND attname != %L ', v_col); END LOOP; END IF; EXECUTE v_sql INTO v_column_list; FOR i IN 1..p_batch_count LOOP IF p_order = 'ASC' THEN EXECUTE format('SELECT min(%s) FROM ONLY %I.%I', v_partition_expression, v_source_schemaname, v_source_tablename) INTO v_start_control; ELSIF p_order = 'DESC' THEN EXECUTE format('SELECT max(%s) FROM ONLY %I.%I', v_partition_expression, v_source_schemaname, v_source_tablename) INTO v_start_control; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; IF v_start_control IS NULL THEN EXIT; END IF; IF v_partition_type = 'partman' THEN CASE WHEN v_partition_interval = '15 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '15min'::interval * floor(date_part('minute', v_start_control) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '30min'::interval * floor(date_part('minute', v_start_control) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control); WHEN v_partition_interval = '1 day' THEN v_min_partition_timestamp := date_trunc('day', v_start_control); WHEN v_partition_interval = '1 week' THEN v_min_partition_timestamp := date_trunc('week', v_start_control); WHEN v_partition_interval = '1 month' THEN v_min_partition_timestamp := date_trunc('month', v_start_control); WHEN v_partition_interval = '3 months' THEN v_min_partition_timestamp := date_trunc('quarter', v_start_control); WHEN v_partition_interval = '1 year' THEN v_min_partition_timestamp := date_trunc('year', v_start_control); END CASE; ELSIF v_partition_type IN ('time-custom', 'native') THEN SELECT child_start_time INTO v_min_partition_timestamp FROM @extschema@.show_partition_info(v_source_schemaname||'.'||v_last_partition , v_partition_interval::text , p_parent_table); v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; LOOP IF v_start_control >= v_min_partition_timestamp AND v_start_control < v_max_partition_timestamp THEN EXIT; ELSE BEGIN IF v_start_control >= v_max_partition_timestamp THEN -- Keep going forward in time, checking if child partition time interval encompasses the current v_start_control value v_min_partition_timestamp := v_max_partition_timestamp; v_max_partition_timestamp := v_max_partition_timestamp + v_partition_interval; ELSE -- Keep going backwards in time, checking if child partition time interval encompasses the current v_start_control value v_max_partition_timestamp := v_min_partition_timestamp; v_min_partition_timestamp := v_min_partition_timestamp - v_partition_interval; END IF; EXCEPTION WHEN datetime_field_overflow THEN RAISE EXCEPTION 'Attempted partition time interval is outside PostgreSQL''s supported time range. Unable to create partition with interval before timestamp % ', v_min_partition_timestamp; END; END IF; END LOOP; END IF; v_partition_timestamp := ARRAY[v_min_partition_timestamp]; IF p_order = 'ASC' THEN -- Ensure batch interval given as parameter doesn't cause maximum to overflow the current partition maximum IF (v_start_control + p_batch_interval) >= (v_min_partition_timestamp + v_partition_interval) THEN v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; ELSE v_max_partition_timestamp := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; -- Ensure batch interval given as parameter doesn't cause minimum to underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_timestamp THEN v_min_partition_timestamp = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT %s FROM ONLY %I.%I WHERE %s >= %L AND %3$s < %5$L FOR UPDATE NOWAIT' , v_column_list , v_source_schemaname , v_source_tablename , v_partition_expression , v_min_partition_timestamp , v_max_partition_timestamp); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; -- This suffix generation code is in create_partition_time() as well v_partition_suffix := to_char(v_min_partition_timestamp, v_datetime_string); v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); IF v_default_exists THEN -- Child tables cannot be created in native partitioning if data that belongs to it exists in the default -- Have to move data out to temporary location, create child table, then move it back -- Temp table created above to avoid excessive temp creation in loop EXECUTE format('WITH partition_data AS ( DELETE FROM %1$I.%2$I WHERE %3$s >= %4$L AND %3$s < %5$L RETURNING *) INSERT INTO partman_temp_data_storage (%6$s) SELECT %6$s FROM partition_data' , v_source_schemaname , v_source_tablename , v_partition_expression , v_min_partition_timestamp , v_max_partition_timestamp , v_column_list); PERFORM @extschema@.create_partition_time(p_parent_table, v_partition_timestamp, p_analyze); EXECUTE format('WITH partition_data AS ( DELETE FROM partman_temp_data_storage RETURNING *) INSERT INTO %I.%I (%3$s) SELECT %3$s FROM partition_data' , v_source_schemaname , v_current_partition_name , v_column_list); ELSE PERFORM @extschema@.create_partition_time(p_parent_table, v_partition_timestamp, p_analyze); EXECUTE format('WITH partition_data AS ( DELETE FROM ONLY %I.%I WHERE %s >= %L AND %3$s < %5$L RETURNING *) INSERT INTO %1$I.%6$I (%7$s) SELECT %7$s FROM partition_data' , v_source_schemaname , v_source_tablename , v_partition_expression , v_min_partition_timestamp , v_max_partition_timestamp , v_current_partition_name , v_column_list); END IF; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; IF v_partition_type IN ('partman', 'time-custom') THEN PERFORM @extschema@.create_function_time(p_parent_table); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_total_rows; END $$; CREATE FUNCTION @extschema@.run_maintenance(p_parent_table text DEFAULT NULL, p_analyze boolean DEFAULT NULL, p_jobmon boolean DEFAULT true) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_analyze boolean; v_check_subpart int; v_control_type text; v_create_count int := 0; v_current_partition text; v_current_partition_id bigint; v_current_partition_timestamp timestamptz; v_default_tablename text; v_drop_count int := 0; v_is_default text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_created boolean; v_last_partition_id bigint; v_last_partition_timestamp timestamptz; v_max_id_parent bigint; v_max_time_default timestamptz; v_new_search_path text := '@extschema@,pg_temp'; v_next_partition_id bigint; v_next_partition_timestamp timestamptz; v_old_search_path text; v_parent_exists text; v_parent_oid oid; v_parent_schema text; v_parent_tablename text; v_partition_expression text; v_premade_count int; v_premake_id_max bigint; v_premake_id_min bigint; v_premake_timestamp_min timestamptz; v_premake_timestamp_max timestamptz; v_row record; v_row_max_id record; v_row_max_time record; v_row_sub record; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_sub_id_max bigint; v_sub_id_max_suffix bigint; v_sub_id_min bigint; v_sub_parent text; v_sub_refresh_done text[]; v_sub_timestamp_max timestamptz; v_sub_timestamp_max_suffix timestamptz; v_sub_timestamp_min timestamptz; v_tablename text; v_tables_list_sql text; BEGIN /* * Function to manage pre-creation of the next partitions in a set. * Also manages dropping old partitions if the retention option is set. * If p_parent_table is passed, will only run run_maintenance() on that one table (no matter what the configuration table may have set for it) * Otherwise, will run on all tables in the config table with p_automatic_maintenance() set to true. * For large partition sets, running analyze can cause maintenance to take longer than expected. Can set p_analyze to false to avoid a forced analyze run on PG versions before 11. 11+ does not analyze by default anymore. * Be aware that constraint exclusion may not work properly until an analyze on the partition set is run. */ v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman maintenance already running.'; RETURN; 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'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN RUN MAINTENANCE'); v_step_id := add_step(v_job_id, 'Running maintenance loop'); END IF; v_tables_list_sql := 'SELECT parent_table , partition_type , partition_interval , control , premake , undo_in_progress , sub_partition_set_full , epoch , infinite_time_partitions , retention , subscription_refresh FROM @extschema@.part_config WHERE undo_in_progress = false'; IF p_parent_table IS NULL THEN v_tables_list_sql := v_tables_list_sql || ' AND automatic_maintenance = ''on'''; ELSE v_tables_list_sql := v_tables_list_sql || format(' AND parent_table = %L', p_parent_table); END IF; FOR v_row IN EXECUTE v_tables_list_sql LOOP CONTINUE WHEN v_row.undo_in_progress; -- When sub-partitioning, retention may drop tables that were already put into the query loop values. -- Check if they still exist in part_config before continuing v_parent_exists := NULL; SELECT parent_table INTO v_parent_exists FROM @extschema@.part_config WHERE parent_table = v_row.parent_table; RAISE DEBUG 'Parent table possibly removed from part_config by retenion'; CONTINUE WHEN v_parent_exists IS NULL; -- Check for consistent data in part_config_sub table. Was unable to get this working properly as either a constraint or trigger. -- Would either delay raising an error until the next write (which I cannot predict) or disallow future edits to update a sub-partition set's configuration. -- This way at least provides a consistent way to check that I know will run. If anyone can get a working constraint/trigger, please help! SELECT sub_parent INTO v_sub_parent FROM @extschema@.part_config_sub WHERE sub_parent = v_row.parent_table; IF v_sub_parent IS NOT NULL THEN SELECT count(*) INTO v_check_subpart FROM @extschema@.check_subpart_sameconfig(v_row.parent_table); IF v_check_subpart > 1 THEN RAISE EXCEPTION 'Inconsistent data in part_config_sub table. Sub-partition tables that are themselves sub-partitions cannot have differing configuration values among their siblings. Run this query: "SELECT * FROM @extschema@.check_subpart_sameconfig(''%'');" This should only return a single row or nothing. If multiple rows are returned, the results are differing configurations in the part_config_sub table for children of the given parent. Determine the child tables of the given parent and look up their entries based on the "part_config_sub.sub_parent" column. Update the differing values to be consistent for your desired values.', v_row.parent_table; END IF; END IF; -- Shouldn't need to analyze tables for most statistics for native sets on PG11+ by default anymore IF p_analyze IS NULL THEN IF v_row.partition_type = 'native' AND current_setting('server_version_num')::int >= 110000 THEN v_analyze := false; ELSE v_analyze := true; END IF; END IF; SELECT n.nspname, c.relname, c.oid INTO v_parent_schema, v_parent_tablename, 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(v_row.parent_table, '.', 1)::name AND c.relname = split_part(v_row.parent_table, '.', 2)::name; -- Used below to see if there's any data in the parent (<=PG10) or default (PG11+) child table. IF v_row.partition_type = 'native' AND current_setting('server_version_num')::int >= 110000 THEN -- Always returns the default partition first if it exists SELECT partition_tablename INTO v_default_tablename FROM @extschema@.show_partitions(v_row.parent_table, p_include_default := true) LIMIT 1; SELECT pg_get_expr(relpartbound, v_parent_oid) INTO v_is_default FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n on c.relnamespace = n.oid WHERE n.nspname = v_parent_schema AND c.relname = v_default_tablename; IF v_is_default != 'DEFAULT' THEN v_default_tablename := v_parent_tablename; END IF; ELSE v_default_tablename := v_parent_tablename; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_row.control); v_partition_expression := CASE WHEN v_row.epoch = 'seconds' THEN format('to_timestamp(%I)', v_row.control) WHEN v_row.epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_row.control) ELSE format('%I', v_row.control) END; RAISE DEBUG 'run_maint: v_partition_expression: %', v_partition_expression; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LIMIT 1; RAISE DEBUG 'run_maint: parent_table: %, v_last_partition: %', v_row.parent_table, v_last_partition; IF v_control_type = 'time' OR (v_control_type = 'id' AND v_row.epoch <> 'none') THEN -- Run retention if needed IF v_row.retention IS NOT NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); IF v_drop_count > 0 AND v_row.partition_type <> 'native' THEN PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); END IF; END IF; IF v_row.sub_partition_set_full THEN CONTINUE; END IF; SELECT child_start_time INTO v_last_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_row.partition_interval, v_row.parent_table); -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. IF v_row.infinite_time_partitions IS TRUE THEN -- Set it to "now" so new partitions continue to be created -- For infinite_time_partitions, don't bother getting the max value in the partitions v_current_partition_timestamp = CURRENT_TIMESTAMP; ELSE FOR v_row_max_time IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%s)::text FROM %I.%I' , v_partition_expression , v_row_max_time.partition_schemaname , v_row_max_time.partition_tablename ) INTO v_current_partition_timestamp; IF v_current_partition_timestamp IS NOT NULL THEN SELECT suffix_timestamp INTO v_current_partition_timestamp FROM @extschema@.show_partition_name(v_row.parent_table, v_current_partition_timestamp::text); EXIT; END IF; END LOOP; END IF; -- end infinite time check -- Check for values in the parent/default table. If they are there and greater than all child values, use that instead -- This allows maintenance to continue working properly if there is a large gap in data insertion. Data will remain in default, but new tables will be created EXECUTE format('SELECT max(%s) FROM ONLY %I.%I', v_partition_expression, v_parent_schema, v_default_tablename) INTO v_max_time_default; RAISE DEBUG 'run_maint: v_current_partition_timestamp: %, v_max_time_default: %', v_current_partition_timestamp, v_max_time_default; IF v_current_partition_timestamp IS NULL AND v_max_time_default IS NULL THEN -- Partition set is completely empty and infinite time partitions not set -- Nothing to do CONTINUE; END IF; IF v_current_partition_timestamp IS NULL OR (v_max_time_default > v_current_partition_timestamp) THEN SELECT suffix_timestamp INTO v_current_partition_timestamp FROM @extschema@.show_partition_name(v_row.parent_table, v_max_time_default::text); END IF; -- If this is a subpartition, determine if the last child table has been made. If so, mark it as full so future maintenance runs can skip it SELECT sub_min::timestamptz, sub_max::timestamptz INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'time'); IF v_sub_timestamp_max IS NOT NULL THEN SELECT suffix_timestamp INTO v_sub_timestamp_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_timestamp_max::text); IF v_sub_timestamp_max_suffix = v_last_partition_timestamp THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; -- Check and see how many premade partitions there are. v_premade_count = round(EXTRACT('epoch' FROM age(v_last_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); v_next_partition_timestamp := v_last_partition_timestamp; RAISE DEBUG 'run_maint before loop: current_partition_timestamp: %, v_premade_count: %, v_sub_timestamp_min: %, v_sub_timestamp_max: %' , v_current_partition_timestamp , v_premade_count , v_sub_timestamp_min , v_sub_timestamp_max; -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed WHILE (v_premade_count < v_row.premake) LOOP RAISE DEBUG 'run_maint: parent_table: %, v_premade_count: %, v_next_partition_timestamp: %', v_row.parent_table, v_premade_count, v_next_partition_timestamp; IF v_next_partition_timestamp < v_sub_timestamp_min OR v_next_partition_timestamp > v_sub_timestamp_max THEN -- With subpartitioning, no need to run if the timestamp is not in the parent table's range EXIT; END IF; BEGIN v_next_partition_timestamp := v_next_partition_timestamp + v_row.partition_interval::interval; EXCEPTION WHEN datetime_field_overflow THEN v_premade_count := v_row.premake; -- do this so it can exit the premake check loop and continue in the outer for loop IF v_jobmon_schema IS NOT NULL THEN 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', format('Child partition creation skipped for parent table: %s', v_partition_time)); END IF; RAISE WARNING 'Attempted partition time interval is outside PostgreSQL''s supported time range. Child partition creation skipped for parent table %', v_row.parent_table; CONTINUE; END; v_last_partition_created := @extschema@.create_partition_time(v_row.parent_table , ARRAY[v_next_partition_timestamp] , v_analyze); IF v_last_partition_created THEN v_create_count := v_create_count + 1; IF v_row.partition_type <> 'native' THEN PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); END IF; END IF; v_premade_count = round(EXTRACT('epoch' FROM age(v_next_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); END LOOP; ELSIF v_control_type = 'id' THEN -- Run retention if needed IF v_row.retention IS NOT NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); IF v_drop_count > 0 AND v_row.partition_type <> 'native' THEN PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); END IF; END IF; IF v_row.sub_partition_set_full THEN CONTINUE; END IF; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%I)::text FROM %I.%I' , v_row.control , v_row_max_id.partition_schemaname , v_row_max_id.partition_tablename) INTO v_current_partition_id; IF v_current_partition_id IS NOT NULL THEN SELECT suffix_id INTO v_current_partition_id FROM @extschema@.show_partition_name(v_row.parent_table, v_current_partition_id::text); EXIT; END IF; END LOOP; -- Check for values in the parent/default table. If they are there and greater than all child values, use that instead -- This allows maintenance to continue working properly if there is a large gap in data insertion. Data will remain in parent, but new tables will be created EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_row.control, v_parent_schema, v_default_tablename) INTO v_max_id_parent; IF v_max_id_parent > v_current_partition_id THEN SELECT suffix_id INTO v_current_partition_id FROM @extschema@.show_partition_name(v_row.parent_table, v_max_id_parent::text); END IF; IF v_current_partition_id IS NULL THEN -- Partition set is completely empty. Nothing to do CONTINUE; END IF; SELECT child_start_id INTO v_last_partition_id FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_row.partition_interval, v_row.parent_table); -- Determine if this table is a child of a subpartition parent. If so, get limits to see if run_maintenance even needs to run for it. SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'id'); IF v_sub_id_max IS NOT NULL THEN SELECT suffix_id INTO v_sub_id_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_id_max::text); IF v_sub_id_max_suffix = v_last_partition_id THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; v_next_partition_id := v_last_partition_id; v_premade_count := ((v_last_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed. WHILE (v_premade_count < v_row.premake) LOOP RAISE DEBUG 'run_maint: parent_table: %, v_premade_count: %, v_next_partition_id: %', v_row.parent_table, v_premade_count, v_next_partition_id; IF v_next_partition_id < v_sub_id_min OR v_next_partition_id > v_sub_id_max THEN -- With subpartitioning, no need to run if the id is not in the parent table's range EXIT; END IF; v_next_partition_id := v_next_partition_id + v_row.partition_interval::bigint; v_last_partition_created := @extschema@.create_partition_id(v_row.parent_table, ARRAY[v_next_partition_id], v_analyze); IF v_last_partition_created THEN v_create_count := v_create_count + 1; IF v_row.partition_type <> 'native' THEN PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); END IF; END IF; v_premade_count := ((v_next_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); END LOOP; END IF; -- end main IF check for time or id -- Refresh subscriptions in order to catch new tables that may have been created in the publication -- Keep track of which ones have been refreshed so it doesn't needlessly run more than once -- in a single maintenance run IF v_row.subscription_refresh IS NOT NULL THEN IF v_sub_refresh_done @> ARRAY[v_row.subscription_refresh] THEN CONTINUE; ELSE v_sql = format('ALTER SUBSCRIPTION %I REFRESH PUBLICATION', v_row.subscription_refresh); RAISE DEBUG '%', v_sql; EXECUTE v_sql; PERFORM array_append(v_sub_refresh_done, v_row.subscription_refresh); END IF; END IF; END LOOP; -- end of main loop through part_config IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Partition maintenance finished. %s partitions made. %s partitions dropped.', v_create_count, v_drop_count)); 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'); 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 RUN MAINTENANCE'')', v_jobmon_schema) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE FUNCTION @extschema@.undo_partition(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval text DEFAULT NULL, p_keep_table boolean DEFAULT true, p_lock_wait numeric DEFAULT 0, p_target_table text DEFAULT NULL, p_ignored_columns text[] DEFAULT NULL, OUT partitions_undone int, OUT rows_undone bigint) RETURNS record LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_batch_interval_id bigint; v_batch_interval_time interval; v_batch_loop_count int := 0; v_child_loop_total bigint := 0; v_child_table text; v_col text; v_column_list text; v_control text; v_control_type text; v_child_min_id bigint; v_child_min_time timestamptz; v_epoch text; v_function_name text; v_jobmon boolean; v_jobmon_schema text; v_job_id bigint; v_inner_loop_count int; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_new_search_path text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_expression text; v_partition_interval text; v_partition_type text; v_relkind char; v_row record; v_rowcount bigint; v_sql text; v_step_id bigint; v_sub_count int; v_target_schema text; v_target_tablename text; v_template_schema text; v_template_siblings int; v_template_table text; v_template_tablename text; v_total bigint := 0; v_trig_name text; v_undo_count int := 0; BEGIN /* * For native, moves data to new, target table since data cannot be moved to parent. * Leaves old parent table as is and does not change name of new table. * For trigger-based, moves data to parent */ v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition_native')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition_native already running.'; partitions_undone = -1; RETURN; END IF; IF p_parent_table = p_target_table THEN RAISE EXCEPTION 'Target table cannot be the same as the parent table'; END IF; SELECT partition_interval::text , partition_type , control , jobmon , epoch , template_table INTO v_partition_interval , v_partition_type , v_control , v_jobmon , v_epoch , v_template_table FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_control IS NULL THEN RAISE EXCEPTION 'No configuration found for pg_partman for given parent table: %', p_parent_table; END IF; IF v_partition_type = 'native' AND p_target_table IS NULL THEN RAISE EXCEPTION 'Natively partitioned tables require setting the p_target_table option'; END IF; SELECT n.nspname, c.relname, c.relkind INTO v_parent_schema, v_parent_tablename, v_relkind 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_tablename IS NULL THEN RAISE EXCEPTION 'Given parent table not found in system catalogs: %', p_parent_table; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN IF p_batch_interval IS NULL THEN v_batch_interval_time := v_partition_interval::interval; ELSE v_batch_interval_time := p_batch_interval::interval; END IF; ELSIF v_control_type = 'id' THEN IF p_batch_interval IS NULL THEN v_batch_interval_id := v_partition_interval::bigint; ELSE v_batch_interval_id := p_batch_interval::bigint; END IF; ELSE RAISE EXCEPTION 'Data type of control column in given partition set must be either data/time or integer.'; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Check if any child tables are themselves partitioned or part of an inheritance tree. Prevent undo at this level if so. -- Need to lock child tables at all levels before multi-level undo can be performed safely. FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table) LOOP SELECT count(*) INTO v_sub_count FROM pg_catalog.pg_inherits i JOIN pg_catalog.pg_class c ON i.inhparent = c.oid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_row.partition_tablename::name AND n.nspname = v_row.partition_schemaname::name; IF v_sub_count > 0 THEN RAISE EXCEPTION 'Child table for this parent has child table(s) itself (%). Run undo partitioning on this table or remove inheritance first to ensure all data is properly moved to parent', v_row.partition_schemaname||'.'||v_row.partition_tablename; END IF; END LOOP; IF p_target_table IS NOT NULL THEN SELECT n.nspname, c.relname INTO v_target_schema, v_target_tablename FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = split_part(p_target_table, '.', 1)::name AND c.relname = split_part(p_target_table, '.', 2)::name; ELSE v_target_schema := v_parent_schema; v_target_tablename := v_parent_tablename; END IF; IF v_target_tablename IS NULL THEN RAISE EXCEPTION 'Given target table not found in system catalogs: %', p_target_table; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN UNDO PARTITIONING: %s', p_parent_table)); v_step_id := add_step(v_job_id, format('Undoing partitioning for table %s', p_parent_table)); END IF; v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) END; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; IF v_partition_type != 'native' THEN -- Stop data going into child tables on non-native partition sets. v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); -- Double-check for proper object existence SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t JOIN pg_catalog.pg_class c ON t.tgrelid = c.oid WHERE tgname = v_trig_name::name AND c.relname = v_parent_tablename::name; SELECT proname INTO v_function_name FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid WHERE n.nspname = v_parent_schema::name AND proname = v_function_name::name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_parent_tablename); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; partitions_undone = -1; RETURN; END IF; END IF; -- END p_lock_wait IF EXECUTE format('DROP TRIGGER IF EXISTS %I ON %I.%I', v_trig_name, v_parent_schema, v_parent_tablename); END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later IF v_function_name IS NOT NULL THEN EXECUTE format('DROP FUNCTION IF EXISTS %I.%I()', v_parent_schema, v_function_name); END IF; END IF; -- end pg_partman trigger cleanup IF v_jobmon_schema IS NOT NULL THEN IF (v_trig_name IS NOT NULL OR v_function_name IS NOT NULL) THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); ELSE PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process.'); END IF; END IF; -- Generate column list to use in SELECT/INSERT statements below. Allows for exclusion of GENERATED (or any other desired) columns. v_sql := format ('SELECT ''"''||string_agg(attname, ''","'')||''"'' 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 n.nspname = %L AND c.relname = %L AND a.attnum > 0 AND a.attisdropped = false' , v_target_schema , v_target_tablename); IF p_ignored_columns IS NOT NULL THEN FOREACH v_col IN ARRAY p_ignored_columns LOOP v_sql := v_sql || format(' AND attname != %L ', v_col); END LOOP; END IF; EXECUTE v_sql INTO v_column_list; <> LOOP -- Get ordered list of child table in set. Store in variable one at a time per loop until none are left or batch count is reached. -- This easily allows it to loop over same child table until empty or move onto next child table after it's dropped -- Include the native default table to ensure all data there is removed as well (final parameter = true) SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC', TRUE) LIMIT 1; EXIT outer_child_loop WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Removing child partition: %s.%s', v_parent_schema, v_child_table)); END IF; IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN EXECUTE format('SELECT min(%s) FROM %I.%I', v_partition_expression, v_parent_schema, v_child_table) INTO v_child_min_time; ELSIF v_control_type = 'id' THEN EXECUTE format('SELECT min(%s) FROM %I.%I', v_partition_expression, v_parent_schema, v_child_table) INTO v_child_min_id; END IF; IF v_child_min_time IS NULL AND v_child_min_id IS NULL THEN -- No rows left in this child table. Remove from partition set. -- lockwait timeout for table drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_child_table); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; partitions_undone = -1; RETURN; END IF; END IF; -- END p_lock_wait IF v_lock_obtained := FALSE; -- reset for reuse later IF v_partition_type = 'native' THEN EXECUTE format('ALTER TABLE %I.%I DETACH PARTITION %I.%I' , v_parent_schema , v_parent_tablename , v_parent_schema , v_child_table); ELSE EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_parent_schema , v_child_table , v_parent_schema , v_parent_tablename); END IF; IF p_keep_table = false THEN EXECUTE format('DROP TABLE %I.%I', v_parent_schema, v_child_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table DROPPED. Moved %s rows to target table', v_child_loop_total)); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table DETACHED/UNINHERITED from parent, not DROPPED. Moved %s rows to target table', v_child_loop_total)); END IF; END IF; IF v_partition_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_parent_schema||'.'||v_child_table; END IF; v_undo_count := v_undo_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached CONTINUE outer_child_loop; -- skip data moving steps below END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM %I.%I WHERE %I <= %L FOR UPDATE NOWAIT' , v_parent_schema , v_child_table , v_control , v_child_min_time + (v_batch_interval_time * v_inner_loop_count)); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; partitions_undone = -1; RETURN; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval EXECUTE format('WITH move_data AS ( DELETE FROM %I.%I WHERE %s <= %L RETURNING %s ) INSERT INTO %I.%I (%5$s) SELECT %5$s FROM move_data' , v_parent_schema , v_child_table , v_partition_expression , v_child_min_time + (v_batch_interval_time * v_inner_loop_count) , v_column_list , v_target_schema , v_target_tablename); GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Moved %s rows to target table.', v_child_loop_total)); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; -- Check again if table is empty and go to outer loop again to drop it if so EXECUTE format('SELECT min(%s) FROM %I.%I', v_partition_expression, v_parent_schema, v_child_table) INTO v_child_min_time; CONTINUE outer_child_loop WHEN v_child_min_time IS NULL; ELSIF v_control_type = 'id' THEN IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM %I.%I WHERE %I <= %L FOR UPDATE NOWAIT' , v_parent_schema , v_child_table , v_control , v_child_min_id + (v_batch_interval_id * v_inner_loop_count)); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; partitions_undone = -1; RETURN; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval EXECUTE format('WITH move_data AS ( DELETE FROM %I.%I WHERE %s <= %L RETURNING %s) INSERT INTO %I.%I (%5$s) SELECT %5$s FROM move_data' , v_parent_schema , v_child_table , v_partition_expression , v_child_min_id + (v_batch_interval_id * v_inner_loop_count) , v_column_list , v_target_schema , v_target_tablename); GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Moved %s rows to target table.', v_child_loop_total)); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; -- Check again if table is empty and go to outer loop again to drop it if so EXECUTE format('SELECT min(%s) FROM %I.%I', v_partition_expression, v_parent_schema, v_child_table) INTO v_child_min_id; CONTINUE outer_child_loop WHEN v_child_min_id IS NULL; END IF; -- end v_control_type check EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC', TRUE) LIMIT 1; IF v_child_table IS NULL THEN DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; -- Check if any other config entries still have this template table and don't remove if so -- Allows other sibling/parent tables to still keep using in case entire partition set isn't being undone SELECT count(*) INTO v_template_siblings FROM @extschema@.part_config WHERE template_table = v_template_table; 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(v_template_table, '.', 1)::name AND c.relname = split_part(v_template_table, '.', 2)::name; IF v_template_siblings = 0 AND v_template_tablename IS NOT NULL THEN EXECUTE format('DROP TABLE IF EXISTS %I.%I', v_template_schema, v_template_tablename); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Moved % row(s) to the target table. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', format('Moved %s row(s) to the target table. Removed %s partitions.', v_total, v_undo_count)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); partitions_undone := v_undo_count; rows_undone := v_total; 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 UNDO PARTITIONING: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; -- Restore dropped object privileges DO $$ DECLARE v_row record; BEGIN FOR v_row IN SELECT statement FROM partman_preserve_privs_temp LOOP IF v_row.statement IS NOT NULL THEN EXECUTE v_row.statement; END IF; END LOOP; END $$; TRUNCATE partman_preserve_privs_temp; -- ######################## START POSTGRESQL 11 ONLY SECTION ############################## DO $pg11only$ DECLARE v_partition_data_proc_sql text; v_undo_partition_proc_sql text; v_run_maintenance_proc_sql text; BEGIN IF current_setting('server_version_num')::int >= 110000 THEN -- ######################## START POSTGRESQL 11 ONLY SECTION ############################## INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON PROCEDURE @extschema@.partition_data_proc(text, text, int, int, text, text, int, int, boolean, text[]) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'partition_data_proc'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON PROCEDURE @extschema@.undo_partition_proc(text, text, int, int, text, boolean, int, int, boolean, text[]) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'undo_partition_proc'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON PROCEDURE @extschema@.run_maintenance_proc(int, boolean, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'run_maintenance_proc'; DROP PROCEDURE @extschema@.partition_data_proc (text, text, int, int, text, text, int, int, boolean); DROP PROCEDURE @extschema@.undo_partition_proc(text, text, int, int, text, boolean, int, int, boolean); DROP PROCEDURE @extschema@.run_maintenance_proc(int, boolean, boolean, boolean); v_partition_data_proc_sql := $partition_data_proc$ CREATE PROCEDURE @extschema@.partition_data_proc (p_parent_table text, p_interval text DEFAULT NULL, p_batch int DEFAULT NULL, p_wait int DEFAULT 1, p_source_table text DEFAULT NULL, p_order text DEFAULT 'ASC', p_lock_wait int DEFAULT 0, p_lock_wait_tries int DEFAULT 10, p_quiet boolean DEFAULT false, p_ignored_columns text[] DEFAULT NULL) LANGUAGE plpgsql AS $$ DECLARE v_adv_lock boolean; v_batch_count int := 0; v_control text; v_control_type text; v_epoch text; v_is_autovac_off boolean := false; v_lockwait_count int := 0; v_parent_schema text; v_parent_tablename text; v_row record; v_rows_moved bigint; v_source_schema text; v_source_tablename text; v_sql text; v_total bigint := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman partition_data_proc'), hashtext(p_parent_table)); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman partition_data_proc already running for given parent table: %.', p_parent_table; RETURN; END IF; SELECT control, epoch INTO v_control, v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: No entry in part_config found for given table: %', p_parent_table; END IF; SELECT n.nspname, c.relname INTO v_parent_schema, v_parent_tablename 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_tablename IS NULL THEN RAISE EXCEPTION 'Unable to find given parent table in system catalogs. Ensure it is schema qualified: %', p_parent_table; END IF; IF p_source_table IS NOT NULL THEN SELECT n.nspname, c.relname INTO v_source_schema, v_source_tablename FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = split_part(p_source_table, '.', 1)::name AND c.relname = split_part(p_source_table, '.', 2)::name; IF v_source_tablename IS NULL THEN RAISE EXCEPTION 'Unable to find given source table in system catalogs. Ensure it is schema qualified: %', p_source_table; END IF; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type = 'id' AND v_epoch <> 'none' THEN v_control_type := 'time'; END IF; /* -- Currently no way to catch exception and reset autovac settings back to normal. Until I can do that, leaving this feature out for now -- Leaving the functions to turn off/reset in to let people do that manually if desired IF p_autovacuum_on = false THEN -- Add this parameter back to definition when this is working -- Turn off autovac for parent, source table if set, and all child tables v_is_autovac_off := @extschema@.autovacuum_off(v_parent_schema, v_parent_tablename, v_source_schema, v_source_tablename); COMMIT; END IF; */ v_sql := format('SELECT %I.partition_data_%s (%L, p_lock_wait := %L, p_order := %L, p_analyze := false' , '@extschema@', v_control_type, p_parent_table, p_lock_wait, p_order); IF p_interval IS NOT NULL THEN v_sql := v_sql || format(', p_batch_interval := %L', p_interval); END IF; IF p_source_table IS NOT NULL THEN v_sql := v_sql || format(', p_source_table := %L', p_source_table); END IF; IF p_ignored_columns IS NOT NULL THEN v_sql := v_sql || format(', p_ignored_columns := %L', p_ignored_columns); END IF; v_sql := v_sql || ')'; RAISE DEBUG 'partition_data sql: %', v_sql; LOOP EXECUTE v_sql INTO v_rows_moved; -- If lock wait timeout, do not increment the counter IF v_rows_moved != -1 THEN v_batch_count := v_batch_count + 1; v_total := v_total + v_rows_moved; v_lockwait_count := 0; ELSE v_lockwait_count := v_lockwait_count + 1; IF v_lockwait_count > p_lock_wait_tries THEN RAISE EXCEPTION 'Quitting due to inability to get lock on next batch of rows to be moved'; END IF; END IF; IF p_quiet = false THEN IF v_rows_moved > 0 THEN RAISE NOTICE 'Batch: %, Rows moved: %', v_batch_count, v_rows_moved; ELSIF v_rows_moved = -1 THEN RAISE NOTICE 'Unable to obtain row locks for data to be moved. Trying again...'; END IF; END IF; -- If no rows left or given batch argument limit is reached IF v_rows_moved = 0 OR (p_batch > 0 AND v_batch_count >= p_batch) THEN EXIT; END IF; COMMIT; PERFORM pg_sleep(p_wait); RAISE DEBUG 'v_rows_moved: %, v_batch_count: %, v_total: %, v_lockwait_count: %, p_wait: %', p_wait, v_rows_moved, v_batch_count, v_total, v_lockwait_count; END LOOP; /* IF v_is_autovac_off = true THEN -- Reset autovac back to default if it was turned off by this procedure PERFORM @extschema@.autovacuum_reset(v_parent_schema, v_parent_tablename, v_source_schema, v_source_tablename); COMMIT; END IF; */ IF p_quiet = false THEN RAISE NOTICE 'Total rows moved: %', v_total; END IF; RAISE NOTICE 'Ensure to VACUUM ANALYZE the parent (and source table if used) after partitioning data'; /* Leaving here until I can figure out what's wrong with procedures and exception handling EXCEPTION WHEN QUERY_CANCELED THEN ROLLBACK; -- Reset autovac back to default if it was turned off by this procedure IF v_is_autovac_off = true THEN PERFORM @extschema@.autovacuum_reset(v_parent_schema, v_parent_tablename, v_source_schema, v_source_tablename); END IF; RAISE EXCEPTION '%', SQLERRM; WHEN OTHERS THEN ROLLBACK; -- Reset autovac back to default if it was turned off by this procedure IF v_is_autovac_off = true THEN PERFORM @extschema@.autovacuum_reset(v_parent_schema, v_parent_tablename, v_source_schema, v_source_tablename); END IF; RAISE EXCEPTION '%', SQLERRM; */ END; $$; $partition_data_proc$; EXECUTE v_partition_data_proc_sql; v_undo_partition_proc_sql := $undo_partition_proc$ CREATE PROCEDURE @extschema@.undo_partition_proc(p_parent_table text, p_interval text DEFAULT NULL, p_batch int DEFAULT NULL, p_wait int DEFAULT 1, p_target_table text DEFAULT NULL, p_keep_table boolean DEFAULT true, p_lock_wait int DEFAULT 0, p_lock_wait_tries int DEFAULT 10, p_quiet boolean DEFAULT false, p_ignored_columns text[] DEFAULT NULL) LANGUAGE plpgsql AS $$ DECLARE v_adv_lock boolean; v_batch_count int := 0; v_is_autovac_off boolean := false; v_lockwait_count int := 0; v_parent_schema text; v_parent_tablename text; v_partition_type text; v_partitions_undone int; v_partitions_undone_total int := 0; v_row record; v_rows_undone bigint; v_target_schema text; v_target_tablename text; v_sql text; v_total bigint := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition_proc'), hashtext(p_parent_table)); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman partition_data_proc already running for given parent table: %.', p_parent_table; RETURN; END IF; SELECT partition_type INTO v_partition_type FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: No entry in part_config found for given table: %', p_parent_table; END IF; IF v_partition_type = 'native' AND p_target_table IS NULL THEN RAISE EXCEPTION 'Natively partitioned table sets require setting the p_target_table parameter to undo partitioning.'; END IF; SELECT n.nspname, c.relname INTO v_parent_schema, v_parent_tablename 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_tablename IS NULL THEN RAISE EXCEPTION 'Unable to find given parent table in system catalogs. Ensure it is schema qualified: %', p_parent_table; END IF; IF p_target_table IS NOT NULL THEN SELECT n.nspname, c.relname INTO v_target_schema, v_target_tablename FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = split_part(p_target_table, '.', 1)::name AND c.relname = split_part(p_target_table, '.', 2)::name; IF v_target_tablename IS NULL THEN RAISE EXCEPTION 'Unable to find given target table in system catalogs. Ensure it is schema qualified: %', p_target_table; END IF; END IF; /* -- Currently no way to catch exception and reset autovac settings back to normal. Until I can do that, leaving this feature out for now -- Leaving the functions to turn off/reset in to let people do that manually if desired IF p_autovacuum_on = false THEN -- Add this parameter back to definition when this is working -- Turn off autovac for parent, source table if set, and all child tables v_is_autovac_off := @extschema@.autovacuum_off(v_parent_schema, v_parent_tablename, v_source_schema, v_source_tablename); COMMIT; END IF; */ v_sql := format('SELECT partitions_undone, rows_undone FROM %I.undo_partition (%L, p_keep_table := %L, p_lock_wait := %L' , '@extschema@', p_parent_table, p_keep_table, p_lock_wait); IF p_interval IS NOT NULL THEN v_sql := v_sql || format(', p_batch_interval := %L', p_interval); END IF; IF p_target_table IS NOT NULL THEN v_sql := v_sql || format(', p_target_table := %L', p_target_table); END IF; IF p_ignored_columns IS NOT NULL THEN v_sql := v_sql || format(', p_ignored_columns := %L', p_ignored_columns); END IF; v_sql := v_sql || ')'; RAISE DEBUG 'partition_data sql: %', v_sql; LOOP EXECUTE v_sql INTO v_partitions_undone, v_rows_undone; -- If lock wait timeout, do not increment the counter IF v_rows_undone != -1 THEN v_batch_count := v_batch_count + 1; v_partitions_undone_total := v_partitions_undone_total + v_partitions_undone; v_total := v_total + v_rows_undone; v_lockwait_count := 0; ELSE v_lockwait_count := v_lockwait_count + 1; IF v_lockwait_count > p_lock_wait_tries THEN RAISE EXCEPTION 'Quitting due to inability to get lock on next batch of rows to be moved'; END IF; END IF; IF p_quiet = false THEN IF v_rows_undone > 0 THEN RAISE NOTICE 'Batch: %, Partitions undone this batch: %, Rows undone this batch: %', v_batch_count, v_partitions_undone, v_rows_undone; ELSIF v_rows_undone = -1 THEN RAISE NOTICE 'Unable to obtain row locks for data to be moved. Trying again...'; END IF; END IF; COMMIT; -- If no rows left or given batch argument limit is reached IF v_rows_undone = 0 OR (p_batch > 0 AND v_batch_count >= p_batch) THEN EXIT; END IF; -- undo_partition functions will remove config entry once last child is dropped -- Added here to handle edge-case SELECT partition_type INTO v_partition_type FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN EXIT; END IF; PERFORM pg_sleep(p_wait); RAISE DEBUG 'v_partitions_undone: %, v_rows_undone: %, v_batch_count: %, v_total: %, v_lockwait_count: %, p_wait: %', v_partitions_undone, p_wait, v_rows_undone, v_batch_count, v_total, v_lockwait_count; END LOOP; /* IF v_is_autovac_off = true THEN -- Reset autovac back to default if it was turned off by this procedure PERFORM @extschema@.autovacuum_reset(v_parent_schema, v_parent_tablename, v_source_schema, v_source_tablename); COMMIT; END IF; */ IF p_quiet = false THEN RAISE NOTICE 'Total partitions undone: %, Total rows moved: %', v_partitions_undone_total, v_total; END IF; RAISE NOTICE 'Ensure to VACUUM ANALYZE the old parent & target table after undo has finished'; END $$; $undo_partition_proc$; EXECUTE v_undo_partition_proc_sql; v_run_maintenance_proc_sql := $run_maintenance_proc$ CREATE PROCEDURE @extschema@.run_maintenance_proc(p_wait int DEFAULT 0, p_analyze boolean DEFAULT NULL, p_jobmon boolean DEFAULT true) LANGUAGE plpgsql AS $$ DECLARE v_adv_lock boolean; v_row record; v_sql text; v_tables_list_sql text; BEGIN v_adv_lock := pg_try_advisory_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = false THEN RAISE NOTICE 'Partman maintenance already running or another session has not released its advisory lock.'; RETURN; END IF; v_tables_list_sql := 'SELECT parent_table FROM @extschema@.part_config WHERE undo_in_progress = false AND automatic_maintenance = ''on'''; FOR v_row IN EXECUTE v_tables_list_sql LOOP /* * Run maintenance with a commit between each partition set * TODO - Once PG11 is more mainstream, see about more full conversion of run_maintenance function as well as turning * create_partition* functions into procedures to commit after every child table is made. May need to wait * for more PROCEDURE features as well (return values, search_path, etc). * - Also see about swapping names so this is the main object to call for maintenance instead of a function. */ v_sql := format('SELECT %I.run_maintenance(%L, p_jobmon := %L', '@extschema@', v_row.parent_table, p_jobmon); IF p_analyze IS NOT NULL THEN v_sql := v_sql || format(', p_analyze := %L', p_analyze); END IF; v_sql := v_sql || ')'; RAISE DEBUG 'v_sql run_maintenance_proc: %', v_sql; EXECUTE v_sql; COMMIT; PERFORM pg_sleep(p_wait); END LOOP; PERFORM pg_advisory_unlock(hashtext('pg_partman run_maintenance')); END $$; $run_maintenance_proc$; EXECUTE v_run_maintenance_proc_sql; -- Restore dropped object privileges DO $$ DECLARE v_row record; BEGIN FOR v_row IN SELECT statement FROM partman_preserve_privs_temp LOOP IF v_row.statement IS NOT NULL THEN EXECUTE v_row.statement; END IF; END LOOP; END $$; -- ######################## END POSTGRESQL 11 ONLY SECTION ############################## END IF; END $pg11only$; -- ######################## END POSTGRESQL 11 ONLY SECTION ############################## DROP TABLE IF EXISTS partman_preserve_privs_temp; pg_partman-4.6.0/updates/pg_partman--4.4.1--4.5.0.sql000066400000000000000000005607761412761706100214570ustar00rootroot00000000000000-- Allow upgrades for anyone that may have installed 4.4.1 -- 4.5.0 -- Officially supported minimal version is now PostgreSQL 9.6. PostgreSQL 9.6 support will be ending the next pg_partman release after PostgreSQL 14 is released. -- Fixed issue when upgrading from 4.4.0 to 4.4.1 on PG10 and below. Upgrade path now goes directly from 4.4.0 to 4.5.0 and skips 4.4.1. All changes in 4.4.1 have been included in 4.5.0 where necessary. (Github Issue #342) -- Added the parameter `p_ignored_columns` to partition_data_time(), partition_data_id(), partition_data_proc(), undo_partition() and undo_partition_proc(). The parameter is an array that can list one or more columns to ignore their values when moving the data. This allows for moving data out of the default/parent table when generated columns are in use. (Github Issue #336) -- Removed p_debug from functions changed in this update. Slowly migrating all functions away from parameter to using debug logging level instead. -- Fixed the p_start_partition parameter working properly with id->timestamp sub-partitioning. (Github Issue #341) CREATE TEMP TABLE partman_preserve_privs_temp (statement text); INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_sub_parent(text, text, text, text, text, text[], int, text, boolean, text, text, boolean, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_sub_parent'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_partition_id(text, bigint[], boolean, text) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_partition_id'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_partition_time(text, timestamptz[], boolean, text) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_partition_time'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_parent(text, text, text, text, text[], int, text, text, boolean, text, text, text[], boolean, text, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_parent'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.apply_constraints(text, text, boolean, bigint) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'apply_constraints'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.partition_data_id(text, int, bigint, numeric, text, boolean, text, text[]) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'partition_data_id'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.partition_data_time(text, int, interval, numeric, text, boolean, text, text[]) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'partition_data_time'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.run_maintenance(text, boolean, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'run_maintenance'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.run_maintenance(text, boolean, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'run_maintenance'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.undo_partition(text, int, text, boolean, numeric, text, text[]) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'undo_partition'; DROP FUNCTION @extschema@.create_partition_id(text, bigint[], boolean, boolean); DROP FUNCTION @extschema@.create_partition_time(text, timestamptz[], boolean, boolean); DROP FUNCTION @extschema@.create_sub_parent(text, text, text, text, text, text[], int, text, boolean, text, text, boolean, boolean, boolean); DROP FUNCTION @extschema@.create_parent(text, text, text, text, text[], int, text, text, boolean, text, text, text[], boolean, text, boolean, boolean); DROP FUNCTION @extschema@.apply_constraints(text, text, boolean, bigint, boolean); DROP FUNCTION @extschema@.partition_data_id(text, int, bigint, numeric, text, boolean, text); DROP FUNCTION @extschema@.partition_data_time(text, int, interval, numeric, text, boolean, text); DROP FUNCTION @extschema@.run_maintenance(text, boolean, boolean, boolean); DROP FUNCTION @extschema@.undo_partition(text, int, text, boolean, numeric, text); CREATE 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' 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 FUNCTION @extschema@.create_partition_id(p_parent_table text, p_partition_ids bigint[], p_analyze boolean DEFAULT true, p_start_partition text DEFAULT NULL) RETURNS boolean LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_control_type text; v_exists text; v_grantees text[]; v_hasoids boolean; v_id bigint; v_inherit_fk boolean; v_inherit_privileges boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_grant record; v_parent_schema text; v_parent_tablename text; v_parent_tablespace text; v_partition_interval bigint; v_partition_created boolean := false; v_partition_name text; v_partition_type text; v_publications text[]; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_sub_control text; v_sub_partition_type text; v_sub_id_max bigint; v_sub_id_min bigint; v_template_table text; v_unlogged char; BEGIN /* * Function to create id partitions */ SELECT control , partition_type , partition_interval , inherit_fk , jobmon , template_table , publications , inherit_privileges INTO v_control , v_partition_type , v_partition_interval , v_inherit_fk , v_jobmon , v_template_table , v_publications , v_inherit_privileges FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT n.nspname, c.relname, t.spcname INTO v_parent_schema, v_parent_tablename, v_parent_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 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; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'id' THEN RAISE EXCEPTION 'ERROR: Given parent table is not set up for id/serial partitioning'; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'id'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; FOREACH v_id IN ARRAY p_partition_ids LOOP -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_id_min IS NOT NULL THEN IF v_id < v_sub_id_min OR v_id > v_sub_id_max THEN CONTINUE; END IF; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_id::text, TRUE); -- If child table already exists, skip creation -- Have to check pg_class because if subpartitioned, table will not be in pg_tables SELECT c.relname INTO v_exists 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_partition_name::name; IF v_exists IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_id||' to '||(v_id + v_partition_interval)-1); END IF; v_sql := 'CREATE'; -- As of PG12, the unlogged/logged status of a native parent table cannot be changed via an ALTER TABLE in order to affect its children. -- As of v4.2x, the unlogged state will be managed via the template table SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c 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; IF v_unlogged = 'u' and v_partition_type != 'native' THEN v_sql := v_sql || ' UNLOGGED'; END IF; -- Close parentheses on LIKE are below due to differing requirements of native subpartitioning -- Same INCLUDING list is used in create_parent() v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING STORAGE INCLUDING COMMENTS ' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); IF current_setting('server_version_num')::int >= 120000 THEN v_sql := v_sql || ' INCLUDING GENERATED '; END IF; SELECT sub_partition_type, sub_control INTO v_sub_partition_type, v_sub_control FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; IF v_sub_partition_type = 'native' THEN -- INCLUDING INDEXES isn't necessary for native partitioning. It isn't supported in v10 and -- for v11+ index inheritance is automatically handled when the partition is attached v_sql := v_sql || format(') PARTITION BY RANGE (%I) ', v_sub_control); ELSE v_sql := v_sql || format(' INCLUDING INDEXES) ', v_sub_control); END IF; IF current_setting('server_version_num')::int < 120000 THEN -- column removed from pgclass in pg12 SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c 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; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; END IF; RAISE DEBUG 'create_partition_id v_sql: %', v_sql; EXECUTE v_sql; IF v_partition_type = 'native' THEN IF current_setting('server_version_num')::int >= 120000 THEN -- PG12 fixed tablespace marking on the parent of a native partition set -- Versions older than 12 handle tablespace setting via inherit_template_properties() call below IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; END IF; IF v_template_table IS NOT NULL THEN PERFORM @extschema@.inherit_template_properties(p_parent_table, v_parent_schema, v_partition_name); END IF; EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , v_id , v_id + v_partition_interval); ELSE -- non-native IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %s AND %I < %s )' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_control , v_id , v_control , v_id + v_partition_interval); EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I', v_parent_schema, v_partition_name, v_parent_schema, v_parent_tablename); -- Indexes cannot be created on the parent, so clustering cannot be used for native yet. PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); -- Foreign keys to other tables not supported on native parent tables IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; END IF; -- NOTE: Privileges not automatically inherited for native. Only do so if config flag is set IF v_partition_type != 'native' OR (v_partition_type = 'native' AND v_inherit_privileges = TRUE) THEN PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition -- 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 FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_infinite_time_partitions , sub_automatic_maintenance , 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 WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Subpartitioning '||v_partition_name); END IF; 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_inherit_fk := %L , p_epoch := %L , p_template_table := %L , p_jobmon := %L , p_start_partition := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_automatic_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_template_table , v_row.sub_jobmon , p_start_partition); RAISE DEBUG 'create_partition_id (create_parent loop): %', v_sql; EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions , trigger_exception_handling = v_row.sub_trigger_exception_handling , upsert = v_row.sub_upsert , inherit_privileges = v_row.sub_inherit_privileges , trigger_return_null = v_row.sub_trigger_return_null , constraint_valid = v_row.sub_constraint_valid , subscription_refresh = v_row.sub_subscription_refresh WHERE parent_table = v_parent_schema||'.'||v_partition_name; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; -- end sub partitioning LOOP -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table, p_job_id := v_job_id); IF v_publications IS NOT NULL THEN -- NOTE: Publications currently not supported on parent table, but are supported on the table partitions if individually assigned. PERFORM @extschema@.apply_publications(p_parent_table, v_parent_schema, v_partition_name); END IF; v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s', p_parent_table)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE FUNCTION @extschema@.create_partition_time(p_parent_table text, p_partition_times timestamptz[], p_analyze boolean DEFAULT true, p_start_partition text DEFAULT NULL) RETURNS boolean LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_control_type text; v_datetime_string text; v_epoch text; v_exists smallint; v_grantees text[]; v_hasoids boolean; v_inherit_privileges boolean; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_grant record; v_parent_schema text; v_parent_tablename text; v_part_col text; v_partition_created boolean := false; v_partition_name text; v_partition_suffix text; v_parent_tablespace text; v_partition_expression text; v_partition_interval interval; v_partition_timestamp_end timestamptz; v_partition_timestamp_start timestamptz; v_publications text[]; v_quarter text; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_sub_control text; v_sub_parent text; v_sub_partition_type text; v_sub_timestamp_max timestamptz; v_sub_timestamp_min timestamptz; v_template_table text; v_trunc_value text; v_time timestamptz; v_partition_type text; v_unlogged char; v_year text; BEGIN /* * Function to create a child table in a time-based partition set */ SELECT partition_type , control , partition_interval , epoch , inherit_fk , jobmon , datetime_string , template_table , publications , inherit_privileges INTO v_partition_type , v_control , v_partition_interval , v_epoch , v_inherit_fk , v_jobmon , v_datetime_string , v_template_table , v_publications , v_inherit_privileges FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT n.nspname, c.relname, t.spcname INTO v_parent_schema, v_parent_tablename, v_parent_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 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; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'time' THEN IF (v_control_type = 'id' AND v_epoch = 'none') OR v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run on partition set without time based control column or epoch flag set with an id column. Found control: %, epoch: %', v_control_type, v_epoch; END IF; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::timestamptz, sub_max::timestamptz INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'time'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) END; RAISE DEBUG 'create_partition_time: v_partition_expression: %', v_partition_expression; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_timestamp_start := v_time; BEGIN v_partition_timestamp_end := v_time + v_partition_interval; 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_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_time||' skipped'); CONTINUE; END; -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_timestamp_min IS NOT NULL THEN IF v_time < v_sub_timestamp_min OR v_time > v_sub_timestamp_max THEN CONTINUE; END IF; END IF; -- This suffix generation code is in partition_data_time() as well v_partition_suffix := to_char(v_time, v_datetime_string); v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); -- Check if child exists. SELECT count(*) INTO v_exists 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_partition_name::name; IF v_exists > 0 THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Creating new partition %s.%s with interval from %s to %s' , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end-'1sec'::interval)); END IF; v_sql := 'CREATE'; -- As of PG12, the unlogged/logged status of a native parent table cannot be changed via an ALTER TABLE in order to affect its children. -- As of v4.2x, the unlogged state will be managed via the template table SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c 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; IF v_unlogged = 'u' and v_partition_type != 'native' THEN v_sql := v_sql || ' UNLOGGED'; END IF; -- Close parentheses on LIKE are below due to differing requirements of native subpartitioning -- Same INCLUDING list is used in create_parent() v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING STORAGE INCLUDING COMMENTS ' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); IF current_setting('server_version_num')::int >= 120000 THEN v_sql := v_sql || ' INCLUDING GENERATED '; END IF; SELECT sub_partition_type, sub_control INTO v_sub_partition_type, v_sub_control FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; IF v_sub_partition_type = 'native' THEN -- INCLUDING INDEXES isn't necessary for native partitioning. It isn't supported in v10 and -- for v11+ index inheritance is automatically handled when the partition is attached v_sql := v_sql || format(') PARTITION BY RANGE (%I) ', v_sub_control); ELSE v_sql := v_sql || format(' INCLUDING INDEXES) ', v_sub_control); END IF; IF current_setting('server_version_num')::int < 120000 THEN -- column removed from pgclass in pg12 SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c 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; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; END IF; RAISE DEBUG 'create_partition_time v_sql: %', v_sql; EXECUTE v_sql; IF v_partition_type = 'native' THEN IF current_setting('server_version_num')::int >= 120000 THEN -- PG12 fixed tablespace marking on the parent of a native partition set -- Versions older than 12 handle tablespace setting via inherit_template_properties() call below IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; END IF; IF v_template_table IS NOT NULL THEN PERFORM @extschema@.inherit_template_properties(p_parent_table, v_parent_schema, v_partition_name); END IF; IF v_epoch = 'none' THEN -- Attach with normal, time-based values for native constraint EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end); ELSE -- Must attach with integer based values for native constraint and epoch IF v_epoch = 'seconds' THEN EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , EXTRACT('epoch' FROM v_partition_timestamp_start) , EXTRACT('epoch' FROM v_partition_timestamp_end)); ELSIF v_epoch = 'milliseconds' THEN EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , EXTRACT('epoch' FROM v_partition_timestamp_start) * 1000 , EXTRACT('epoch' FROM v_partition_timestamp_end) * 1000); END IF; -- Create secondary, time-based constraint since native's constraint is already integer based EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%s >= %L AND %4$s < %6$L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_partition_expression , v_partition_timestamp_start , v_partition_timestamp_end); END IF; ELSE -- non-native IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; -- Non-native always gets time-based constraint EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%s >= %L AND %4$s < %6$L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_partition_expression , v_partition_timestamp_start , v_partition_timestamp_end); IF v_epoch = 'seconds' THEN -- Non-native needs secondary, integer based constraint for epoch EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_int_check' , v_control , EXTRACT('epoch' from v_partition_timestamp_start) , v_control , EXTRACT('epoch' from v_partition_timestamp_end) ); ELSIF v_epoch = 'milliseconds' THEN EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_int_check' , v_control , EXTRACT('epoch' from v_partition_timestamp_start) * 1000 , v_control , EXTRACT('epoch' from v_partition_timestamp_end) * 1000); END IF; EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); -- If custom time, set extra config options. IF v_partition_type = 'time-custom' THEN INSERT INTO @extschema@.custom_time_partitions (parent_table, child_table, partition_range) VALUES ( p_parent_table, v_parent_schema||'.'||v_partition_name, tstzrange(v_partition_timestamp_start, v_partition_timestamp_end, '[)') ); END IF; -- Indexes cannot be created on the parent, so clustering cannot be used for native yet. PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); -- Foreign keys to other tables not supported in native IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; END IF; -- end native check -- NOTE: Privileges not automatically inherited for native. Only do so if config flag is set IF v_partition_type != 'native' OR (v_partition_type = 'native' AND v_inherit_privileges = TRUE) THEN PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition -- 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 FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_infinite_time_partitions , sub_automatic_maintenance , 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 WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Subpartitioning %s.%s', v_parent_schema, v_partition_name)); END IF; 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_inherit_fk := %L , p_epoch := %L , p_template_table := %L , p_jobmon := %L , p_start_partition := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_automatic_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_template_table , v_row.sub_jobmon , p_start_partition); RAISE DEBUG 'create_partition_time (create_parent loop): %', v_sql; EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions , trigger_exception_handling = v_row.sub_trigger_exception_handling , upsert = v_row.sub_upsert , inherit_privileges = v_row.sub_inherit_privileges , trigger_return_null = v_row.sub_trigger_return_null , constraint_valid = v_row.sub_constraint_valid , subscription_refresh = v_row.sub_subscription_refresh WHERE parent_table = v_parent_schema||'.'||v_partition_name; END LOOP; -- end sub partitioning LOOP -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table, p_job_id := v_job_id); IF v_publications IS NOT NULL THEN -- NOTE: Publications currently not supported on parent table, but are supported on the table partitions if individually assigned. PERFORM @extschema@.apply_publications(p_parent_table, v_parent_schema, v_partition_name); END IF; v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s. Attempted intervals: %s', p_parent_table, p_partition_times)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; 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_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE 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_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 INTO v_higher_parent_schema, v_higher_parent_table, v_higher_parent_control 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' 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 $$; CREATE FUNCTION @extschema@.apply_constraints(p_parent_table text, p_child_table text DEFAULT NULL, p_analyze boolean DEFAULT FALSE, p_job_id bigint DEFAULT NULL) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_child_exists text; v_child_tablename text; v_col text; v_constraint_cols text[]; v_constraint_col_type text; v_constraint_name text; v_constraint_valid boolean; v_constraint_values record; v_control text; v_control_type text; v_datetime_string text; v_epoch text; v_existing_constraint_name text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_id bigint; v_last_partition_timestamp timestamptz; v_max_id bigint; v_max_timestamp timestamptz; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_optimize_constraint int; v_parent_schema text; v_parent_table text; v_parent_tablename text; v_partition_interval text; v_partition_suffix text; v_premake int; v_sql text; v_step_id bigint; v_suffix_position int; v_type text; BEGIN /* * Apply constraints managed by partman extension */ SELECT parent_table , partition_type , control , premake , partition_interval , optimize_constraint , epoch , datetime_string , constraint_cols , jobmon , constraint_valid INTO v_parent_table , v_type , v_control , v_premake , v_partition_interval , v_optimize_constraint , v_epoch , v_datetime_string , v_constraint_cols , v_jobmon , v_constraint_valid FROM @extschema@.part_config WHERE parent_table = p_parent_table AND constraint_cols IS NOT NULL; IF v_constraint_cols IS NULL THEN RAISE DEBUG 'Given parent table (%) not set up for constraint management (constraint_cols is NULL)', p_parent_table; -- Returns silently to allow this function to be simply called by maintenance processes without having to check if config options are set. RETURN; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(v_parent_table, '.', 1)::name AND tablename = split_part(v_parent_table, '.', 2)::name; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE CONSTRAINT: %s', v_parent_table)); ELSE v_job_id = p_job_id; END IF; END IF; -- If p_child_table is null, figure out the partition that is the one right before the optimize_constraint value backwards. IF p_child_table IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying additional constraints: Automatically determining most recent child on which to apply constraints'); END IF; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(v_parent_table, 'DESC') LIMIT 1; IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN SELECT child_start_time INTO v_last_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_partition_interval, v_parent_table); v_partition_suffix := to_char(v_last_partition_timestamp - (v_partition_interval::interval * (v_optimize_constraint + v_premake + 1) ), v_datetime_string); ELSIF v_control_type = 'id' THEN SELECT child_start_id INTO v_last_partition_id FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_partition_interval, v_parent_table); v_partition_suffix := (v_last_partition_id - (v_partition_interval::int * (v_optimize_constraint + v_premake + 1) ))::text; END IF; v_child_tablename := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); RAISE DEBUG 'apply_constraint: v_parent_tablename: % , v_partition_suffix: %', v_parent_tablename, v_partition_suffix; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Target child table: %s.%s', v_parent_schema, v_child_tablename)); END IF; ELSE v_child_tablename = split_part(p_child_table, '.', 2); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying additional constraints: Checking if target child table exists'); END IF; SELECT tablename FROM pg_catalog.pg_tables INTO v_child_exists WHERE schemaname = v_parent_schema::name AND tablename = v_child_tablename::name; IF v_child_exists IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Target child table (%s) does not exist. Skipping constraint creation.', v_child_tablename)); IF p_job_id IS NULL THEN PERFORM close_job(v_job_id); END IF; END IF; RAISE DEBUG 'Target child table (%) does not exist. Skipping constraint creation.', v_child_tablename; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; FOREACH v_col IN ARRAY v_constraint_cols LOOP SELECT con.conname INTO v_existing_constraint_name FROM pg_catalog.pg_constraint con JOIN pg_class c ON c.oid = con.conrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN pg_catalog.pg_attribute a ON con.conrelid = a.attrelid WHERE c.relname = v_child_tablename::name AND n.nspname = v_parent_schema::name AND con.conname LIKE 'partmanconstr_%' AND con.contype = 'c' AND a.attname = v_col::name AND ARRAY[a.attnum] OPERATOR(pg_catalog.<@) con.conkey AND a.attisdropped = false; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Applying additional constraints: Applying new constraint on column: %s', v_col)); END IF; IF v_existing_constraint_name IS NOT NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Partman managed constraint already exists on this table (%s) and column (%s). Skipping creation.', v_child_tablename, v_col)); END IF; RAISE DEBUG 'Partman managed constraint already exists on this table (%) and column (%). Skipping creation.', v_child_tablename, v_col ; CONTINUE; END IF; -- Ensure column name gets put on end of constraint name to help avoid naming conflicts v_constraint_name := @extschema@.check_name_length('partmanconstr_'||v_child_tablename, p_suffix := '_'||v_col); EXECUTE format('SELECT min(%I)::text AS min, max(%I)::text AS max FROM %I.%I', v_col, v_col, v_parent_schema, v_child_tablename) INTO v_constraint_values; IF v_constraint_values IS NOT NULL THEN v_sql := format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I <= %L)' , v_parent_schema , v_child_tablename , v_constraint_name , v_col , v_constraint_values.min , v_col , v_constraint_values.max); IF v_constraint_valid = false THEN v_sql := format('%s NOT VALID', v_sql); END IF; RAISE DEBUG 'Constraint creation query: %', v_sql; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('New constraint created: %s', v_sql)); END IF; ELSE RAISE DEBUG 'Given column (%) contains all NULLs. No constraint created', v_col; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Given column (%s) contains all NULLs. No constraint created', v_col)); END IF; END IF; END LOOP; IF p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Applying additional constraints: Running analyze on partition set: %s', v_parent_table)); END IF; RAISE DEBUG 'Running analyze on partition set: %', v_parent_table; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); 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 CONSTRAINT: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE FUNCTION @extschema@.partition_data_id(p_parent_table text , p_batch_count int DEFAULT 1 , p_batch_interval bigint DEFAULT NULL , p_lock_wait numeric DEFAULT 0 , p_order text DEFAULT 'ASC' , p_analyze boolean DEFAULT true , p_source_table text DEFAULT NULL , p_ignored_columns text[] DEFAULT NULL) RETURNS bigint LANGUAGE plpgsql AS $$ DECLARE v_col text; v_column_list text; v_control text; v_control_type text; v_current_partition_name text; v_default_exists boolean; v_default_schemaname text; v_default_tablename text; v_epoch text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_id bigint; v_min_partition_id bigint; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_tablename text; v_partition_interval bigint; v_partition_id bigint[]; v_partition_type text; v_rowcount bigint; v_source_schemaname text; v_source_tablename text; v_sql text; v_start_control bigint; v_total_rows bigint := 0; BEGIN /* * Populate the child table(s) of an id-based partition set with old data from the original parent */ SELECT partition_interval::bigint , partition_type , control , epoch INTO v_partition_interval , v_partition_type , v_control , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: No entry in part_config found for given table: %', p_parent_table; END IF; SELECT schemaname, tablename INTO v_source_schemaname, v_source_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- Preserve real parent tablename for use below v_parent_tablename := v_source_tablename; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_source_schemaname, v_source_tablename, v_control); IF v_control_type <> 'id' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN RAISE EXCEPTION 'Control column for given partition set is not id/serial based or epoch flag is set for time-based partitioning.'; END IF; IF p_source_table IS NOT NULL THEN -- Set source table to user given source table instead of parent table v_source_schemaname := NULL; v_source_tablename := NULL; SELECT schemaname, tablename INTO v_source_schemaname, v_source_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_source_table, '.', 1)::name AND tablename = split_part(p_source_table, '.', 2)::name; IF v_source_tablename IS NULL THEN RAISE EXCEPTION 'Given source table does not exist in system catalogs: %', p_source_table; END IF; ELSIF v_partition_type = 'native' AND current_setting('server_version_num')::int >= 110000 THEN IF p_batch_interval IS NOT NULL AND p_batch_interval != v_partition_interval THEN -- This is true because all data for a given child table must be moved out of the default partition before the child table can be created. -- So cannot create the child table when only some of the data has been moved out of the default partition. RAISE EXCEPTION 'Custom intervals are not allowed when moving data out of the DEFAULT partition in a native set. Please leave p_interval/p_batch_interval parameters unset or NULL to allow use of partition set''s default partitioning interval.'; END IF; -- Set source table to default table if PG11+, p_source_table is not set, and it exists -- Otherwise just return with a DEBUG that no data source exists v_sql := format('SELECT n.nspname::text, c.relname::text FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = ''%I.%I''::regclass AND pg_get_expr(relpartbound, c.oid) = ''DEFAULT''' , v_source_schemaname , v_source_tablename); EXECUTE v_sql INTO v_default_schemaname, v_default_tablename; IF v_default_tablename IS NOT NULL THEN v_source_schemaname := v_default_schemaname; v_source_tablename := v_default_tablename; v_default_exists := true; EXECUTE format ('CREATE TEMP TABLE IF NOT EXISTS partman_temp_data_storage (LIKE %I.%I INCLUDING INDEXES) ON COMMIT DROP', v_source_schemaname, v_source_tablename); ELSE RAISE DEBUG 'No default table found when partition_data_id() was called'; RETURN v_total_rows; END IF; END IF; 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'); IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; -- Generate column list to use in SELECT/INSERT statements below. Allows for exclusion of GENERATED (or any other desired) columns. v_sql := format ('SELECT ''"''||string_agg(attname, ''","'')||''"'' 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 n.nspname = %L AND c.relname = %L AND a.attnum > 0 AND a.attisdropped = false' , v_source_schemaname , v_source_tablename); IF p_ignored_columns IS NOT NULL THEN FOREACH v_col IN ARRAY p_ignored_columns LOOP v_sql := v_sql || format(' AND attname != %L ', v_col); END LOOP; END IF; EXECUTE v_sql INTO v_column_list; FOR i IN 1..p_batch_count LOOP IF p_order = 'ASC' THEN EXECUTE format('SELECT min(%I) FROM ONLY %I.%I', v_control, v_source_schemaname, v_source_tablename) INTO v_start_control; IF v_start_control IS NULL THEN EXIT; END IF; v_min_partition_id = v_start_control - (v_start_control % v_partition_interval); v_partition_id := ARRAY[v_min_partition_id]; -- Check if custom batch interval overflows current partition maximum IF (v_start_control + p_batch_interval) >= (v_min_partition_id + v_partition_interval) THEN v_max_partition_id := v_min_partition_id + v_partition_interval; ELSE v_max_partition_id := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_control, v_source_schemaname, v_source_tablename) INTO v_start_control; IF v_start_control IS NULL THEN EXIT; END IF; v_min_partition_id = v_start_control - (v_start_control % v_partition_interval); -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_id := v_min_partition_id + v_partition_interval; v_partition_id := ARRAY[v_min_partition_id]; -- Make sure minimum doesn't underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_id THEN v_min_partition_id = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN v_sql := format('SELECT %s FROM ONLY %I.%I WHERE %I >= %s AND %I < %s FOR UPDATE NOWAIT' , v_column_list , v_source_schemaname , v_source_tablename , v_control , v_min_partition_id , v_control , v_max_partition_id); EXECUTE v_sql; v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; v_current_partition_name := @extschema@.check_name_length(COALESCE(v_parent_tablename), v_min_partition_id::text, TRUE); IF v_default_exists THEN -- Child tables cannot be created in native partitioning if data that belongs to it exists in the default -- Have to move data out to temporary location, create child table, then move it back -- Temp table created above to avoid excessive temp creation in loop EXECUTE format('WITH partition_data AS ( DELETE FROM %1$I.%2$I WHERE %3$I >= %4$s AND %3$I < %5$s RETURNING *) INSERT INTO partman_temp_data_storage (%6$s) SELECT %6$s FROM partition_data' , v_source_schemaname , v_source_tablename , v_control , v_min_partition_id , v_max_partition_id , v_column_list); PERFORM @extschema@.create_partition_id(p_parent_table, v_partition_id, p_analyze); EXECUTE format('WITH partition_data AS ( DELETE FROM partman_temp_data_storage RETURNING *) INSERT INTO %1$I.%2$I (%3$s) SELECT %3$s FROM partition_data' , v_source_schemaname , v_current_partition_name , v_column_list); ELSE PERFORM @extschema@.create_partition_id(p_parent_table, v_partition_id, p_analyze); EXECUTE format('WITH partition_data AS ( DELETE FROM ONLY %1$I.%2$I WHERE %3$I >= %4$s AND %3$I < %5$s RETURNING *) INSERT INTO %1$I.%6$I (%7$s) SELECT %7$s FROM partition_data' , v_source_schemaname , v_source_tablename , v_control , v_min_partition_id , v_max_partition_id , v_current_partition_name , v_column_list); END IF; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; IF v_partition_type = 'partman' THEN PERFORM @extschema@.create_function_id(p_parent_table); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_total_rows; END $$; CREATE FUNCTION @extschema@.partition_data_time( p_parent_table text , p_batch_count int DEFAULT 1 , p_batch_interval interval DEFAULT NULL , p_lock_wait numeric DEFAULT 0 , p_order text DEFAULT 'ASC' , p_analyze boolean DEFAULT true , p_source_table text DEFAULT NULL , p_ignored_columns text[] DEFAULT NULL) RETURNS bigint LANGUAGE plpgsql AS $$ DECLARE v_col text; v_column_list text; v_control text; v_control_type text; v_datetime_string text; v_current_partition_name text; v_default_exists boolean; v_default_schemaname text; v_default_tablename text; v_epoch text; v_last_partition text; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_max_partition_timestamp timestamptz; v_min_partition_timestamp timestamptz; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_tablename text; v_parent_tablename_real text; v_partition_expression text; v_partition_interval interval; v_partition_suffix text; v_partition_timestamp timestamptz[]; v_partition_type text; v_source_schemaname text; v_source_tablename text; v_rowcount bigint; v_sql text; v_start_control timestamptz; v_total_rows bigint := 0; BEGIN /* * Populate the child table(s) of a time-based partition set with old data from the original parent */ SELECT partition_type , partition_interval::interval , control , datetime_string , epoch INTO v_partition_type , v_partition_interval , v_control , v_datetime_string , v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: No entry in part_config found for given table: %', p_parent_table; END IF; SELECT schemaname, tablename INTO v_source_schemaname, v_source_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- Preserve real parent tablename for use below v_parent_tablename := v_source_tablename; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_source_schemaname, v_source_tablename, v_control); IF v_control_type <> 'time' THEN IF (v_control_type = 'id' AND v_epoch = 'none') OR v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run on partition set without time based control column or epoch flag set with an id column. Found control: %, epoch: %', v_control_type, v_epoch; END IF; END IF; -- Replace the parent variables with the source variables if using source table for child table data IF p_source_table IS NOT NULL THEN -- Set source table to user given source table instead of parent table v_source_schemaname := NULL; v_source_tablename := NULL; SELECT schemaname, tablename INTO v_source_schemaname, v_source_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_source_table, '.', 1)::name AND tablename = split_part(p_source_table, '.', 2)::name; IF v_source_tablename IS NULL THEN RAISE EXCEPTION 'Given source table does not exist in system catalogs: %', p_source_table; END IF; ELSIF v_partition_type = 'native' AND current_setting('server_version_num')::int >= 110000 THEN IF p_batch_interval IS NOT NULL AND p_batch_interval != v_partition_interval THEN -- This is true because all data for a given child table must be moved out of the default partition before the child table can be created. -- So cannot create the child table when only some of the data has been moved out of the default partition. RAISE EXCEPTION 'Custom intervals are not allowed when moving data out of the DEFAULT partition in a native set. Please leave p_interval/p_batch_interval parameters unset or NULL to allow use of partition set''s default partitioning interval.'; END IF; -- Set source table to default table if PG11+, p_source_table is not set, and it exists -- Otherwise just return with a DEBUG that no data source exists v_sql := format('SELECT n.nspname::text, c.relname::text FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhparent = ''%I.%I''::regclass AND pg_get_expr(relpartbound, c.oid) = ''DEFAULT''' , v_source_schemaname , v_source_tablename); EXECUTE v_sql INTO v_default_schemaname, v_default_tablename; IF v_default_tablename IS NOT NULL THEN v_source_schemaname := v_default_schemaname; v_source_tablename := v_default_tablename; v_default_exists := true; EXECUTE format ('CREATE TEMP TABLE IF NOT EXISTS partman_temp_data_storage (LIKE %I.%I INCLUDING INDEXES) ON COMMIT DROP', v_source_schemaname, v_source_tablename); ELSE RAISE DEBUG 'No default table found when partition_data_id() was called'; RETURN v_total_rows; END IF; END IF; 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'); IF p_batch_interval IS NULL OR p_batch_interval > v_partition_interval THEN p_batch_interval := v_partition_interval; END IF; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) END; -- Generate column list to use in SELECT/INSERT statements below. Allows for exclusion of GENERATED (or any other desired) columns. v_sql := format ('SELECT ''"''||string_agg(attname, ''","'')||''"'' 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 n.nspname = %L AND c.relname = %L AND a.attnum > 0 AND a.attisdropped = false' , v_source_schemaname , v_source_tablename); IF p_ignored_columns IS NOT NULL THEN FOREACH v_col IN ARRAY p_ignored_columns LOOP v_sql := v_sql || format(' AND attname != %L ', v_col); END LOOP; END IF; EXECUTE v_sql INTO v_column_list; FOR i IN 1..p_batch_count LOOP IF p_order = 'ASC' THEN EXECUTE format('SELECT min(%s) FROM ONLY %I.%I', v_partition_expression, v_source_schemaname, v_source_tablename) INTO v_start_control; ELSIF p_order = 'DESC' THEN EXECUTE format('SELECT max(%s) FROM ONLY %I.%I', v_partition_expression, v_source_schemaname, v_source_tablename) INTO v_start_control; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; IF v_start_control IS NULL THEN EXIT; END IF; IF v_partition_type = 'partman' THEN CASE WHEN v_partition_interval = '15 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '15min'::interval * floor(date_part('minute', v_start_control) / 15.0); WHEN v_partition_interval = '30 mins' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control) + '30min'::interval * floor(date_part('minute', v_start_control) / 30.0); WHEN v_partition_interval = '1 hour' THEN v_min_partition_timestamp := date_trunc('hour', v_start_control); WHEN v_partition_interval = '1 day' THEN v_min_partition_timestamp := date_trunc('day', v_start_control); WHEN v_partition_interval = '1 week' THEN v_min_partition_timestamp := date_trunc('week', v_start_control); WHEN v_partition_interval = '1 month' THEN v_min_partition_timestamp := date_trunc('month', v_start_control); WHEN v_partition_interval = '3 months' THEN v_min_partition_timestamp := date_trunc('quarter', v_start_control); WHEN v_partition_interval = '1 year' THEN v_min_partition_timestamp := date_trunc('year', v_start_control); END CASE; ELSIF v_partition_type IN ('time-custom', 'native') THEN SELECT child_start_time INTO v_min_partition_timestamp FROM @extschema@.show_partition_info(v_source_schemaname||'.'||v_last_partition , v_partition_interval::text , p_parent_table); v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; LOOP IF v_start_control >= v_min_partition_timestamp AND v_start_control < v_max_partition_timestamp THEN EXIT; ELSE BEGIN IF v_start_control >= v_max_partition_timestamp THEN -- Keep going forward in time, checking if child partition time interval encompasses the current v_start_control value v_min_partition_timestamp := v_max_partition_timestamp; v_max_partition_timestamp := v_max_partition_timestamp + v_partition_interval; ELSE -- Keep going backwards in time, checking if child partition time interval encompasses the current v_start_control value v_max_partition_timestamp := v_min_partition_timestamp; v_min_partition_timestamp := v_min_partition_timestamp - v_partition_interval; END IF; EXCEPTION WHEN datetime_field_overflow THEN RAISE EXCEPTION 'Attempted partition time interval is outside PostgreSQL''s supported time range. Unable to create partition with interval before timestamp % ', v_min_partition_timestamp; END; END IF; END LOOP; END IF; v_partition_timestamp := ARRAY[v_min_partition_timestamp]; IF p_order = 'ASC' THEN -- Ensure batch interval given as parameter doesn't cause maximum to overflow the current partition maximum IF (v_start_control + p_batch_interval) >= (v_min_partition_timestamp + v_partition_interval) THEN v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; ELSE v_max_partition_timestamp := v_start_control + p_batch_interval; END IF; ELSIF p_order = 'DESC' THEN -- Must be greater than max value still in parent table since query below grabs < max v_max_partition_timestamp := v_min_partition_timestamp + v_partition_interval; -- Ensure batch interval given as parameter doesn't cause minimum to underflow current partition minimum IF (v_start_control - p_batch_interval) >= v_min_partition_timestamp THEN v_min_partition_timestamp = v_start_control - p_batch_interval; END IF; ELSE RAISE EXCEPTION 'Invalid value for p_order. Must be ASC or DESC'; END IF; -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT %s FROM ONLY %I.%I WHERE %s >= %L AND %3$s < %5$L FOR UPDATE NOWAIT' , v_column_list , v_source_schemaname , v_source_tablename , v_partition_expression , v_min_partition_timestamp , v_max_partition_timestamp); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RETURN -1; END IF; END IF; -- This suffix generation code is in create_partition_time() as well v_partition_suffix := to_char(v_min_partition_timestamp, v_datetime_string); v_current_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); IF v_default_exists THEN -- Child tables cannot be created in native partitioning if data that belongs to it exists in the default -- Have to move data out to temporary location, create child table, then move it back -- Temp table created above to avoid excessive temp creation in loop EXECUTE format('WITH partition_data AS ( DELETE FROM %1$I.%2$I WHERE %3$s >= %4$L AND %3$s < %5$L RETURNING *) INSERT INTO partman_temp_data_storage (%6$s) SELECT %6$s FROM partition_data' , v_source_schemaname , v_source_tablename , v_partition_expression , v_min_partition_timestamp , v_max_partition_timestamp , v_column_list); PERFORM @extschema@.create_partition_time(p_parent_table, v_partition_timestamp, p_analyze); EXECUTE format('WITH partition_data AS ( DELETE FROM partman_temp_data_storage RETURNING *) INSERT INTO %I.%I (%3$s) SELECT %3$s FROM partition_data' , v_source_schemaname , v_current_partition_name , v_column_list); ELSE PERFORM @extschema@.create_partition_time(p_parent_table, v_partition_timestamp, p_analyze); EXECUTE format('WITH partition_data AS ( DELETE FROM ONLY %I.%I WHERE %s >= %L AND %3$s < %5$L RETURNING *) INSERT INTO %1$I.%6$I (%7$s) SELECT %7$s FROM partition_data' , v_source_schemaname , v_source_tablename , v_partition_expression , v_min_partition_timestamp , v_max_partition_timestamp , v_current_partition_name , v_column_list); END IF; GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total_rows := v_total_rows + v_rowcount; IF v_rowcount = 0 THEN EXIT; END IF; END LOOP; IF v_partition_type IN ('partman', 'time-custom') THEN PERFORM @extschema@.create_function_time(p_parent_table); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_total_rows; END $$; CREATE FUNCTION @extschema@.run_maintenance(p_parent_table text DEFAULT NULL, p_analyze boolean DEFAULT NULL, p_jobmon boolean DEFAULT true) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_analyze boolean; v_check_subpart int; v_control_type text; v_create_count int := 0; v_current_partition text; v_current_partition_id bigint; v_current_partition_timestamp timestamptz; v_default_tablename text; v_drop_count int := 0; v_is_default text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_created boolean; v_last_partition_id bigint; v_last_partition_timestamp timestamptz; v_max_id_parent bigint; v_max_time_default timestamptz; v_new_search_path text := '@extschema@,pg_temp'; v_next_partition_id bigint; v_next_partition_timestamp timestamptz; v_old_search_path text; v_parent_exists text; v_parent_oid oid; v_parent_schema text; v_parent_tablename text; v_partition_expression text; v_premade_count int; v_premake_id_max bigint; v_premake_id_min bigint; v_premake_timestamp_min timestamptz; v_premake_timestamp_max timestamptz; v_row record; v_row_max_id record; v_row_max_time record; v_row_sub record; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_sub_id_max bigint; v_sub_id_max_suffix bigint; v_sub_id_min bigint; v_sub_parent text; v_sub_refresh_done text[]; v_sub_timestamp_max timestamptz; v_sub_timestamp_max_suffix timestamptz; v_sub_timestamp_min timestamptz; v_tablename text; v_tables_list_sql text; BEGIN /* * Function to manage pre-creation of the next partitions in a set. * Also manages dropping old partitions if the retention option is set. * If p_parent_table is passed, will only run run_maintenance() on that one table (no matter what the configuration table may have set for it) * Otherwise, will run on all tables in the config table with p_automatic_maintenance() set to true. * For large partition sets, running analyze can cause maintenance to take longer than expected. Can set p_analyze to false to avoid a forced analyze run on PG versions before 11. 11+ does not analyze by default anymore. * Be aware that constraint exclusion may not work properly until an analyze on the partition set is run. */ v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman maintenance already running.'; RETURN; 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'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job('PARTMAN RUN MAINTENANCE'); v_step_id := add_step(v_job_id, 'Running maintenance loop'); END IF; v_tables_list_sql := 'SELECT parent_table , partition_type , partition_interval , control , premake , undo_in_progress , sub_partition_set_full , epoch , infinite_time_partitions , retention , subscription_refresh FROM @extschema@.part_config WHERE undo_in_progress = false'; IF p_parent_table IS NULL THEN v_tables_list_sql := v_tables_list_sql || ' AND automatic_maintenance = ''on'''; ELSE v_tables_list_sql := v_tables_list_sql || format(' AND parent_table = %L', p_parent_table); END IF; FOR v_row IN EXECUTE v_tables_list_sql LOOP CONTINUE WHEN v_row.undo_in_progress; -- When sub-partitioning, retention may drop tables that were already put into the query loop values. -- Check if they still exist in part_config before continuing v_parent_exists := NULL; SELECT parent_table INTO v_parent_exists FROM @extschema@.part_config WHERE parent_table = v_row.parent_table; RAISE DEBUG 'Parent table possibly removed from part_config by retenion'; CONTINUE WHEN v_parent_exists IS NULL; -- Check for consistent data in part_config_sub table. Was unable to get this working properly as either a constraint or trigger. -- Would either delay raising an error until the next write (which I cannot predict) or disallow future edits to update a sub-partition set's configuration. -- This way at least provides a consistent way to check that I know will run. If anyone can get a working constraint/trigger, please help! SELECT sub_parent INTO v_sub_parent FROM @extschema@.part_config_sub WHERE sub_parent = v_row.parent_table; IF v_sub_parent IS NOT NULL THEN SELECT count(*) INTO v_check_subpart FROM @extschema@.check_subpart_sameconfig(v_row.parent_table); IF v_check_subpart > 1 THEN RAISE EXCEPTION 'Inconsistent data in part_config_sub table. Sub-partition tables that are themselves sub-partitions cannot have differing configuration values among their siblings. Run this query: "SELECT * FROM @extschema@.check_subpart_sameconfig(''%'');" This should only return a single row or nothing. If multiple rows are returned, the results are differing configurations in the part_config_sub table for children of the given parent. Determine the child tables of the given parent and look up their entries based on the "part_config_sub.sub_parent" column. Update the differing values to be consistent for your desired values.', v_row.parent_table; END IF; END IF; -- Shouldn't need to analyze tables for most statistics for native sets on PG11+ by default anymore IF p_analyze IS NULL THEN IF v_row.partition_type = 'native' AND current_setting('server_version_num')::int >= 110000 THEN v_analyze := false; ELSE v_analyze := true; END IF; END IF; SELECT n.nspname, c.relname, c.oid INTO v_parent_schema, v_parent_tablename, 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(v_row.parent_table, '.', 1)::name AND c.relname = split_part(v_row.parent_table, '.', 2)::name; -- Used below to see if there's any data in the parent (<=PG10) or default (PG11+) child table. IF v_row.partition_type = 'native' AND current_setting('server_version_num')::int >= 110000 THEN -- Always returns the default partition first if it exists SELECT partition_tablename INTO v_default_tablename FROM @extschema@.show_partitions(v_row.parent_table, p_include_default := true) LIMIT 1; SELECT pg_get_expr(relpartbound, v_parent_oid) INTO v_is_default FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n on c.relnamespace = n.oid WHERE n.nspname = v_parent_schema AND c.relname = v_default_tablename; IF v_is_default != 'DEFAULT' THEN v_default_tablename := v_parent_tablename; END IF; ELSE v_default_tablename := v_parent_tablename; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_row.control); v_partition_expression := CASE WHEN v_row.epoch = 'seconds' THEN format('to_timestamp(%I)', v_row.control) WHEN v_row.epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_row.control) ELSE format('%I', v_row.control) END; RAISE DEBUG 'run_maint: v_partition_expression: %', v_partition_expression; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LIMIT 1; RAISE DEBUG 'run_maint: parent_table: %, v_last_partition: %', v_row.parent_table, v_last_partition; IF v_control_type = 'time' OR (v_control_type = 'id' AND v_row.epoch <> 'none') THEN -- Run retention if needed IF v_row.retention IS NOT NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_time(v_row.parent_table); IF v_drop_count > 0 AND v_row.partition_type <> 'native' THEN PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); END IF; END IF; IF v_row.sub_partition_set_full THEN CONTINUE; END IF; SELECT child_start_time INTO v_last_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_row.partition_interval, v_row.parent_table); -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. IF v_row.infinite_time_partitions IS TRUE THEN -- Set it to "now" so new partitions continue to be created -- For infinite_time_partitions, don't bother getting the max value in the partitions v_current_partition_timestamp = CURRENT_TIMESTAMP; ELSE FOR v_row_max_time IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%s)::text FROM %I.%I' , v_partition_expression , v_row_max_time.partition_schemaname , v_row_max_time.partition_tablename ) INTO v_current_partition_timestamp; IF v_current_partition_timestamp IS NOT NULL THEN SELECT suffix_timestamp INTO v_current_partition_timestamp FROM @extschema@.show_partition_name(v_row.parent_table, v_current_partition_timestamp::text); EXIT; END IF; END LOOP; END IF; -- end infinite time check -- Check for values in the parent/default table. If they are there and greater than all child values, use that instead -- This allows maintenance to continue working properly if there is a large gap in data insertion. Data will remain in default, but new tables will be created EXECUTE format('SELECT max(%s) FROM ONLY %I.%I', v_partition_expression, v_parent_schema, v_default_tablename) INTO v_max_time_default; RAISE DEBUG 'run_maint: v_current_partition_timestamp: %, v_max_time_default: %', v_current_partition_timestamp, v_max_time_default; IF v_current_partition_timestamp IS NULL AND v_max_time_default IS NULL THEN -- Partition set is completely empty and infinite time partitions not set -- Nothing to do CONTINUE; END IF; IF v_current_partition_timestamp IS NULL OR (v_max_time_default > v_current_partition_timestamp) THEN SELECT suffix_timestamp INTO v_current_partition_timestamp FROM @extschema@.show_partition_name(v_row.parent_table, v_max_time_default::text); END IF; -- If this is a subpartition, determine if the last child table has been made. If so, mark it as full so future maintenance runs can skip it SELECT sub_min::timestamptz, sub_max::timestamptz INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'time'); IF v_sub_timestamp_max IS NOT NULL THEN SELECT suffix_timestamp INTO v_sub_timestamp_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_timestamp_max::text); IF v_sub_timestamp_max_suffix = v_last_partition_timestamp THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; -- Check and see how many premade partitions there are. v_premade_count = round(EXTRACT('epoch' FROM age(v_last_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); v_next_partition_timestamp := v_last_partition_timestamp; RAISE DEBUG 'run_maint before loop: current_partition_timestamp: %, v_premade_count: %, v_sub_timestamp_min: %, v_sub_timestamp_max: %' , v_current_partition_timestamp , v_premade_count , v_sub_timestamp_min , v_sub_timestamp_max; -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed WHILE (v_premade_count < v_row.premake) LOOP RAISE DEBUG 'run_maint: parent_table: %, v_premade_count: %, v_next_partition_timestamp: %', v_row.parent_table, v_premade_count, v_next_partition_timestamp; IF v_next_partition_timestamp < v_sub_timestamp_min OR v_next_partition_timestamp > v_sub_timestamp_max THEN -- With subpartitioning, no need to run if the timestamp is not in the parent table's range EXIT; END IF; BEGIN v_next_partition_timestamp := v_next_partition_timestamp + v_row.partition_interval::interval; EXCEPTION WHEN datetime_field_overflow THEN v_premade_count := v_row.premake; -- do this so it can exit the premake check loop and continue in the outer for loop IF v_jobmon_schema IS NOT NULL THEN 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', format('Child partition creation skipped for parent table: %s', v_partition_time)); END IF; RAISE WARNING 'Attempted partition time interval is outside PostgreSQL''s supported time range. Child partition creation skipped for parent table %', v_row.parent_table; CONTINUE; END; v_last_partition_created := @extschema@.create_partition_time(v_row.parent_table , ARRAY[v_next_partition_timestamp] , v_analyze); IF v_last_partition_created THEN v_create_count := v_create_count + 1; IF v_row.partition_type <> 'native' THEN PERFORM @extschema@.create_function_time(v_row.parent_table, v_job_id); END IF; END IF; v_premade_count = round(EXTRACT('epoch' FROM age(v_next_partition_timestamp, v_current_partition_timestamp)) / EXTRACT('epoch' FROM v_row.partition_interval::interval)); END LOOP; ELSIF v_control_type = 'id' THEN -- Run retention if needed IF v_row.retention IS NOT NULL THEN v_drop_count := v_drop_count + @extschema@.drop_partition_id(v_row.parent_table); IF v_drop_count > 0 AND v_row.partition_type <> 'native' THEN PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); END IF; END IF; IF v_row.sub_partition_set_full THEN CONTINUE; END IF; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(v_row.parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%I)::text FROM %I.%I' , v_row.control , v_row_max_id.partition_schemaname , v_row_max_id.partition_tablename) INTO v_current_partition_id; IF v_current_partition_id IS NOT NULL THEN SELECT suffix_id INTO v_current_partition_id FROM @extschema@.show_partition_name(v_row.parent_table, v_current_partition_id::text); EXIT; END IF; END LOOP; -- Check for values in the parent/default table. If they are there and greater than all child values, use that instead -- This allows maintenance to continue working properly if there is a large gap in data insertion. Data will remain in parent, but new tables will be created EXECUTE format('SELECT max(%I) FROM ONLY %I.%I', v_row.control, v_parent_schema, v_default_tablename) INTO v_max_id_parent; IF v_max_id_parent > v_current_partition_id THEN SELECT suffix_id INTO v_current_partition_id FROM @extschema@.show_partition_name(v_row.parent_table, v_max_id_parent::text); END IF; IF v_current_partition_id IS NULL THEN -- Partition set is completely empty. Nothing to do CONTINUE; END IF; SELECT child_start_id INTO v_last_partition_id FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_row.partition_interval, v_row.parent_table); -- Determine if this table is a child of a subpartition parent. If so, get limits to see if run_maintenance even needs to run for it. SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(v_row.parent_table, 'id'); IF v_sub_id_max IS NOT NULL THEN SELECT suffix_id INTO v_sub_id_max_suffix FROM @extschema@.show_partition_name(v_row.parent_table, v_sub_id_max::text); IF v_sub_id_max_suffix = v_last_partition_id THEN -- Final partition for this set is created. Set full and skip it UPDATE @extschema@.part_config SET sub_partition_set_full = true WHERE parent_table = v_row.parent_table; CONTINUE; END IF; END IF; v_next_partition_id := v_last_partition_id; v_premade_count := ((v_last_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); -- Loop premaking until config setting is met. Allows it to catch up if it fell behind or if premake changed. WHILE (v_premade_count < v_row.premake) LOOP RAISE DEBUG 'run_maint: parent_table: %, v_premade_count: %, v_next_partition_id: %', v_row.parent_table, v_premade_count, v_next_partition_id; IF v_next_partition_id < v_sub_id_min OR v_next_partition_id > v_sub_id_max THEN -- With subpartitioning, no need to run if the id is not in the parent table's range EXIT; END IF; v_next_partition_id := v_next_partition_id + v_row.partition_interval::bigint; v_last_partition_created := @extschema@.create_partition_id(v_row.parent_table, ARRAY[v_next_partition_id], v_analyze); IF v_last_partition_created THEN v_create_count := v_create_count + 1; IF v_row.partition_type <> 'native' THEN PERFORM @extschema@.create_function_id(v_row.parent_table, v_job_id); END IF; END IF; v_premade_count := ((v_next_partition_id - v_current_partition_id) / v_row.partition_interval::bigint); END LOOP; END IF; -- end main IF check for time or id -- Refresh subscriptions in order to catch new tables that may have been created in the publication -- Keep track of which ones have been refreshed so it doesn't needlessly run more than once -- in a single maintenance run IF v_row.subscription_refresh IS NOT NULL THEN IF v_sub_refresh_done @> ARRAY[v_row.subscription_refresh] THEN CONTINUE; ELSE v_sql = format('ALTER SUBSCRIPTION %I REFRESH PUBLICATION', v_row.subscription_refresh); RAISE DEBUG '%', v_sql; EXECUTE v_sql; PERFORM array_append(v_sub_refresh_done, v_row.subscription_refresh); END IF; END IF; END LOOP; -- end of main loop through part_config IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Partition maintenance finished. %s partitions made. %s partitions dropped.', v_create_count, v_drop_count)); 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'); 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 RUN MAINTENANCE'')', v_jobmon_schema) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE FUNCTION @extschema@.undo_partition(p_parent_table text, p_batch_count int DEFAULT 1, p_batch_interval text DEFAULT NULL, p_keep_table boolean DEFAULT true, p_lock_wait numeric DEFAULT 0, p_target_table text DEFAULT NULL, p_ignored_columns text[] DEFAULT NULL, OUT partitions_undone int, OUT rows_undone bigint) RETURNS record LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_batch_interval_id bigint; v_batch_interval_time interval; v_batch_loop_count int := 0; v_child_loop_total bigint := 0; v_child_table text; v_col text; v_column_list text; v_control text; v_control_type text; v_child_min_id bigint; v_child_min_time timestamptz; v_epoch text; v_function_name text; v_jobmon boolean; v_jobmon_schema text; v_job_id bigint; v_inner_loop_count int; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_new_search_path text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_expression text; v_partition_interval text; v_partition_type text; v_relkind char; v_row record; v_rowcount bigint; v_sql text; v_step_id bigint; v_sub_count int; v_target_schema text; v_target_tablename text; v_template_schema text; v_template_siblings int; v_template_table text; v_template_tablename text; v_total bigint := 0; v_trig_name text; v_undo_count int := 0; BEGIN /* * For native, moves data to new, target table since data cannot be moved to parent. * Leaves old parent table as is and does not change name of new table. * For trigger-based, moves data to parent */ v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition_native')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition_native already running.'; partitions_undone = -1; RETURN; END IF; IF p_parent_table = p_target_table THEN RAISE EXCEPTION 'Target table cannot be the same as the parent table'; END IF; SELECT partition_interval::text , partition_type , control , jobmon , epoch , template_table INTO v_partition_interval , v_partition_type , v_control , v_jobmon , v_epoch , v_template_table FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_control IS NULL THEN RAISE EXCEPTION 'No configuration found for pg_partman for given parent table: %', p_parent_table; END IF; IF v_partition_type = 'native' AND p_target_table IS NULL THEN RAISE EXCEPTION 'Natively partitioned tables require setting the p_target_table option'; END IF; SELECT n.nspname, c.relname, c.relkind INTO v_parent_schema, v_parent_tablename, v_relkind 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_tablename IS NULL THEN RAISE EXCEPTION 'Given parent table not found in system catalogs: %', p_parent_table; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN IF p_batch_interval IS NULL THEN v_batch_interval_time := v_partition_interval::interval; ELSE v_batch_interval_time := p_batch_interval::interval; END IF; ELSIF v_control_type = 'id' THEN IF p_batch_interval IS NULL THEN v_batch_interval_id := v_partition_interval::bigint; ELSE v_batch_interval_id := p_batch_interval::bigint; END IF; ELSE RAISE EXCEPTION 'Data type of control column in given partition set must be either data/time or integer.'; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Check if any child tables are themselves partitioned or part of an inheritance tree. Prevent undo at this level if so. -- Need to lock child tables at all levels before multi-level undo can be performed safely. FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table) LOOP SELECT count(*) INTO v_sub_count FROM pg_catalog.pg_inherits i JOIN pg_catalog.pg_class c ON i.inhparent = c.oid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_row.partition_tablename::name AND n.nspname = v_row.partition_schemaname::name; IF v_sub_count > 0 THEN RAISE EXCEPTION 'Child table for this parent has child table(s) itself (%). Run undo partitioning on this table or remove inheritance first to ensure all data is properly moved to parent', v_row.partition_schemaname||'.'||v_row.partition_tablename; END IF; END LOOP; IF p_target_table IS NOT NULL THEN SELECT n.nspname, c.relname INTO v_target_schema, v_target_tablename FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = split_part(p_target_table, '.', 1)::name AND c.relname = split_part(p_target_table, '.', 2)::name; ELSE v_target_schema := v_parent_schema; v_target_tablename := v_parent_tablename; END IF; IF v_target_tablename IS NULL THEN RAISE EXCEPTION 'Given target table not found in system catalogs: %', p_target_table; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN UNDO PARTITIONING: %s', p_parent_table)); v_step_id := add_step(v_job_id, format('Undoing partitioning for table %s', p_parent_table)); END IF; v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) END; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; IF v_partition_type != 'native' THEN -- Stop data going into child tables on non-native partition sets. v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); -- Double-check for proper object existence SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t JOIN pg_catalog.pg_class c ON t.tgrelid = c.oid WHERE tgname = v_trig_name::name AND c.relname = v_parent_tablename::name; SELECT proname INTO v_function_name FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid WHERE n.nspname = v_parent_schema::name AND proname = v_function_name::name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_parent_tablename); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; partitions_undone = -1; RETURN; END IF; END IF; -- END p_lock_wait IF EXECUTE format('DROP TRIGGER IF EXISTS %I ON %I.%I', v_trig_name, v_parent_schema, v_parent_tablename); END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later IF v_function_name IS NOT NULL THEN EXECUTE format('DROP FUNCTION IF EXISTS %I.%I()', v_parent_schema, v_function_name); END IF; END IF; -- end pg_partman trigger cleanup IF v_jobmon_schema IS NOT NULL THEN IF (v_trig_name IS NOT NULL OR v_function_name IS NOT NULL) THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); ELSE PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process.'); END IF; END IF; -- Generate column list to use in SELECT/INSERT statements below. Allows for exclusion of GENERATED (or any other desired) columns. v_sql := format ('SELECT ''"''||string_agg(attname, ''","'')||''"'' 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 n.nspname = %L AND c.relname = %L AND a.attnum > 0 AND a.attisdropped = false' , v_target_schema , v_target_tablename); IF p_ignored_columns IS NOT NULL THEN FOREACH v_col IN ARRAY p_ignored_columns LOOP v_sql := v_sql || format(' AND attname != %L ', v_col); END LOOP; END IF; EXECUTE v_sql INTO v_column_list; <> LOOP -- Get ordered list of child table in set. Store in variable one at a time per loop until none are left or batch count is reached. -- This easily allows it to loop over same child table until empty or move onto next child table after it's dropped -- Include the native default table to ensure all data there is removed as well (final parameter = true) SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC', TRUE) LIMIT 1; EXIT outer_child_loop WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Removing child partition: %s.%s', v_parent_schema, v_child_table)); END IF; IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN EXECUTE format('SELECT min(%s) FROM %I.%I', v_partition_expression, v_parent_schema, v_child_table) INTO v_child_min_time; ELSIF v_control_type = 'id' THEN EXECUTE format('SELECT min(%s) FROM %I.%I', v_partition_expression, v_parent_schema, v_child_table) INTO v_child_min_id; END IF; IF v_child_min_time IS NULL AND v_child_min_id IS NULL THEN -- No rows left in this child table. Remove from partition set. -- lockwait timeout for table drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_child_table); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; partitions_undone = -1; RETURN; END IF; END IF; -- END p_lock_wait IF v_lock_obtained := FALSE; -- reset for reuse later IF v_partition_type = 'native' THEN EXECUTE format('ALTER TABLE %I.%I DETACH PARTITION %I.%I' , v_parent_schema , v_parent_tablename , v_parent_schema , v_child_table); ELSE EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_parent_schema , v_child_table , v_parent_schema , v_parent_tablename); END IF; IF p_keep_table = false THEN EXECUTE format('DROP TABLE %I.%I', v_parent_schema, v_child_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table DROPPED. Moved %s rows to target table', v_child_loop_total)); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table DETACHED/UNINHERITED from parent, not DROPPED. Moved %s rows to target table', v_child_loop_total)); END IF; END IF; IF v_partition_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_parent_schema||'.'||v_child_table; END IF; v_undo_count := v_undo_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached CONTINUE outer_child_loop; -- skip data moving steps below END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM %I.%I WHERE %I <= %L FOR UPDATE NOWAIT' , v_parent_schema , v_child_table , v_control , v_child_min_time + (v_batch_interval_time * v_inner_loop_count)); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; partitions_undone = -1; RETURN; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval EXECUTE format('WITH move_data AS ( DELETE FROM %I.%I WHERE %s <= %L RETURNING %s ) INSERT INTO %I.%I (%5$s) SELECT %5$s FROM move_data' , v_parent_schema , v_child_table , v_partition_expression , v_child_min_time + (v_batch_interval_time * v_inner_loop_count) , v_column_list , v_target_schema , v_target_tablename); GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Moved %s rows to target table.', v_child_loop_total)); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; -- Check again if table is empty and go to outer loop again to drop it if so EXECUTE format('SELECT min(%s) FROM %I.%I', v_partition_expression, v_parent_schema, v_child_table) INTO v_child_min_time; CONTINUE outer_child_loop WHEN v_child_min_time IS NULL; ELSIF v_control_type = 'id' THEN IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM %I.%I WHERE %I <= %L FOR UPDATE NOWAIT' , v_parent_schema , v_child_table , v_control , v_child_min_id + (v_batch_interval_id * v_inner_loop_count)); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; partitions_undone = -1; RETURN; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval EXECUTE format('WITH move_data AS ( DELETE FROM %I.%I WHERE %s <= %L RETURNING %s) INSERT INTO %I.%I (%5$s) SELECT %5$s FROM move_data' , v_parent_schema , v_child_table , v_partition_expression , v_child_min_id + (v_batch_interval_id * v_inner_loop_count) , v_column_list , v_target_schema , v_target_tablename); GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Moved %s rows to target table.', v_child_loop_total)); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; -- Check again if table is empty and go to outer loop again to drop it if so EXECUTE format('SELECT min(%s) FROM %I.%I', v_partition_expression, v_parent_schema, v_child_table) INTO v_child_min_id; CONTINUE outer_child_loop WHEN v_child_min_id IS NULL; END IF; -- end v_control_type check EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC', TRUE) LIMIT 1; IF v_child_table IS NULL THEN DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; -- Check if any other config entries still have this template table and don't remove if so -- Allows other sibling/parent tables to still keep using in case entire partition set isn't being undone SELECT count(*) INTO v_template_siblings FROM @extschema@.part_config WHERE template_table = v_template_table; 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(v_template_table, '.', 1)::name AND c.relname = split_part(v_template_table, '.', 2)::name; IF v_template_siblings = 0 AND v_template_tablename IS NOT NULL THEN EXECUTE format('DROP TABLE IF EXISTS %I.%I', v_template_schema, v_template_tablename); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Moved % row(s) to the target table. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', format('Moved %s row(s) to the target table. Removed %s partitions.', v_total, v_undo_count)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); partitions_undone := v_undo_count; rows_undone := v_total; 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 UNDO PARTITIONING: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; -- Restore dropped object privileges DO $$ DECLARE v_row record; BEGIN FOR v_row IN SELECT statement FROM partman_preserve_privs_temp LOOP IF v_row.statement IS NOT NULL THEN EXECUTE v_row.statement; END IF; END LOOP; END $$; TRUNCATE partman_preserve_privs_temp; -- ######################## START POSTGRESQL 11 ONLY SECTION ############################## DO $pg11only$ DECLARE v_partition_data_proc_sql text; v_undo_partition_proc_sql text; v_run_maintenance_proc_sql text; BEGIN IF current_setting('server_version_num')::int >= 110000 THEN -- ######################## START POSTGRESQL 11 ONLY SECTION ############################## INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON PROCEDURE @extschema@.partition_data_proc(text, text, int, int, text, text, int, int, boolean, text[]) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'partition_data_proc'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON PROCEDURE @extschema@.undo_partition_proc(text, text, int, int, text, boolean, int, int, boolean, text[]) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'undo_partition_proc'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON PROCEDURE @extschema@.run_maintenance_proc(int, boolean, boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'run_maintenance_proc'; DROP PROCEDURE @extschema@.partition_data_proc (text, text, int, int, text, text, int, int, boolean); DROP PROCEDURE @extschema@.undo_partition_proc(text, text, int, int, text, boolean, int, int, boolean); DROP PROCEDURE @extschema@.run_maintenance_proc(int, boolean, boolean, boolean); v_partition_data_proc_sql := $partition_data_proc$ CREATE PROCEDURE @extschema@.partition_data_proc (p_parent_table text, p_interval text DEFAULT NULL, p_batch int DEFAULT NULL, p_wait int DEFAULT 1, p_source_table text DEFAULT NULL, p_order text DEFAULT 'ASC', p_lock_wait int DEFAULT 0, p_lock_wait_tries int DEFAULT 10, p_quiet boolean DEFAULT false, p_ignored_columns text[] DEFAULT NULL) LANGUAGE plpgsql AS $$ DECLARE v_adv_lock boolean; v_batch_count int := 0; v_control text; v_control_type text; v_epoch text; v_is_autovac_off boolean := false; v_lockwait_count int := 0; v_parent_schema text; v_parent_tablename text; v_row record; v_rows_moved bigint; v_source_schema text; v_source_tablename text; v_sql text; v_total bigint := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman partition_data_proc'), hashtext(p_parent_table)); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman partition_data_proc already running for given parent table: %.', p_parent_table; RETURN; END IF; SELECT control, epoch INTO v_control, v_epoch FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: No entry in part_config found for given table: %', p_parent_table; END IF; SELECT n.nspname, c.relname INTO v_parent_schema, v_parent_tablename 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_tablename IS NULL THEN RAISE EXCEPTION 'Unable to find given parent table in system catalogs. Ensure it is schema qualified: %', p_parent_table; END IF; IF p_source_table IS NOT NULL THEN SELECT n.nspname, c.relname INTO v_source_schema, v_source_tablename FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = split_part(p_source_table, '.', 1)::name AND c.relname = split_part(p_source_table, '.', 2)::name; IF v_source_tablename IS NULL THEN RAISE EXCEPTION 'Unable to find given source table in system catalogs. Ensure it is schema qualified: %', p_source_table; END IF; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type = 'id' AND v_epoch <> 'none' THEN v_control_type := 'time'; END IF; /* -- Currently no way to catch exception and reset autovac settings back to normal. Until I can do that, leaving this feature out for now -- Leaving the functions to turn off/reset in to let people do that manually if desired IF p_autovacuum_on = false THEN -- Add this parameter back to definition when this is working -- Turn off autovac for parent, source table if set, and all child tables v_is_autovac_off := @extschema@.autovacuum_off(v_parent_schema, v_parent_tablename, v_source_schema, v_source_tablename); COMMIT; END IF; */ v_sql := format('SELECT %I.partition_data_%s (%L, p_lock_wait := %L, p_order := %L, p_analyze := false' , '@extschema@', v_control_type, p_parent_table, p_lock_wait, p_order); IF p_interval IS NOT NULL THEN v_sql := v_sql || format(', p_batch_interval := %L', p_interval); END IF; IF p_source_table IS NOT NULL THEN v_sql := v_sql || format(', p_source_table := %L', p_source_table); END IF; IF p_ignored_columns IS NOT NULL THEN v_sql := v_sql || format(', p_ignored_columns := %L', p_ignored_columns); END IF; v_sql := v_sql || ')'; RAISE DEBUG 'partition_data sql: %', v_sql; LOOP EXECUTE v_sql INTO v_rows_moved; -- If lock wait timeout, do not increment the counter IF v_rows_moved != -1 THEN v_batch_count := v_batch_count + 1; v_total := v_total + v_rows_moved; v_lockwait_count := 0; ELSE v_lockwait_count := v_lockwait_count + 1; IF v_lockwait_count > p_lock_wait_tries THEN RAISE EXCEPTION 'Quitting due to inability to get lock on next batch of rows to be moved'; END IF; END IF; IF p_quiet = false THEN IF v_rows_moved > 0 THEN RAISE NOTICE 'Batch: %, Rows moved: %', v_batch_count, v_rows_moved; ELSIF v_rows_moved = -1 THEN RAISE NOTICE 'Unable to obtain row locks for data to be moved. Trying again...'; END IF; END IF; -- If no rows left or given batch argument limit is reached IF v_rows_moved = 0 OR (p_batch > 0 AND v_batch_count >= p_batch) THEN EXIT; END IF; COMMIT; PERFORM pg_sleep(p_wait); RAISE DEBUG 'v_rows_moved: %, v_batch_count: %, v_total: %, v_lockwait_count: %, p_wait: %', p_wait, v_rows_moved, v_batch_count, v_total, v_lockwait_count; END LOOP; /* IF v_is_autovac_off = true THEN -- Reset autovac back to default if it was turned off by this procedure PERFORM @extschema@.autovacuum_reset(v_parent_schema, v_parent_tablename, v_source_schema, v_source_tablename); COMMIT; END IF; */ IF p_quiet = false THEN RAISE NOTICE 'Total rows moved: %', v_total; END IF; RAISE NOTICE 'Ensure to VACUUM ANALYZE the parent (and source table if used) after partitioning data'; /* Leaving here until I can figure out what's wrong with procedures and exception handling EXCEPTION WHEN QUERY_CANCELED THEN ROLLBACK; -- Reset autovac back to default if it was turned off by this procedure IF v_is_autovac_off = true THEN PERFORM @extschema@.autovacuum_reset(v_parent_schema, v_parent_tablename, v_source_schema, v_source_tablename); END IF; RAISE EXCEPTION '%', SQLERRM; WHEN OTHERS THEN ROLLBACK; -- Reset autovac back to default if it was turned off by this procedure IF v_is_autovac_off = true THEN PERFORM @extschema@.autovacuum_reset(v_parent_schema, v_parent_tablename, v_source_schema, v_source_tablename); END IF; RAISE EXCEPTION '%', SQLERRM; */ END; $$; $partition_data_proc$; EXECUTE v_partition_data_proc_sql; v_undo_partition_proc_sql := $undo_partition_proc$ CREATE PROCEDURE @extschema@.undo_partition_proc(p_parent_table text, p_interval text DEFAULT NULL, p_batch int DEFAULT NULL, p_wait int DEFAULT 1, p_target_table text DEFAULT NULL, p_keep_table boolean DEFAULT true, p_lock_wait int DEFAULT 0, p_lock_wait_tries int DEFAULT 10, p_quiet boolean DEFAULT false, p_ignored_columns text[] DEFAULT NULL) LANGUAGE plpgsql AS $$ DECLARE v_adv_lock boolean; v_batch_count int := 0; v_is_autovac_off boolean := false; v_lockwait_count int := 0; v_parent_schema text; v_parent_tablename text; v_partition_type text; v_partitions_undone int; v_partitions_undone_total int := 0; v_row record; v_rows_undone bigint; v_target_schema text; v_target_tablename text; v_sql text; v_total bigint := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition_proc'), hashtext(p_parent_table)); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman partition_data_proc already running for given parent table: %.', p_parent_table; RETURN; END IF; SELECT partition_type INTO v_partition_type FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: No entry in part_config found for given table: %', p_parent_table; END IF; IF v_partition_type = 'native' AND p_target_table IS NULL THEN RAISE EXCEPTION 'Natively partitioned table sets require setting the p_target_table parameter to undo partitioning.'; END IF; SELECT n.nspname, c.relname INTO v_parent_schema, v_parent_tablename 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_tablename IS NULL THEN RAISE EXCEPTION 'Unable to find given parent table in system catalogs. Ensure it is schema qualified: %', p_parent_table; END IF; IF p_target_table IS NOT NULL THEN SELECT n.nspname, c.relname INTO v_target_schema, v_target_tablename FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = split_part(p_target_table, '.', 1)::name AND c.relname = split_part(p_target_table, '.', 2)::name; IF v_target_tablename IS NULL THEN RAISE EXCEPTION 'Unable to find given target table in system catalogs. Ensure it is schema qualified: %', p_target_table; END IF; END IF; /* -- Currently no way to catch exception and reset autovac settings back to normal. Until I can do that, leaving this feature out for now -- Leaving the functions to turn off/reset in to let people do that manually if desired IF p_autovacuum_on = false THEN -- Add this parameter back to definition when this is working -- Turn off autovac for parent, source table if set, and all child tables v_is_autovac_off := @extschema@.autovacuum_off(v_parent_schema, v_parent_tablename, v_source_schema, v_source_tablename); COMMIT; END IF; */ v_sql := format('SELECT partitions_undone, rows_undone FROM %I.undo_partition (%L, p_keep_table := %L, p_lock_wait := %L' , '@extschema@', p_parent_table, p_keep_table, p_lock_wait); IF p_interval IS NOT NULL THEN v_sql := v_sql || format(', p_batch_interval := %L', p_interval); END IF; IF p_target_table IS NOT NULL THEN v_sql := v_sql || format(', p_target_table := %L', p_target_table); END IF; IF p_ignored_columns IS NOT NULL THEN v_sql := v_sql || format(', p_ignored_columns := %L', p_ignored_columns); END IF; v_sql := v_sql || ')'; RAISE DEBUG 'partition_data sql: %', v_sql; LOOP EXECUTE v_sql INTO v_partitions_undone, v_rows_undone; -- If lock wait timeout, do not increment the counter IF v_rows_undone != -1 THEN v_batch_count := v_batch_count + 1; v_partitions_undone_total := v_partitions_undone_total + v_partitions_undone; v_total := v_total + v_rows_undone; v_lockwait_count := 0; ELSE v_lockwait_count := v_lockwait_count + 1; IF v_lockwait_count > p_lock_wait_tries THEN RAISE EXCEPTION 'Quitting due to inability to get lock on next batch of rows to be moved'; END IF; END IF; IF p_quiet = false THEN IF v_rows_undone > 0 THEN RAISE NOTICE 'Batch: %, Partitions undone this batch: %, Rows undone this batch: %', v_batch_count, v_partitions_undone, v_rows_undone; ELSIF v_rows_undone = -1 THEN RAISE NOTICE 'Unable to obtain row locks for data to be moved. Trying again...'; END IF; END IF; COMMIT; -- If no rows left or given batch argument limit is reached IF v_rows_undone = 0 OR (p_batch > 0 AND v_batch_count >= p_batch) THEN EXIT; END IF; -- undo_partition functions will remove config entry once last child is dropped -- Added here to handle edge-case SELECT partition_type INTO v_partition_type FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN EXIT; END IF; PERFORM pg_sleep(p_wait); RAISE DEBUG 'v_partitions_undone: %, v_rows_undone: %, v_batch_count: %, v_total: %, v_lockwait_count: %, p_wait: %', v_partitions_undone, p_wait, v_rows_undone, v_batch_count, v_total, v_lockwait_count; END LOOP; /* IF v_is_autovac_off = true THEN -- Reset autovac back to default if it was turned off by this procedure PERFORM @extschema@.autovacuum_reset(v_parent_schema, v_parent_tablename, v_source_schema, v_source_tablename); COMMIT; END IF; */ IF p_quiet = false THEN RAISE NOTICE 'Total partitions undone: %, Total rows moved: %', v_partitions_undone_total, v_total; END IF; RAISE NOTICE 'Ensure to VACUUM ANALYZE the old parent & target table after undo has finished'; END $$; $undo_partition_proc$; EXECUTE v_undo_partition_proc_sql; v_run_maintenance_proc_sql := $run_maintenance_proc$ CREATE PROCEDURE @extschema@.run_maintenance_proc(p_wait int DEFAULT 0, p_analyze boolean DEFAULT NULL, p_jobmon boolean DEFAULT true) LANGUAGE plpgsql AS $$ DECLARE v_adv_lock boolean; v_row record; v_sql text; v_tables_list_sql text; BEGIN v_adv_lock := pg_try_advisory_lock(hashtext('pg_partman run_maintenance')); IF v_adv_lock = false THEN RAISE NOTICE 'Partman maintenance already running or another session has not released its advisory lock.'; RETURN; END IF; v_tables_list_sql := 'SELECT parent_table FROM @extschema@.part_config WHERE undo_in_progress = false AND automatic_maintenance = ''on'''; FOR v_row IN EXECUTE v_tables_list_sql LOOP /* * Run maintenance with a commit between each partition set * TODO - Once PG11 is more mainstream, see about more full conversion of run_maintenance function as well as turning * create_partition* functions into procedures to commit after every child table is made. May need to wait * for more PROCEDURE features as well (return values, search_path, etc). * - Also see about swapping names so this is the main object to call for maintenance instead of a function. */ v_sql := format('SELECT %I.run_maintenance(%L, p_jobmon := %L', '@extschema@', v_row.parent_table, p_jobmon); IF p_analyze IS NOT NULL THEN v_sql := v_sql || format(', p_analyze := %L', p_analyze); END IF; v_sql := v_sql || ')'; RAISE DEBUG 'v_sql run_maintenance_proc: %', v_sql; EXECUTE v_sql; COMMIT; PERFORM pg_sleep(p_wait); END LOOP; PERFORM pg_advisory_unlock(hashtext('pg_partman run_maintenance')); END $$; $run_maintenance_proc$; EXECUTE v_run_maintenance_proc_sql; -- Restore dropped object privileges DO $$ DECLARE v_row record; BEGIN FOR v_row IN SELECT statement FROM partman_preserve_privs_temp LOOP IF v_row.statement IS NOT NULL THEN EXECUTE v_row.statement; END IF; END LOOP; END $$; -- ######################## END POSTGRESQL 11 ONLY SECTION ############################## END IF; END $pg11only$; -- ######################## END POSTGRESQL 11 ONLY SECTION ############################## DROP TABLE IF EXISTS partman_preserve_privs_temp; pg_partman-4.6.0/updates/pg_partman--4.5.0--4.5.1.sql000066400000000000000000001663051412761706100214460ustar00rootroot00000000000000-- 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 $$; pg_partman-4.6.0/updates/pg_partman--4.5.1--4.6.0.sql000066400000000000000000005542441412761706100214520ustar00rootroot00000000000000-- Support for PostgreSQL 14 -- Better handle custom intervals that don't fall on typical datetime boundaries (monthly, daily, etc). Previously if you chose a custom interval that did not initially align on a typical boundary, pg_partman would always try and truncate the initial table's datetime start value to one of those typical boundaries. This could lead to child table constraints that were not expected. A new parameter to create_parent(), 'p_date_trunc_interval' has been added to allow you to tell pg_partman how to truncate the initial datetime value to better meet custom interval needs. This accepts any valid interval value that the built-in 'date_trunc()' function would accept. (Ex: 9 week interval starting on 2021-04-05 would need to be truncated by 'week' vs 'month'. Otherwise pg_partman would truncate by 'month' and the partitions would start on 2021-04-01 and result in unexpected child table boundaries). -- When dropping tables for retention, DROP...CASCADE is no longer the default. This avoids foreign key references TO the partition set getting dropped if any child table is dropped. Set the option "drop_cascade_fk" in part_config to allow the old behavior if desired. This cascade is only allowed with non-native, trigger-based partitioning and is not supported with sub-partitioning at this time. -- Fixed show_partition_info() to properly return boundary values no matter what custom interval may be used for the partition set. Update documentation to note that the show_partition_info() boundary results are INCLUSIVE for returned start values and EXCLUSIVE for returned end values. -- Added p_drop_cascade option to undo_partition()/undo_partition_proc() to allow undoing sub-partition sets from parent tables higher in the inheritance tree. Only applies when p_keep_tables is set to false. Note this causes all child tables below a sub-partition parent to be dropped when that parent is dropped. -- Deprecate pg_partman's specialized UPSERT support for native partitioning in PG11+. Raise error for users to use built-in INSERT ON CONFLICT feature instead. -- Better support NOTIFY commands with the extension's built-in background worker. Thank you to @eisaev on Github for this fix. (GitHub Issue #384). -- Fixed edge case in sub-partitioning where an additional subpartition child table could be created that is just outside the max boundary limit of its parent table boundaries. -- Added new optional parameter to drop_partition_time() to tell partman to use a different reference timestamp from which to determine which partitions should be affected. Thanks to @wesselvdv on Github for this feature (Github PR#386). ALTER TABLE @extschema@.part_config ADD COLUMN drop_cascade_fk BOOLEAN NOT NULL DEFAULT false; ALTER TABLE @extschema@.part_config_sub ADD COLUMN sub_date_trunc_interval TEXT; CREATE TEMP TABLE partman_preserve_privs_temp (statement text); INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.check_subpart_sameconfig(text) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'check_subpart_sameconfig'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_parent(text, text, text, text, text[], int, text, text, boolean, text, text, text[], boolean, text, boolean, text) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_parent'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.create_sub_parent(text, text, text, text, text, text[], int, text, boolean, text, text, boolean, boolean , text) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'create_sub_parent'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.undo_partition(text, int, text, boolean, numeric, text, text[], boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'undo_partition'; INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON FUNCTION @extschema@.drop_partition_time(text, interval, boolean, boolean, text, timestamptz) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'drop_partition_time'; DROP FUNCTION @extschema@.check_subpart_sameconfig(text); DROP FUNCTION @extschema@.create_parent(text, text, text, text, text[], int, text, text, boolean, text, text, text[], boolean, text, boolean); DROP FUNCTION @extschema@.create_sub_parent(text, text, text, text, text, text[], int, text, boolean, text, text, boolean, boolean); DROP FUNCTION @extschema@.undo_partition(text, int, text, boolean, numeric, text, text[]); DROP FUNCTION @extschema@.drop_partition_time(text, interval, boolean, boolean, text); CREATE OR REPLACE FUNCTION @extschema@.drop_partition_id(p_parent_table text, p_retention bigint DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL) RETURNS int LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_control text; v_control_type text; v_count int; v_drop_cascade_fk boolean; v_drop_count int := 0; v_index record; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_max bigint; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval bigint; v_partition_id bigint; v_partition_type text; v_retention bigint; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_row record; v_row_max_id record; v_sql text; v_step_id bigint; v_sub_parent text; BEGIN /* * Function to drop child tables from an id-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman drop_partition_id')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_id already running.'; RETURN 0; END IF; IF p_retention IS NULL THEN SELECT partition_interval::bigint , partition_type , control , retention::bigint , retention_keep_table , retention_keep_index , retention_schema , jobmon , drop_cascade_fk INTO v_partition_interval , v_partition_type , v_control , v_retention , v_retention_keep_table , v_retention_keep_index , v_retention_schema , v_jobmon , v_drop_cascade_fk FROM @extschema@.part_config WHERE parent_table = p_parent_table AND retention IS NOT NULL; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE -- Allow override of configuration options SELECT partition_interval::bigint , partition_type , control , retention_keep_table , retention_keep_index , retention_schema , jobmon , drop_cascade_fk INTO v_partition_interval , v_partition_type , v_control , v_retention_keep_table , v_retention_keep_index , v_retention_schema , v_jobmon , v_drop_cascade_fk FROM @extschema@.part_config WHERE parent_table = p_parent_table; v_retention := p_retention; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'id' THEN RAISE EXCEPTION 'Data type of control column in given partition set is not an integer type'; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; -- Loop through child tables starting from highest to get current max value in partition set -- Avoids doing a scan on entire partition set and/or getting any values accidentally in parent. FOR v_row_max_id IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'DESC') LOOP EXECUTE format('SELECT max(%I) FROM %I.%I', v_control, v_row_max_id.partition_schemaname, v_row_max_id.partition_tablename) INTO v_max; IF v_max IS NOT NULL THEN EXIT; END IF; END LOOP; SELECT sub_parent INTO v_sub_parent FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; -- Loop through child tables of the given parent -- Must go in ascending order to avoid dropping what may be the "last" partition in the set after dropping tables that match retention period FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'ASC') LOOP SELECT child_start_id INTO v_partition_id FROM @extschema@.show_partition_info(v_row.partition_schemaname||'.'||v_row.partition_tablename , v_partition_interval::text , p_parent_table); -- Add one interval since partition names contain the start of the constraint period IF v_retention <= (v_max - (v_partition_id + v_partition_interval)) THEN -- Do not allow final partition to be dropped if it is not a sub-partition parent SELECT count(*) INTO v_count FROM @extschema@.show_partitions(p_parent_table); IF v_count = 1 AND v_sub_parent IS NULL THEN RAISE WARNING 'Attempt to drop final partition in partition set % as part of retention policy. If you see this message multiple times for the same table, advise reviewing retention policy and/or data entry into the partition set. Also consider setting "infinite_time_partitions = true" if there are large gaps in data insertion.).', p_parent_table; CONTINUE; END IF; -- Only create a jobmon entry if there's actual retention work done IF v_jobmon_schema IS NOT NULL AND v_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN DROP ID PARTITION: %s', p_parent_table)); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Detach/Uninherit table %s.%s from %s', v_row.partition_schemaname, v_row.partition_tablename, p_parent_table)); END IF; IF v_retention_keep_table = true THEN -- No need to detach partition before dropping since it's going away anyway -- Avoids issue of FKs not allowing detachment (Github Issue #294). IF v_partition_type = 'native' THEN EXECUTE format('ALTER TABLE %I.%I DETACH PARTITION %I.%I' , v_parent_schema , v_parent_tablename , v_row.partition_schemaname , v_row.partition_tablename); ELSE EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_row.partition_schemaname , v_row.partition_tablename , v_parent_schema , v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop table %s.%s', v_row.partition_schemaname, v_row.partition_tablename)); END IF; v_sql := 'DROP TABLE %I.%I'; IF v_drop_cascade_fk OR v_sub_parent IS NOT NULL THEN v_sql := v_sql || ' CASCADE'; END IF; EXECUTE format(v_sql, v_row.partition_schemaname, v_row.partition_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN IF v_partition_type = 'partman' OR ( v_partition_type = 'native' AND current_setting('server_version_num')::int < 110000) THEN -- Cannot drop child indexes on native partition sets in PG11+ FOR v_index IN WITH child_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE c1.relname = v_row.partition_tablename::name AND n1.nspname = v_row.partition_schema::name ) SELECT c.relname as name , con.conname FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid LEFT JOIN pg_catalog.pg_constraint con ON i.indexrelid = con.conindid JOIN child_info ON i.indrelid = child_info.oid LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop index %s from %s.%s' , v_index.name , v_row.partition_schemaname , v_row.partition_tablename)); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I DROP CONSTRAINT %I', v_row.partition_schemaname, v_row.partition_tablename, v_index.conname); ELSE EXECUTE format('DROP INDEX %I.%I', v_row.partition_schemaname, v_index.name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; -- end native/11 check END IF; -- end v_retention_keep_index IF ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Moving table %s.%s to schema %s' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema)); END IF; EXECUTE format('ALTER TABLE %I.%I SET SCHEMA %I' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if -- If child table is a subpartition, remove it from part_config & part_config_sub (should cascade due to FK) DELETE FROM @extschema@.part_config WHERE parent_table = v_row.partition_schemaname ||'.'||v_row.partition_tablename; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', format('%s partitions dropped.', v_drop_count)); 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_drop_count; 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 DROP ID PARTITION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE FUNCTION @extschema@.drop_partition_time(p_parent_table text, p_retention interval DEFAULT NULL, p_keep_table boolean DEFAULT NULL, p_keep_index boolean DEFAULT NULL, p_retention_schema text DEFAULT NULL, p_reference_timestamp timestamptz DEFAULT CURRENT_TIMESTAMP) RETURNS int LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_control text; v_control_type text; v_count int; v_datetime_string text; v_drop_cascade_fk boolean; v_drop_count int := 0; v_epoch text; v_index record; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_interval interval; v_partition_timestamp timestamptz; v_partition_type text; v_retention interval; v_retention_keep_index boolean; v_retention_keep_table boolean; v_retention_schema text; v_row record; v_sql text; v_step_id bigint; v_sub_parent text; BEGIN /* * Function to drop child tables from a time-based partition set. * Options to move table to different schema, drop only indexes or actually drop the table from the database. */ v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman drop_partition_time')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'drop_partition_time already running.'; RETURN 0; END IF; -- Allow override of configuration options IF p_retention IS NULL THEN SELECT partition_type , control , partition_interval::interval , epoch , retention::interval , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_partition_type , v_control , v_partition_interval , v_epoch , v_retention , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table AND retention IS NOT NULL; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table with a retention period not found: %', p_parent_table; END IF; ELSE SELECT partition_type , partition_interval::interval , epoch , retention_keep_table , retention_keep_index , datetime_string , retention_schema , jobmon INTO v_partition_type , v_partition_interval , v_epoch , v_retention_keep_table , v_retention_keep_index , v_datetime_string , v_retention_schema , v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; v_retention := p_retention; IF v_partition_interval IS NULL THEN RAISE EXCEPTION 'Configuration for given parent table not found: %', p_parent_table; END IF; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'time' THEN IF (v_control_type = 'id' AND v_epoch = 'none') OR v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run on partition set without time based control column or epoch flag set with an id column. Found control: %, epoch: %', v_control_type, v_epoch; END IF; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); IF p_keep_table IS NOT NULL THEN v_retention_keep_table = p_keep_table; END IF; IF p_keep_index IS NOT NULL THEN v_retention_keep_index = p_keep_index; END IF; IF p_retention_schema IS NOT NULL THEN v_retention_schema = p_retention_schema; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; SELECT sub_parent INTO v_sub_parent FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; -- Loop through child tables of the given parent -- Must go in ascending order to avoid dropping what may be the "last" partition in the set after dropping tables that match retention period FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'ASC') LOOP -- pull out datetime portion of partition's tablename to make the next one SELECT child_start_time INTO v_partition_timestamp FROM @extschema@.show_partition_info(v_row.partition_schemaname||'.'||v_row.partition_tablename , v_partition_interval::text , p_parent_table); -- Add one interval since partition names contain the start of the constraint period IF v_retention < (p_reference_timestamp - (v_partition_timestamp + v_partition_interval)) THEN -- Do not allow final partition to be dropped if it is not a sub-partition parent SELECT count(*) INTO v_count FROM @extschema@.show_partitions(p_parent_table); IF v_count = 1 AND v_sub_parent IS NULL THEN RAISE WARNING 'Attempt to drop final partition in partition set % as part of retention policy. If you see this message multiple times for the same table, advise reviewing retention policy and/or data entry into the partition set. Also consider setting "infinite_time_partitions = true" if there are large gaps in data insertion.).', p_parent_table; CONTINUE; END IF; -- Only create a jobmon entry if there's actual retention work done IF v_jobmon_schema IS NOT NULL AND v_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN DROP TIME PARTITION: %s', p_parent_table)); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Detach/Uninherit table %s.%s from %s' , v_row.partition_schemaname , v_row.partition_tablename , p_parent_table)); END IF; IF v_retention_keep_table = true THEN -- No need to detach partition before dropping since it's going away anyway -- Avoids issue of FKs not allowing detachment (Github Issue #294). IF v_partition_type = 'native' THEN EXECUTE format('ALTER TABLE %I.%I DETACH PARTITION %I.%I' , v_parent_schema , v_parent_tablename , v_row.partition_schemaname , v_row.partition_tablename); ELSE EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_row.partition_schemaname , v_row.partition_tablename , v_parent_schema , v_parent_tablename); END IF; END IF; IF v_partition_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_row.partition_schemaname||'.'||v_row.partition_tablename; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; IF v_retention_schema IS NULL THEN IF v_retention_keep_table = false THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop table %s.%s', v_row.partition_schemaname, v_row.partition_tablename)); END IF; v_sql := 'DROP TABLE %I.%I'; IF v_drop_cascade_fk OR v_sub_parent IS NOT NULL THEN v_sql := v_sql || ' CASCADE'; END IF; EXECUTE format(v_sql, v_row.partition_schemaname, v_row.partition_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; ELSIF v_retention_keep_index = false THEN IF v_partition_type = 'partman' OR ( v_partition_type = 'native' AND current_setting('server_version_num')::int < 110000) THEN -- Cannot drop child indexes on native partition sets in PG11+ FOR v_index IN WITH child_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE c1.relname = v_row.partition_tablename::name AND n1.nspname = v_row.partition_schemaname::name ) SELECT c.relname as name , con.conname FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid LEFT JOIN pg_catalog.pg_constraint con ON i.indexrelid = con.conindid JOIN child_info ON i.indrelid = child_info.oid LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Drop index %s from %s.%s' , v_index.name , v_row.partition_schemaname , v_row.partition_tablename)); END IF; IF v_index.conname IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I DROP CONSTRAINT %I' , v_row.partition_schemaname , v_row.partition_tablename , v_index.conname); ELSE EXECUTE format('DROP INDEX %I.%I', v_parent_schema, v_index.name); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; END IF; -- end native/11 check END IF; -- end v_retention_keep_index IF ELSE -- Move to new schema IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Moving table %s.%s to schema %s' , v_row.partition_schemaname , v_row.partition_tablename , v_retention_schema)); END IF; EXECUTE format('ALTER TABLE %I.%I SET SCHEMA %I', v_row.partition_schemaname, v_row.partition_tablename, v_retention_schema); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; -- End retention schema if -- If child table is a subpartition, remove it from part_config & part_config_sub (should cascade due to FK) DELETE FROM @extschema@.part_config WHERE parent_table = v_row.partition_schemaname||'.'||v_row.partition_tablename; v_drop_count := v_drop_count + 1; END IF; -- End retention check IF END LOOP; -- End child table loop IF v_jobmon_schema IS NOT NULL THEN IF v_job_id IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Finished partition drop maintenance'); PERFORM update_step(v_step_id, 'OK', format('%s partitions dropped.', v_drop_count)); 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_drop_count; 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 DROP TIME PARTITION: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE OR REPLACE FUNCTION @extschema@.dump_partitioned_table_definition( p_parent_table TEXT, p_ignore_template_table BOOLEAN DEFAULT false ) RETURNS TEXT LANGUAGE PLPGSQL STABLE AS $$ DECLARE v_create_parent_definition TEXT; v_update_part_config_definition TEXT; -- Columns from part_config table. v_parent_table TEXT; -- NOT NULL v_control TEXT; -- NOT NULL v_partition_type TEXT; -- NOT NULL v_partition_interval TEXT; -- NOT NULL v_constraint_cols TEXT[]; v_premake integer; -- NOT NULL v_optimize_trigger integer; -- NOT NULL v_optimize_constraint integer; -- NOT NULL v_epoch text; -- NOT NULL v_inherit_fk BOOLEAN; -- NOT NULL v_retention TEXT; v_retention_schema TEXT; v_retention_keep_table BOOLEAN; -- NOT NULL v_retention_keep_index BOOLEAN; -- NOT NULL v_infinite_time_partitions BOOLEAN; -- NOT NULL v_datetime_string TEXT; v_automatic_maintenance TEXT; -- NOT NULL v_jobmon BOOLEAN; -- NOT NULL v_sub_partition_set_full BOOLEAN; -- NOT NULL v_trigger_exception_handling BOOLEAN; v_upsert TEXT; -- NOT NULL v_trigger_return_null BOOLEAN; -- NOT NULL v_template_table TEXT; v_publications TEXT[]; v_inherit_privileges BOOLEAN; -- DEFAULT false v_constraint_valid BOOLEAN; -- DEFAULT true NOT NULL v_subscription_refresh text; v_drop_cascade_fk boolean; -- DEFAULT false NOT NULL BEGIN SELECT pc.parent_table, pc.control, pc.partition_type, pc.partition_interval, pc.constraint_cols, pc.premake, pc.optimize_trigger, pc.optimize_constraint, pc.epoch, pc.inherit_fk, pc.retention, pc.retention_schema, pc.retention_keep_table, pc.retention_keep_index, pc.infinite_time_partitions, pc.datetime_string, pc.automatic_maintenance, pc.jobmon, pc.sub_partition_set_full, pc.trigger_exception_handling, pc.upsert, pc.trigger_return_null, pc.template_table, pc.publications, pc.inherit_privileges, pc.constraint_valid, pc.subscription_refresh, pc.drop_cascade_fk INTO v_parent_table, v_control, v_partition_type, v_partition_interval, v_constraint_cols, v_premake, v_optimize_trigger, v_optimize_constraint, v_epoch, v_inherit_fk, v_retention, v_retention_schema, v_retention_keep_table, v_retention_keep_index, v_infinite_time_partitions, v_datetime_string, v_automatic_maintenance, v_jobmon, v_sub_partition_set_full, v_trigger_exception_handling, v_upsert, v_trigger_return_null, v_template_table, v_publications, v_inherit_privileges, v_constraint_valid, v_subscription_refresh, v_drop_cascade_fk FROM @extschema@.part_config pc WHERE pc.parent_table = p_parent_table; IF v_partition_type = 'partman' THEN CASE WHEN v_partition_interval::INTERVAL = '1 year'::INTERVAL THEN v_partition_interval := 'yearly'; WHEN v_partition_interval::INTERVAL = '3 months'::INTERVAL THEN v_partition_interval := 'quarterly'; WHEN v_partition_interval::INTERVAL = '1 month'::INTERVAL THEN v_partition_interval := 'monthly'; WHEN v_partition_interval::INTERVAL = '1 week'::INTERVAL THEN v_partition_interval := 'weekly'; WHEN v_partition_interval::INTERVAL = '1 day'::INTERVAL THEN v_partition_interval := 'daily'; WHEN v_partition_interval::INTERVAL = '1 hour'::INTERVAL THEN v_partition_interval := 'hourly'; WHEN v_partition_interval::INTERVAL = '30 mins'::INTERVAL THEN v_partition_interval := 'half-hour'; WHEN v_partition_interval::INTERVAL = '15 mins'::INTERVAL THEN v_partition_interval := 'quarter-hour'; ELSE RAISE EXCEPTION 'Partitioning interval not recognized for "partman" partitioning type'; END CASE; END IF; IF v_partition_type = 'native' AND p_ignore_template_table THEN v_template_table := NULL; END IF; v_create_parent_definition := format( E'SELECT @extschema@.create_parent( \tp_parent_table := %L, \tp_control := %L, \tp_type := %L, \tp_interval := %L, \tp_constraint_cols := %L, \tp_premake := %s, \tp_automatic_maintenance := %L, \tp_inherit_fk := %L, \tp_epoch := %L, \tp_upsert := %L, \tp_publications := %L, \tp_trigger_return_null := %L, \tp_template_table := %L, \tp_jobmon := %L \t-- v_start_partition is intentionally ignored as there \t-- isn''t any obviously correct definition. );', v_parent_table, v_control, v_partition_type, v_partition_interval, v_constraint_cols, v_premake, v_automatic_maintenance, v_inherit_fk, v_epoch, v_upsert, v_publications, v_trigger_return_null, v_template_table, v_jobmon ); v_update_part_config_definition := format( E'UPDATE @extschema@.part_config SET \toptimize_trigger = %s, \toptimize_constraint = %s, \tretention = %L, \tretention_schema = %L, \tretention_keep_table = %L, \tretention_keep_index = %L, \tinfinite_time_partitions = %L, \tdatetime_string = %L, \tsub_partition_set_full = %L, \ttrigger_exception_handling = %L, \tinherit_privileges = %L, \tconstraint_valid = %L, \tsubscription_refresh = %L WHERE parent_table = %L;', v_optimize_trigger, v_optimize_constraint, v_retention, v_retention_schema, v_retention_keep_table, v_retention_keep_index, v_infinite_time_partitions, v_datetime_string, v_sub_partition_set_full, v_trigger_exception_handling, v_inherit_privileges, v_constraint_valid, v_subscription_refresh, v_parent_table ); RETURN concat_ws(E'\n', v_create_parent_definition, v_update_part_config_definition ); END $$; CREATE OR REPLACE FUNCTION @extschema@.reapply_privileges(p_parent_table text) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_row record; v_step_id bigint; BEGIN /* * Function to re-apply ownership & privileges on all child tables in a partition set using parent table as reference */ SELECT jobmon INTO v_jobmon FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_jobmon IS NULL THEN RAISE EXCEPTION 'Given table is not managed by this extention: %', p_parent_table; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(p_parent_table, '.', 1)::name AND tablename = split_part(p_parent_table, '.', 2)::name; IF v_parent_tablename IS NULL THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RAISE EXCEPTION 'Given parent table does not exist: %', p_parent_table; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN RE-APPLYING PRIVILEGES TO ALL CHILD TABLES OF: %s', p_parent_table)); END IF; FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table, 'ASC', p_include_default := true) LOOP PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_row.partition_schemaname, v_row.partition_tablename, v_job_id); END LOOP; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); 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 RE-APPLYING PRIVILEGES TO ALL CHILD TABLES OF: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE 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 , p_date_trunc_interval text DEFAULT NULL) 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' AND current_setting('server_version_num')::int >= 110000 THEN RAISE EXCEPTION 'The pg_partman upsert feature is not supported with native partitioning in PG11+. Use the built-in support for INSERT ON CONFLICT with native partitioning instead.'; 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)); v_datetime_string := 'YYYY'; IF p_date_trunc_interval IS NOT NULL THEN v_base_timestamp := date_trunc(p_date_trunc_interval, v_start_time); IF v_time_interval >= '1 day' THEN v_datetime_string := v_datetime_string || '_MM_DD'; ELSE v_datetime_string := v_datetime_string || '_MM_DD_HH24MISS'; END IF; ELSE 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 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 END IF; -- end p_date_trunc_interval IF RAISE DEBUG 'create_parent(): parent_table: %, v_base_timestamp: %', p_parent_table, v_base_timestamp; 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 $$; CREATE OR REPLACE FUNCTION @extschema@.show_partition_info(p_child_table text , p_partition_interval text DEFAULT NULL , p_parent_table text DEFAULT NULL , OUT child_start_time timestamptz , OUT child_end_time timestamptz , OUT child_start_id bigint , OUT child_end_id bigint , OUT suffix text) RETURNS record LANGUAGE plpgsql STABLE AS $$ DECLARE v_child_schema text; v_child_tablename text; v_start_time_string text; v_control text; v_control_type text; v_datetime_string text; v_epoch text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_table text; v_partition_interval text; v_partition_type text; v_quarter text; v_start_time_epoch double precision; v_suffix text; v_suffix_position int; v_year text; BEGIN /* * Show the data boundries for a given child table as well as the suffix that will be used. * Passing the parent table argument improves performance by avoiding a catalog lookup. * Passing an interval lets you set one different than the default configured one if desired. */ 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'); SELECT n.nspname, c.relname INTO v_child_schema, v_child_tablename FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = split_part(p_child_table, '.', 1)::name AND c.relname = split_part(p_child_table, '.', 2)::name; IF v_child_tablename IS NULL THEN EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RAISE EXCEPTION 'Child table given does not exist (%)', p_child_table; END IF; IF p_parent_table IS NULL THEN SELECT n.nspname||'.'|| c.relname INTO v_parent_table FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhparent JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE h.inhrelid::regclass = p_child_table::regclass; ELSE v_parent_table := p_parent_table; END IF; IF p_partition_interval IS NULL THEN SELECT control, partition_interval, partition_type, datetime_string, epoch INTO v_control, v_partition_interval, v_partition_type, v_datetime_string, v_epoch FROM @extschema@.part_config WHERE parent_table = v_parent_table; ELSE v_partition_interval := p_partition_interval; SELECT control, partition_type, datetime_string, epoch INTO v_control, v_partition_type, v_datetime_string, v_epoch FROM @extschema@.part_config WHERE parent_table = v_parent_table; END IF; IF v_control IS NULL THEN RAISE EXCEPTION 'Parent table of given child not managed by pg_partman: %', v_parent_table; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_child_schema, v_child_tablename, v_control); v_suffix_position := (length(v_child_tablename) - position('p_' in reverse(v_child_tablename))) + 2; v_suffix := substring(v_child_tablename from v_suffix_position); RAISE DEBUG 'show_partition_info: v_child_schema: %, v_child_tablename: %, v_suffix: %', v_child_schema, v_child_tablename, v_suffix; IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN IF v_partition_type = 'native' THEN -- Look at actual partition bounds in catalog and pull values from there. -- For native partitioning, handles any possible interval type much better than old methods in remaining conditions of this IF block SELECT (regexp_match(pg_get_expr(c.relpartbound, c.oid, true) , $REGEX$\(([^)]+)\) TO \(([^)]+)\)$REGEX$))[1]::text INTO v_start_time_string FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_child_tablename AND n.nspname = v_child_schema; IF v_control_type = 'time' THEN child_start_time := v_start_time_string::timestamptz; ELSIF (v_control_type = 'id' AND v_epoch <> 'none') THEN -- bigint data type is stored as a single-quoted string in the partition expression. Must strip quotes for valid type-cast. v_start_time_string := trim(BOTH '''' FROM v_start_time_string); IF v_epoch = 'seconds' THEN child_start_time := to_timestamp(v_start_time_string::double precision); ELSIF v_epoch = 'milliseconds' THEN child_start_time := to_timestamp((v_start_time_string::double precision) / 1000); END IF; ELSE RAISE EXCEPTION 'Unexpected code path in show_partition_info(). Please report this bug with the configuration that lead to it.'; END IF; ELSIF v_partition_interval::interval <> '3 months' OR (v_partition_interval::interval = '3 months' AND v_partition_type = 'time-custom') THEN child_start_time := to_timestamp(v_suffix, v_datetime_string); ELSE -- to_timestamp doesn't recognize 'Q' date string formater. Handle it v_year := split_part(v_suffix, 'q', 1); v_quarter := split_part(v_suffix, 'q', 2); CASE WHEN v_quarter = '1' THEN child_start_time := to_timestamp(v_year || '-01-01', 'YYYY-MM-DD'); WHEN v_quarter = '2' THEN child_start_time := to_timestamp(v_year || '-04-01', 'YYYY-MM-DD'); WHEN v_quarter = '3' THEN child_start_time := to_timestamp(v_year || '-07-01', 'YYYY-MM-DD'); WHEN v_quarter = '4' THEN child_start_time := to_timestamp(v_year || '-10-01', 'YYYY-MM-DD'); ELSE -- handle case when partition name did not use "q" convetion child_start_time := to_timestamp(v_suffix, v_datetime_string); END CASE; END IF; child_end_time := (child_start_time + v_partition_interval::interval); ELSIF v_control_type = 'id' THEN -- Note for future updates, if trigger based partitioning is dropped, do catalog lookup for integer boundaries similar to time. child_start_id := v_suffix::bigint; child_end_id := (child_start_id + v_partition_interval::bigint) - 1; ELSE RAISE EXCEPTION 'Invalid partition type encountered in show_partition_info()'; END IF; suffix = v_suffix; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN; END $$; CREATE FUNCTION @extschema@.undo_partition( p_parent_table text , p_batch_count int DEFAULT 1 , p_batch_interval text DEFAULT NULL , p_keep_table boolean DEFAULT true , p_lock_wait numeric DEFAULT 0 , p_target_table text DEFAULT NULL , p_ignored_columns text[] DEFAULT NULL , p_drop_cascade boolean DEFAULT false , OUT partitions_undone int , OUT rows_undone bigint) RETURNS record LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_adv_lock boolean; v_batch_interval_id bigint; v_batch_interval_time interval; v_batch_loop_count int := 0; v_child_loop_total bigint := 0; v_child_table text; v_col text; v_column_list text; v_control text; v_control_type text; v_child_min_id bigint; v_child_min_time timestamptz; v_epoch text; v_function_name text; v_jobmon boolean; v_jobmon_schema text; v_job_id bigint; v_inner_loop_count int; v_lock_iter int := 1; v_lock_obtained boolean := FALSE; v_new_search_path text; v_old_search_path text; v_parent_schema text; v_parent_tablename text; v_partition_expression text; v_partition_interval text; v_partition_type text; v_relkind char; v_row record; v_rowcount bigint; v_sql text; v_step_id bigint; v_sub_count int; v_target_schema text; v_target_tablename text; v_template_schema text; v_template_siblings int; v_template_table text; v_template_tablename text; v_total bigint := 0; v_trig_name text; v_undo_count int := 0; BEGIN /* * For native, moves data to new, target table since data cannot be moved to parent. * Leaves old parent table as is and does not change name of new table. * For trigger-based, moves data to parent */ v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition_native')); IF v_adv_lock = 'false' THEN RAISE NOTICE 'undo_partition_native already running.'; partitions_undone = -1; RETURN; END IF; IF p_parent_table = p_target_table THEN RAISE EXCEPTION 'Target table cannot be the same as the parent table'; END IF; SELECT partition_interval::text , partition_type , control , jobmon , epoch , template_table INTO v_partition_interval , v_partition_type , v_control , v_jobmon , v_epoch , v_template_table FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_control IS NULL THEN RAISE EXCEPTION 'No configuration found for pg_partman for given parent table: %', p_parent_table; END IF; IF v_partition_type = 'native' AND p_target_table IS NULL THEN RAISE EXCEPTION 'Natively partitioned tables require setting the p_target_table option'; END IF; SELECT n.nspname, c.relname, c.relkind INTO v_parent_schema, v_parent_tablename, v_relkind 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_tablename IS NULL THEN RAISE EXCEPTION 'Given parent table not found in system catalogs: %', p_parent_table; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN IF p_batch_interval IS NULL THEN v_batch_interval_time := v_partition_interval::interval; ELSE v_batch_interval_time := p_batch_interval::interval; END IF; ELSIF v_control_type = 'id' THEN IF p_batch_interval IS NULL THEN v_batch_interval_id := v_partition_interval::bigint; ELSE v_batch_interval_id := p_batch_interval::bigint; END IF; ELSE RAISE EXCEPTION 'Data type of control column in given partition set must be either data/time or integer.'; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Check if any child tables are themselves partitioned or part of an inheritance tree. Prevent undo at this level if so. -- Need to lock child tables at all levels before multi-level undo can be performed safely. FOR v_row IN SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(p_parent_table) LOOP SELECT count(*) INTO v_sub_count FROM pg_catalog.pg_inherits i JOIN pg_catalog.pg_class c ON i.inhparent = c.oid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = v_row.partition_tablename::name AND n.nspname = v_row.partition_schemaname::name; IF v_sub_count > 0 THEN RAISE EXCEPTION 'Child table for this parent has child table(s) itself (%). Run undo partitioning on this table or remove inheritance first to ensure all data is properly moved to parent', v_row.partition_schemaname||'.'||v_row.partition_tablename; END IF; END LOOP; IF p_target_table IS NOT NULL THEN SELECT n.nspname, c.relname INTO v_target_schema, v_target_tablename FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = split_part(p_target_table, '.', 1)::name AND c.relname = split_part(p_target_table, '.', 2)::name; ELSE v_target_schema := v_parent_schema; v_target_tablename := v_parent_tablename; END IF; IF v_target_tablename IS NULL THEN RAISE EXCEPTION 'Given target table not found in system catalogs: %', p_target_table; END IF; IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN UNDO PARTITIONING: %s', p_parent_table)); v_step_id := add_step(v_job_id, format('Undoing partitioning for table %s', p_parent_table)); END IF; v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) END; -- Stops new time partitions from being made as well as stopping child tables from being dropped if they were configured with a retention period. UPDATE @extschema@.part_config SET undo_in_progress = true WHERE parent_table = p_parent_table; IF v_partition_type != 'native' THEN -- Stop data going into child tables on non-native partition sets. v_trig_name := @extschema@.check_name_length(p_object_name := v_parent_tablename, p_suffix := '_part_trig'); v_function_name := @extschema@.check_name_length(v_parent_tablename, '_part_trig_func', FALSE); -- Double-check for proper object existence SELECT tgname INTO v_trig_name FROM pg_catalog.pg_trigger t JOIN pg_catalog.pg_class c ON t.tgrelid = c.oid WHERE tgname = v_trig_name::name AND c.relname = v_parent_tablename::name; SELECT proname INTO v_function_name FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid WHERE n.nspname = v_parent_schema::name AND proname = v_function_name::name; IF v_trig_name IS NOT NULL THEN -- lockwait for trigger drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_parent_tablename); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on parent table to remove trigger'; partitions_undone = -1; RETURN; END IF; END IF; -- END p_lock_wait IF EXECUTE format('DROP TRIGGER IF EXISTS %I ON %I.%I', v_trig_name, v_parent_schema, v_parent_tablename); END IF; -- END trigger IF v_lock_obtained := FALSE; -- reset for reuse later IF v_function_name IS NOT NULL THEN EXECUTE format('DROP FUNCTION IF EXISTS %I.%I()', v_parent_schema, v_function_name); END IF; END IF; -- end pg_partman trigger cleanup IF v_jobmon_schema IS NOT NULL THEN IF (v_trig_name IS NOT NULL OR v_function_name IS NOT NULL) THEN PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process. Removed trigger & trigger function'); ELSE PERFORM update_step(v_step_id, 'OK', 'Stopped partition creation process.'); END IF; END IF; -- Generate column list to use in SELECT/INSERT statements below. Allows for exclusion of GENERATED (or any other desired) columns. v_sql := format ('SELECT ''"''||string_agg(attname, ''","'')||''"'' 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 n.nspname = %L AND c.relname = %L AND a.attnum > 0 AND a.attisdropped = false' , v_target_schema , v_target_tablename); IF p_ignored_columns IS NOT NULL THEN FOREACH v_col IN ARRAY p_ignored_columns LOOP v_sql := v_sql || format(' AND attname != %L ', v_col); END LOOP; END IF; EXECUTE v_sql INTO v_column_list; <> LOOP -- Get ordered list of child table in set. Store in variable one at a time per loop until none are left or batch count is reached. -- This easily allows it to loop over same child table until empty or move onto next child table after it's dropped -- Include the native default table to ensure all data there is removed as well (final parameter = true) SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC', TRUE) LIMIT 1; EXIT outer_child_loop WHEN v_child_table IS NULL; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Removing child partition: %s.%s', v_parent_schema, v_child_table)); END IF; IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN EXECUTE format('SELECT min(%s) FROM %I.%I', v_partition_expression, v_parent_schema, v_child_table) INTO v_child_min_time; ELSIF v_control_type = 'id' THEN EXECUTE format('SELECT min(%s) FROM %I.%I', v_partition_expression, v_parent_schema, v_child_table) INTO v_child_min_id; END IF; IF v_child_min_time IS NULL AND v_child_min_id IS NULL THEN -- No rows left in this child table. Remove from partition set. -- lockwait timeout for table drop IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('LOCK TABLE ONLY %I.%I IN ACCESS EXCLUSIVE MODE NOWAIT', v_parent_schema, v_child_table); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on child table for removal from partition set'; partitions_undone = -1; RETURN; END IF; END IF; -- END p_lock_wait IF v_lock_obtained := FALSE; -- reset for reuse later IF v_partition_type = 'native' THEN EXECUTE format('ALTER TABLE %I.%I DETACH PARTITION %I.%I' , v_parent_schema , v_parent_tablename , v_parent_schema , v_child_table); ELSE EXECUTE format('ALTER TABLE %I.%I NO INHERIT %I.%I' , v_parent_schema , v_child_table , v_parent_schema , v_parent_tablename); END IF; IF p_keep_table = false THEN v_sql := 'DROP TABLE %I.%I'; IF p_drop_cascade THEN v_sql := v_sql || ' CASCADE'; END IF; EXECUTE format(v_sql, v_parent_schema, v_child_table); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table DROPPED. Moved %s rows to target table', v_child_loop_total)); END IF; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Child table DETACHED/UNINHERITED from parent, not DROPPED. Moved %s rows to target table', v_child_loop_total)); END IF; END IF; IF v_partition_type = 'time-custom' THEN DELETE FROM @extschema@.custom_time_partitions WHERE parent_table = p_parent_table AND child_table = v_parent_schema||'.'||v_child_table; END IF; v_undo_count := v_undo_count + 1; EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached CONTINUE outer_child_loop; -- skip data moving steps below END IF; v_inner_loop_count := 1; v_child_loop_total := 0; <> LOOP IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN -- do some locking with timeout, if required IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM %I.%I WHERE %I <= %L FOR UPDATE NOWAIT' , v_parent_schema , v_child_table , v_control , v_child_min_time + (v_batch_interval_time * v_inner_loop_count)); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; partitions_undone = -1; RETURN; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval EXECUTE format('WITH move_data AS ( DELETE FROM %I.%I WHERE %s <= %L RETURNING %s ) INSERT INTO %I.%I (%5$s) SELECT %5$s FROM move_data' , v_parent_schema , v_child_table , v_partition_expression , v_child_min_time + (v_batch_interval_time * v_inner_loop_count) , v_column_list , v_target_schema , v_target_tablename); GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Moved %s rows to target table.', v_child_loop_total)); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; -- Check again if table is empty and go to outer loop again to drop it if so EXECUTE format('SELECT min(%s) FROM %I.%I', v_partition_expression, v_parent_schema, v_child_table) INTO v_child_min_time; CONTINUE outer_child_loop WHEN v_child_min_time IS NULL; ELSIF v_control_type = 'id' THEN IF p_lock_wait > 0 THEN v_lock_iter := 0; WHILE v_lock_iter <= 5 LOOP v_lock_iter := v_lock_iter + 1; BEGIN EXECUTE format('SELECT * FROM %I.%I WHERE %I <= %L FOR UPDATE NOWAIT' , v_parent_schema , v_child_table , v_control , v_child_min_id + (v_batch_interval_id * v_inner_loop_count)); v_lock_obtained := TRUE; EXCEPTION WHEN lock_not_available THEN PERFORM pg_sleep( p_lock_wait / 5.0 ); CONTINUE; END; EXIT WHEN v_lock_obtained; END LOOP; IF NOT v_lock_obtained THEN RAISE NOTICE 'Unable to obtain lock on batch of rows to move'; partitions_undone = -1; RETURN; END IF; END IF; -- Get everything from the current child minimum up to the multiples of the given interval EXECUTE format('WITH move_data AS ( DELETE FROM %I.%I WHERE %s <= %L RETURNING %s) INSERT INTO %I.%I (%5$s) SELECT %5$s FROM move_data' , v_parent_schema , v_child_table , v_partition_expression , v_child_min_id + (v_batch_interval_id * v_inner_loop_count) , v_column_list , v_target_schema , v_target_tablename); GET DIAGNOSTICS v_rowcount = ROW_COUNT; v_total := v_total + v_rowcount; v_child_loop_total := v_child_loop_total + v_rowcount; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Moved %s rows to target table.', v_child_loop_total)); END IF; EXIT inner_child_loop WHEN v_rowcount = 0; -- exit before loop incr if table is empty v_inner_loop_count := v_inner_loop_count + 1; v_batch_loop_count := v_batch_loop_count + 1; -- Check again if table is empty and go to outer loop again to drop it if so EXECUTE format('SELECT min(%s) FROM %I.%I', v_partition_expression, v_parent_schema, v_child_table) INTO v_child_min_id; CONTINUE outer_child_loop WHEN v_child_min_id IS NULL; END IF; -- end v_control_type check EXIT outer_child_loop WHEN v_batch_loop_count >= p_batch_count; -- Exit outer FOR loop if p_batch_count is reached END LOOP inner_child_loop; END LOOP outer_child_loop; SELECT partition_tablename INTO v_child_table FROM @extschema@.show_partitions(p_parent_table, 'ASC', TRUE) LIMIT 1; IF v_child_table IS NULL THEN DELETE FROM @extschema@.part_config WHERE parent_table = p_parent_table; -- Check if any other config entries still have this template table and don't remove if so -- Allows other sibling/parent tables to still keep using in case entire partition set isn't being undone SELECT count(*) INTO v_template_siblings FROM @extschema@.part_config WHERE template_table = v_template_table; 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(v_template_table, '.', 1)::name AND c.relname = split_part(v_template_table, '.', 2)::name; IF v_template_siblings = 0 AND v_template_tablename IS NOT NULL THEN EXECUTE format('DROP TABLE IF EXISTS %I.%I', v_template_schema, v_template_tablename); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Removing config from pg_partman'); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; RAISE NOTICE 'Moved % row(s) to the target table. Removed % partitions.', v_total, v_undo_count; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Final stats'); PERFORM update_step(v_step_id, 'OK', format('Moved %s row(s) to the target table. Removed %s partitions.', v_total, v_undo_count)); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); partitions_undone := v_undo_count; rows_undone := v_total; 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 UNDO PARTITIONING: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE OR REPLACE FUNCTION @extschema@.apply_constraints(p_parent_table text, p_child_table text DEFAULT NULL, p_analyze boolean DEFAULT FALSE, p_job_id bigint DEFAULT NULL) RETURNS void LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_child_exists text; v_child_tablename text; v_col text; v_constraint_cols text[]; v_constraint_col_type text; v_constraint_name text; v_constraint_valid boolean; v_constraint_values record; v_control text; v_control_type text; v_datetime_string text; v_epoch text; v_existing_constraint_name text; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_last_partition text; v_last_partition_id bigint; v_last_partition_timestamp timestamptz; v_max_id bigint; v_max_timestamp timestamptz; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_optimize_constraint int; v_parent_schema text; v_parent_table text; v_parent_tablename text; v_partition_interval text; v_partition_suffix text; v_premake int; v_sql text; v_step_id bigint; v_suffix_position int; v_type text; BEGIN /* * Apply constraints managed by partman extension */ SELECT parent_table , partition_type , control , premake , partition_interval , optimize_constraint , epoch , datetime_string , constraint_cols , jobmon , constraint_valid INTO v_parent_table , v_type , v_control , v_premake , v_partition_interval , v_optimize_constraint , v_epoch , v_datetime_string , v_constraint_cols , v_jobmon , v_constraint_valid FROM @extschema@.part_config WHERE parent_table = p_parent_table AND constraint_cols IS NOT NULL; IF v_constraint_cols IS NULL THEN RAISE DEBUG 'apply_constraints: Given parent table (%) not set up for constraint management (constraint_cols is NULL)', p_parent_table; -- Returns silently to allow this function to be simply called by maintenance processes without having to check if config options are set. RETURN; END IF; SELECT schemaname, tablename INTO v_parent_schema, v_parent_tablename FROM pg_catalog.pg_tables WHERE schemaname = split_part(v_parent_table, '.', 1)::name AND tablename = split_part(v_parent_table, '.', 2)::name; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); IF v_jobmon_schema IS NOT NULL THEN IF p_job_id IS NULL THEN v_job_id := add_job(format('PARTMAN CREATE CONSTRAINT: %s', v_parent_table)); ELSE v_job_id = p_job_id; END IF; END IF; -- If p_child_table is null, figure out the partition that is the one right before the optimize_constraint value backwards. IF p_child_table IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying additional constraints: Automatically determining most recent child on which to apply constraints'); END IF; SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(v_parent_table, 'DESC') LIMIT 1; IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN SELECT child_start_time INTO v_last_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_partition_interval, v_parent_table); v_partition_suffix := to_char(v_last_partition_timestamp - (v_partition_interval::interval * (v_optimize_constraint + v_premake + 1) ), v_datetime_string); ELSIF v_control_type = 'id' THEN SELECT child_start_id INTO v_last_partition_id FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_partition_interval, v_parent_table); v_partition_suffix := (v_last_partition_id - (v_partition_interval::bigint * (v_optimize_constraint + v_premake + 1) ))::text; END IF; RAISE DEBUG 'apply_constraint: v_parent_tablename: %, v_last_partition: %, v_last_partition_timestamp: %, v_partition_suffix: %' , v_parent_tablename, v_last_partition, v_last_partition_timestamp, v_partition_suffix; v_child_tablename := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('Target child table: %s.%s', v_parent_schema, v_child_tablename)); END IF; ELSE v_child_tablename = split_part(p_child_table, '.', 2); END IF; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Applying additional constraints: Checking if target child table exists'); END IF; SELECT tablename FROM pg_catalog.pg_tables INTO v_child_exists WHERE schemaname = v_parent_schema::name AND tablename = v_child_tablename::name; IF v_child_exists IS NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Target child table (%s) does not exist. Skipping constraint creation.', v_child_tablename)); IF p_job_id IS NULL THEN PERFORM close_job(v_job_id); END IF; END IF; RAISE DEBUG 'Target child table (%) does not exist. Skipping constraint creation.', v_child_tablename; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN; ELSE IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; FOREACH v_col IN ARRAY v_constraint_cols LOOP SELECT con.conname INTO v_existing_constraint_name FROM pg_catalog.pg_constraint con JOIN pg_class c ON c.oid = con.conrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN pg_catalog.pg_attribute a ON con.conrelid = a.attrelid WHERE c.relname = v_child_tablename::name AND n.nspname = v_parent_schema::name AND con.conname LIKE 'partmanconstr_%' AND con.contype = 'c' AND a.attname = v_col::name AND ARRAY[a.attnum] OPERATOR(pg_catalog.<@) con.conkey AND a.attisdropped = false; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Applying additional constraints: Applying new constraint on column: %s', v_col)); END IF; IF v_existing_constraint_name IS NOT NULL THEN IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Partman managed constraint already exists on this table (%s) and column (%s). Skipping creation.', v_child_tablename, v_col)); END IF; RAISE DEBUG 'Partman managed constraint already exists on this table (%) and column (%). Skipping creation.', v_child_tablename, v_col ; CONTINUE; END IF; -- Ensure column name gets put on end of constraint name to help avoid naming conflicts v_constraint_name := @extschema@.check_name_length('partmanconstr_'||v_child_tablename, p_suffix := '_'||v_col); EXECUTE format('SELECT min(%I)::text AS min, max(%I)::text AS max FROM %I.%I', v_col, v_col, v_parent_schema, v_child_tablename) INTO v_constraint_values; IF v_constraint_values IS NOT NULL THEN v_sql := format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I <= %L)' , v_parent_schema , v_child_tablename , v_constraint_name , v_col , v_constraint_values.min , v_col , v_constraint_values.max); IF v_constraint_valid = false THEN v_sql := format('%s NOT VALID', v_sql); END IF; RAISE DEBUG 'Constraint creation query: %', v_sql; EXECUTE v_sql; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', format('New constraint created: %s', v_sql)); END IF; ELSE RAISE DEBUG 'Given column (%) contains all NULLs. No constraint created', v_col; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'NOTICE', format('Given column (%s) contains all NULLs. No constraint created', v_col)); END IF; END IF; END LOOP; IF p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Applying additional constraints: Running analyze on partition set: %s', v_parent_table)); END IF; RAISE DEBUG 'Running analyze on partition set: %', v_parent_table; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); 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 CONSTRAINT: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE 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 , p_date_trunc_interval text DEFAULT NULL) 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' AND current_setting('server_version_num')::int >= 110000 THEN RAISE EXCEPTION 'The pg_partman upsert feature is not supported with native partitioning in PG11+. Use the built-in support for INSERT ON CONFLICT with native partitioning instead.'; 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 , sub_date_trunc_interval) 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 , p_date_trunc_interval); 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 intereval of sub-parent is less than 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) OR (p_date_trunc_interval = 'week') 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 time periods'; 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 , p_date_trunc_interval := %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 , p_date_trunc_interval); 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@.create_partition_time(p_parent_table text, p_partition_times timestamptz[], p_analyze boolean DEFAULT true, p_start_partition text DEFAULT NULL) RETURNS boolean LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_control_type text; v_datetime_string text; v_epoch text; v_exists smallint; v_grantees text[]; v_hasoids boolean; v_inherit_privileges boolean; v_inherit_fk boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_grant record; v_parent_schema text; v_parent_tablename text; v_part_col text; v_partition_created boolean := false; v_partition_name text; v_partition_suffix text; v_parent_tablespace text; v_partition_expression text; v_partition_interval interval; v_partition_timestamp_end timestamptz; v_partition_timestamp_start timestamptz; v_publications text[]; v_quarter text; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_step_overflow_id bigint; v_sub_control text; v_sub_parent text; v_sub_partition_type text; v_sub_timestamp_max timestamptz; v_sub_timestamp_min timestamptz; v_template_table text; v_trunc_value text; v_time timestamptz; v_partition_type text; v_unlogged char; v_year text; BEGIN /* * Function to create a child table in a time-based partition set */ SELECT partition_type , control , partition_interval , epoch , inherit_fk , jobmon , datetime_string , template_table , publications , inherit_privileges INTO v_partition_type , v_control , v_partition_interval , v_epoch , v_inherit_fk , v_jobmon , v_datetime_string , v_template_table , v_publications , v_inherit_privileges FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT n.nspname, c.relname, t.spcname INTO v_parent_schema, v_parent_tablename, v_parent_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 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; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'time' THEN IF (v_control_type = 'id' AND v_epoch = 'none') OR v_control_type <> 'id' THEN RAISE EXCEPTION 'Cannot run on partition set without time based control column or epoch flag set with an id column. Found control: %, epoch: %', v_control_type, v_epoch; END IF; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::timestamptz, sub_max::timestamptz INTO v_sub_timestamp_min, v_sub_timestamp_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'time'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; v_partition_expression := CASE WHEN v_epoch = 'seconds' THEN format('to_timestamp(%I)', v_control) WHEN v_epoch = 'milliseconds' THEN format('to_timestamp((%I/1000)::float)', v_control) ELSE format('%I', v_control) END; RAISE DEBUG 'create_partition_time: v_partition_expression: %', v_partition_expression; FOREACH v_time IN ARRAY p_partition_times LOOP v_partition_timestamp_start := v_time; BEGIN v_partition_timestamp_end := v_time + v_partition_interval; 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_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_time||' skipped'); CONTINUE; END; -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_timestamp_min IS NOT NULL THEN IF v_time < v_sub_timestamp_min OR v_time >= v_sub_timestamp_max THEN RAISE DEBUG 'create_partition_time: p_parent_table: %, v_time: %, v_sub_timestamp_min: %, v_sub_timestamp_max: %' , p_parent_table, v_time, v_sub_timestamp_min, v_sub_timestamp_max; CONTINUE; END IF; END IF; -- This suffix generation code is in partition_data_time() as well v_partition_suffix := to_char(v_time, v_datetime_string); v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); -- Check if child exists. SELECT count(*) INTO v_exists 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_partition_name::name; IF v_exists > 0 THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Creating new partition %s.%s with interval from %s to %s' , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end-'1sec'::interval)); END IF; v_sql := 'CREATE'; -- As of PG12, the unlogged/logged status of a native parent table cannot be changed via an ALTER TABLE in order to affect its children. -- As of v4.2x, the unlogged state will be managed via the template table SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c 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; IF v_unlogged = 'u' and v_partition_type != 'native' THEN v_sql := v_sql || ' UNLOGGED'; END IF; -- Close parentheses on LIKE are below due to differing requirements of native subpartitioning -- Same INCLUDING list is used in create_parent() v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING STORAGE INCLUDING COMMENTS ' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); IF current_setting('server_version_num')::int >= 120000 THEN v_sql := v_sql || ' INCLUDING GENERATED '; END IF; SELECT sub_partition_type, sub_control INTO v_sub_partition_type, v_sub_control FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; IF v_sub_partition_type = 'native' THEN -- INCLUDING INDEXES isn't necessary for native partitioning. It isn't supported in v10 and -- for v11+ index inheritance is automatically handled when the partition is attached v_sql := v_sql || format(') PARTITION BY RANGE (%I) ', v_sub_control); ELSE v_sql := v_sql || format(' INCLUDING INDEXES) ', v_sub_control); END IF; IF current_setting('server_version_num')::int < 120000 THEN -- column removed from pgclass in pg12 SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c 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; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; END IF; RAISE DEBUG 'create_partition_time v_sql: %', v_sql; EXECUTE v_sql; IF v_partition_type = 'native' THEN IF current_setting('server_version_num')::int >= 120000 THEN -- PG12 fixed tablespace marking on the parent of a native partition set -- Versions older than 12 handle tablespace setting via inherit_template_properties() call below IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; END IF; IF v_template_table IS NOT NULL THEN PERFORM @extschema@.inherit_template_properties(p_parent_table, v_parent_schema, v_partition_name); END IF; IF v_epoch = 'none' THEN -- Attach with normal, time-based values for native constraint EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , v_partition_timestamp_start , v_partition_timestamp_end); ELSE -- Must attach with integer based values for native constraint and epoch IF v_epoch = 'seconds' THEN EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , EXTRACT('epoch' FROM v_partition_timestamp_start)::bigint , EXTRACT('epoch' FROM v_partition_timestamp_end)::bigint); ELSIF v_epoch = 'milliseconds' THEN EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , EXTRACT('epoch' FROM v_partition_timestamp_start)::bigint * 1000 , EXTRACT('epoch' FROM v_partition_timestamp_end)::bigint * 1000); END IF; -- Create secondary, time-based constraint since native's constraint is already integer based EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%s >= %L AND %4$s < %6$L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_partition_expression , v_partition_timestamp_start , v_partition_timestamp_end); END IF; ELSE -- non-native IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; -- Non-native always gets time-based constraint EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%s >= %L AND %4$s < %6$L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_partition_expression , v_partition_timestamp_start , v_partition_timestamp_end); IF v_epoch = 'seconds' THEN -- Non-native needs secondary, integer based constraint for epoch EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_int_check' , v_control , EXTRACT('epoch' from v_partition_timestamp_start)::bigint , v_control , EXTRACT('epoch' from v_partition_timestamp_end)::bigint ); ELSIF v_epoch = 'milliseconds' THEN EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %L AND %I < %L)' , v_parent_schema , v_partition_name , v_partition_name||'_partition_int_check' , v_control , EXTRACT('epoch' from v_partition_timestamp_start)::bigint * 1000 , v_control , EXTRACT('epoch' from v_partition_timestamp_end)::bigint * 1000); END IF; EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); -- If custom time, set extra config options. IF v_partition_type = 'time-custom' THEN INSERT INTO @extschema@.custom_time_partitions (parent_table, child_table, partition_range) VALUES ( p_parent_table, v_parent_schema||'.'||v_partition_name, tstzrange(v_partition_timestamp_start, v_partition_timestamp_end, '[)') ); END IF; -- Indexes cannot be created on the parent, so clustering cannot be used for native yet. PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); -- Foreign keys to other tables not supported in native IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; END IF; -- end native check -- NOTE: Privileges not automatically inherited for native. Only do so if config flag is set IF v_partition_type != 'native' OR (v_partition_type = 'native' AND v_inherit_privileges = TRUE) THEN PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition -- 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 FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_infinite_time_partitions , sub_automatic_maintenance , sub_jobmon , sub_trigger_exception_handling , sub_upsert , sub_trigger_return_null , sub_template_table , sub_inherit_privileges , sub_constraint_valid , sub_subscription_refresh , sub_date_trunc_interval FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Subpartitioning %s.%s', v_parent_schema, v_partition_name)); END IF; 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_inherit_fk := %L , p_epoch := %L , p_template_table := %L , p_jobmon := %L , p_start_partition := %L , p_date_trunc_interval := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_automatic_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_template_table , v_row.sub_jobmon , p_start_partition , v_row.sub_date_trunc_interval); RAISE DEBUG 'create_partition_time (create_parent loop): %', v_sql; EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions , trigger_exception_handling = v_row.sub_trigger_exception_handling , upsert = v_row.sub_upsert , inherit_privileges = v_row.sub_inherit_privileges , trigger_return_null = v_row.sub_trigger_return_null , constraint_valid = v_row.sub_constraint_valid , subscription_refresh = v_row.sub_subscription_refresh WHERE parent_table = v_parent_schema||'.'||v_partition_name; END LOOP; -- end sub partitioning LOOP -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table, p_job_id := v_job_id); IF v_publications IS NOT NULL THEN -- NOTE: Publications currently not supported on parent table, but are supported on the table partitions if individually assigned. PERFORM @extschema@.apply_publications(p_parent_table, v_parent_schema, v_partition_name); END IF; v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s. Attempted intervals: %s', p_parent_table, p_partition_times)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; 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_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE OR REPLACE FUNCTION @extschema@.create_partition_id(p_parent_table text, p_partition_ids bigint[], p_analyze boolean DEFAULT true, p_start_partition text DEFAULT NULL) RETURNS boolean LANGUAGE plpgsql AS $$ DECLARE ex_context text; ex_detail text; ex_hint text; ex_message text; v_all text[] := ARRAY['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER']; v_analyze boolean := FALSE; v_control text; v_control_type text; v_exists text; v_grantees text[]; v_hasoids boolean; v_id bigint; v_inherit_fk boolean; v_inherit_privileges boolean; v_job_id bigint; v_jobmon boolean; v_jobmon_schema text; v_new_search_path text := '@extschema@,pg_temp'; v_old_search_path text; v_parent_grant record; v_parent_schema text; v_parent_tablename text; v_parent_tablespace text; v_partition_interval bigint; v_partition_created boolean := false; v_partition_name text; v_partition_type text; v_publications text[]; v_revoke text; v_row record; v_sql text; v_step_id bigint; v_sub_control text; v_sub_partition_type text; v_sub_id_max bigint; v_sub_id_min bigint; v_template_table text; v_unlogged char; BEGIN /* * Function to create id partitions */ SELECT control , partition_type , partition_interval , inherit_fk , jobmon , template_table , publications , inherit_privileges INTO v_control , v_partition_type , v_partition_interval , v_inherit_fk , v_jobmon , v_template_table , v_publications , v_inherit_privileges FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: no config found for %', p_parent_table; END IF; SELECT n.nspname, c.relname, t.spcname INTO v_parent_schema, v_parent_tablename, v_parent_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 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; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); IF v_control_type <> 'id' THEN RAISE EXCEPTION 'ERROR: Given parent table is not set up for id/serial partitioning'; END IF; SELECT current_setting('search_path') INTO v_old_search_path; IF v_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'); -- Determine if this table is a child of a subpartition parent. If so, get limits of what child tables can be created based on parent suffix SELECT sub_min::bigint, sub_max::bigint INTO v_sub_id_min, v_sub_id_max FROM @extschema@.check_subpartition_limits(p_parent_table, 'id'); IF v_jobmon_schema IS NOT NULL THEN v_job_id := add_job(format('PARTMAN CREATE TABLE: %s', p_parent_table)); END IF; FOREACH v_id IN ARRAY p_partition_ids LOOP -- Do not create the child table if it's outside the bounds of the top parent. IF v_sub_id_min IS NOT NULL THEN IF v_id < v_sub_id_min OR v_id >= v_sub_id_max THEN CONTINUE; END IF; END IF; v_partition_name := @extschema@.check_name_length(v_parent_tablename, v_id::text, TRUE); -- If child table already exists, skip creation -- Have to check pg_class because if subpartitioned, table will not be in pg_tables SELECT c.relname INTO v_exists 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_partition_name::name; IF v_exists IS NOT NULL THEN CONTINUE; END IF; -- Ensure analyze is run if a new partition is created. Otherwise if one isn't, will be false and analyze will be skipped v_analyze := TRUE; IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Creating new partition '||v_partition_name||' with interval from '||v_id||' to '||(v_id + v_partition_interval)-1); END IF; v_sql := 'CREATE'; -- As of PG12, the unlogged/logged status of a native parent table cannot be changed via an ALTER TABLE in order to affect its children. -- As of v4.2x, the unlogged state will be managed via the template table SELECT relpersistence INTO v_unlogged FROM pg_catalog.pg_class c 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; IF v_unlogged = 'u' and v_partition_type != 'native' THEN v_sql := v_sql || ' UNLOGGED'; END IF; -- Close parentheses on LIKE are below due to differing requirements of native subpartitioning -- Same INCLUDING list is used in create_parent() v_sql := v_sql || format(' TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING STORAGE INCLUDING COMMENTS ' , v_parent_schema , v_partition_name , v_parent_schema , v_parent_tablename); IF current_setting('server_version_num')::int >= 120000 THEN v_sql := v_sql || ' INCLUDING GENERATED '; END IF; SELECT sub_partition_type, sub_control INTO v_sub_partition_type, v_sub_control FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table; IF v_sub_partition_type = 'native' THEN -- INCLUDING INDEXES isn't necessary for native partitioning. It isn't supported in v10 and -- for v11+ index inheritance is automatically handled when the partition is attached v_sql := v_sql || format(') PARTITION BY RANGE (%I) ', v_sub_control); ELSE v_sql := v_sql || format(' INCLUDING INDEXES) ', v_sub_control); END IF; IF current_setting('server_version_num')::int < 120000 THEN -- column removed from pgclass in pg12 SELECT relhasoids INTO v_hasoids FROM pg_catalog.pg_class c 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; IF v_hasoids IS TRUE THEN v_sql := v_sql || ' WITH (OIDS)'; END IF; END IF; RAISE DEBUG 'create_partition_id v_sql: %', v_sql; EXECUTE v_sql; IF v_partition_type = 'native' THEN IF current_setting('server_version_num')::int >= 120000 THEN -- PG12 fixed tablespace marking on the parent of a native partition set -- Versions older than 12 handle tablespace setting via inherit_template_properties() call below IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; END IF; IF v_template_table IS NOT NULL THEN PERFORM @extschema@.inherit_template_properties(p_parent_table, v_parent_schema, v_partition_name); END IF; EXECUTE format('ALTER TABLE %I.%I ATTACH PARTITION %I.%I FOR VALUES FROM (%L) TO (%L)' , v_parent_schema , v_parent_tablename , v_parent_schema , v_partition_name , v_id , v_id + v_partition_interval); ELSE -- non-native IF v_parent_tablespace IS NOT NULL THEN EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_parent_schema, v_partition_name, v_parent_tablespace); END IF; EXECUTE format('ALTER TABLE %I.%I ADD CONSTRAINT %I CHECK (%I >= %s AND %I < %s )' , v_parent_schema , v_partition_name , v_partition_name||'_partition_check' , v_control , v_id , v_control , v_id + v_partition_interval); EXECUTE format('ALTER TABLE %I.%I INHERIT %I.%I', v_parent_schema, v_partition_name, v_parent_schema, v_parent_tablename); -- Indexes cannot be created on the parent, so clustering cannot be used for native yet. PERFORM @extschema@.apply_cluster(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name); -- Foreign keys to other tables not supported on native parent tables IF v_inherit_fk THEN PERFORM @extschema@.apply_foreign_keys(p_parent_table, v_parent_schema||'.'||v_partition_name, v_job_id); END IF; END IF; -- NOTE: Privileges not automatically inherited for native. Only do so if config flag is set IF v_partition_type != 'native' OR (v_partition_type = 'native' AND v_inherit_privileges = TRUE) THEN PERFORM @extschema@.apply_privileges(v_parent_schema, v_parent_tablename, v_parent_schema, v_partition_name, v_job_id); END IF; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; -- Will only loop once and only if sub_partitioning is actually configured -- This seemed easier than assigning a bunch of variables then doing an IF condition -- 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 FOR v_row IN SELECT sub_parent , sub_partition_type , sub_control , sub_partition_interval , sub_constraint_cols , sub_premake , sub_optimize_trigger , sub_optimize_constraint , sub_epoch , sub_inherit_fk , sub_retention , sub_retention_schema , sub_retention_keep_table , sub_retention_keep_index , sub_infinite_time_partitions , sub_automatic_maintenance , sub_jobmon , sub_trigger_exception_handling , sub_upsert , sub_trigger_return_null , sub_template_table , sub_inherit_privileges , sub_constraint_valid , sub_subscription_refresh , sub_date_trunc_interval FROM @extschema@.part_config_sub WHERE sub_parent = p_parent_table LOOP IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, 'Subpartitioning '||v_partition_name); END IF; 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_inherit_fk := %L , p_epoch := %L , p_template_table := %L , p_jobmon := %L , p_start_partition := %L , p_date_trunc_interval := %L )' , v_parent_schema||'.'||v_partition_name , v_row.sub_control , v_row.sub_partition_type , v_row.sub_partition_interval , v_row.sub_constraint_cols , v_row.sub_premake , v_row.sub_automatic_maintenance , v_row.sub_inherit_fk , v_row.sub_epoch , v_row.sub_template_table , v_row.sub_jobmon , p_start_partition , v_row.sub_date_trunc_interval); RAISE DEBUG 'create_partition_id (create_parent loop): %', v_sql; EXECUTE v_sql; UPDATE @extschema@.part_config SET retention_schema = v_row.sub_retention_schema , retention_keep_table = v_row.sub_retention_keep_table , retention_keep_index = v_row.sub_retention_keep_index , optimize_trigger = v_row.sub_optimize_trigger , optimize_constraint = v_row.sub_optimize_constraint , infinite_time_partitions = v_row.sub_infinite_time_partitions , trigger_exception_handling = v_row.sub_trigger_exception_handling , upsert = v_row.sub_upsert , inherit_privileges = v_row.sub_inherit_privileges , trigger_return_null = v_row.sub_trigger_return_null , constraint_valid = v_row.sub_constraint_valid , subscription_refresh = v_row.sub_subscription_refresh WHERE parent_table = v_parent_schema||'.'||v_partition_name; IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END LOOP; -- end sub partitioning LOOP -- Manage additonal constraints if set PERFORM @extschema@.apply_constraints(p_parent_table, p_job_id := v_job_id); IF v_publications IS NOT NULL THEN -- NOTE: Publications currently not supported on parent table, but are supported on the table partitions if individually assigned. PERFORM @extschema@.apply_publications(p_parent_table, v_parent_schema, v_partition_name); END IF; v_partition_created := true; END LOOP; -- v_analyze is a local check if a new table is made. -- p_analyze is a parameter to say whether to run the analyze at all. Used by create_parent() to avoid long exclusive lock or run_maintenence() to avoid long creation runs. IF v_analyze AND p_analyze THEN IF v_jobmon_schema IS NOT NULL THEN v_step_id := add_step(v_job_id, format('Analyzing partition set: %s', p_parent_table)); END IF; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); IF v_jobmon_schema IS NOT NULL THEN PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; END IF; IF v_jobmon_schema IS NOT NULL THEN IF v_partition_created = false THEN v_step_id := add_step(v_job_id, format('No partitions created for partition set: %s', p_parent_table)); PERFORM update_step(v_step_id, 'OK', 'Done'); END IF; PERFORM close_job(v_job_id); END IF; EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false'); RETURN v_partition_created; 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 TABLE: %s'')', v_jobmon_schema, p_parent_table) INTO v_job_id; EXECUTE format('SELECT %I.add_step(%s, ''EXCEPTION before job logging started'')', 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 $$; CREATE FUNCTION @extschema@.check_subpart_sameconfig(p_parent_table text) RETURNS TABLE (sub_partition_type text , sub_control text , sub_partition_interval text , sub_constraint_cols text[] , sub_premake int , sub_optimize_trigger int , sub_optimize_constraint int , sub_epoch text , sub_inherit_fk boolean , sub_retention text , sub_retention_schema text , sub_retention_keep_table boolean , sub_retention_keep_index boolean , sub_infinite_time_partitions boolean , sub_automatic_maintenance text , sub_jobmon boolean , sub_trigger_exception_handling boolean , sub_upsert text , sub_trigger_return_null boolean , sub_template_table text , sub_inherit_privileges boolean , sub_constraint_valid boolean , sub_subscription_refresh text , sub_date_trunc_interval text) LANGUAGE sql STABLE SET search_path = @extschema@,pg_temp AS $$ /* * Check for consistent data in part_config_sub table. Was unable to get this working properly as either a constraint or trigger. * Would either delay raising an error until the next write (which I cannot predict) or disallow future edits to update a sub-partition set's configuration. * This is called by run_maintainance() and at least provides a consistent way to check that I know will run. * If anyone can get a working constraint/trigger, please help! */ WITH parent_info AS ( SELECT c1.oid FROM pg_catalog.pg_class c1 JOIN pg_catalog.pg_namespace n1 ON c1.relnamespace = n1.oid WHERE n1.nspname = split_part(p_parent_table, '.', 1)::name AND c1.relname = split_part(p_parent_table, '.', 2)::name ) , child_tables AS ( SELECT n.nspname||'.'||c.relname AS tablename FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid JOIN parent_info pi ON h.inhparent = pi.oid ) -- Column order here must match the RETURNS TABLE definition -- 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 a.sub_partition_type , a.sub_control , a.sub_partition_interval , a.sub_constraint_cols , a.sub_premake , a.sub_optimize_trigger , a.sub_optimize_constraint , a.sub_epoch , a.sub_inherit_fk , a.sub_retention , a.sub_retention_schema , a.sub_retention_keep_table , a.sub_retention_keep_index , a.sub_infinite_time_partitions , a.sub_automatic_maintenance , a.sub_jobmon , a.sub_trigger_exception_handling , a.sub_upsert , a.sub_trigger_return_null , a.sub_template_table , a.sub_inherit_privileges , a.sub_constraint_valid , a.sub_subscription_refresh , a.sub_date_trunc_interval FROM @extschema@.part_config_sub a JOIN child_tables b on a.sub_parent = b.tablename; $$; -- Restore dropped object privileges DO $$ DECLARE v_row record; BEGIN FOR v_row IN SELECT statement FROM partman_preserve_privs_temp LOOP IF v_row.statement IS NOT NULL THEN EXECUTE v_row.statement; END IF; END LOOP; END $$; TRUNCATE partman_preserve_privs_temp; -- ######################## START POSTGRESQL 11 ONLY SECTION ############################## DO $pg11only$ DECLARE v_undo_partition_proc_sql text; v_reapply_constraints_proc_sql text; BEGIN IF current_setting('server_version_num')::int >= 110000 THEN -- ######################## START POSTGRESQL 11 ONLY SECTION ############################## INSERT INTO partman_preserve_privs_temp SELECT 'GRANT EXECUTE ON PROCEDURE @extschema@.undo_partition_proc(text, text, int, int, text, boolean, int, int, boolean, text[], boolean) TO '||array_to_string(array_agg(grantee::text), ',')||';' FROM information_schema.routine_privileges WHERE routine_schema = '@extschema@' AND routine_name = 'undo_partition_proc'; DROP PROCEDURE @extschema@.undo_partition_proc(text, text, int, int, text, boolean, int, int, boolean, text[]); v_undo_partition_proc_sql := $undo_partition_proc$ CREATE PROCEDURE @extschema@.undo_partition_proc( p_parent_table text , p_interval text DEFAULT NULL , p_batch int DEFAULT NULL , p_wait int DEFAULT 1 , p_target_table text DEFAULT NULL , p_keep_table boolean DEFAULT true , p_lock_wait int DEFAULT 0 , p_lock_wait_tries int DEFAULT 10 , p_quiet boolean DEFAULT false , p_ignored_columns text[] DEFAULT NULL , p_drop_cascade boolean DEFAULT false) LANGUAGE plpgsql AS $$ DECLARE v_adv_lock boolean; v_batch_count int := 0; v_is_autovac_off boolean := false; v_lockwait_count int := 0; v_parent_schema text; v_parent_tablename text; v_partition_type text; v_partitions_undone int; v_partitions_undone_total int := 0; v_row record; v_rows_undone bigint; v_target_schema text; v_target_tablename text; v_sql text; v_total bigint := 0; BEGIN v_adv_lock := pg_try_advisory_xact_lock(hashtext('pg_partman undo_partition_proc'), hashtext(p_parent_table)); IF v_adv_lock = 'false' THEN RAISE NOTICE 'Partman undo_partition_proc already running for given parent table: %.', p_parent_table; RETURN; END IF; SELECT partition_type INTO v_partition_type FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN RAISE EXCEPTION 'ERROR: No entry in part_config found for given table: %', p_parent_table; END IF; IF v_partition_type = 'native' AND p_target_table IS NULL THEN RAISE EXCEPTION 'Natively partitioned table sets require setting the p_target_table parameter to undo partitioning.'; END IF; SELECT n.nspname, c.relname INTO v_parent_schema, v_parent_tablename 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_tablename IS NULL THEN RAISE EXCEPTION 'Unable to find given parent table in system catalogs. Ensure it is schema qualified: %', p_parent_table; END IF; IF p_target_table IS NOT NULL THEN SELECT n.nspname, c.relname INTO v_target_schema, v_target_tablename FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = split_part(p_target_table, '.', 1)::name AND c.relname = split_part(p_target_table, '.', 2)::name; IF v_target_tablename IS NULL THEN RAISE EXCEPTION 'Unable to find given target table in system catalogs. Ensure it is schema qualified: %', p_target_table; END IF; END IF; /* -- Currently no way to catch exception and reset autovac settings back to normal. Until I can do that, leaving this feature out for now -- Leaving the functions to turn off/reset in to let people do that manually if desired IF p_autovacuum_on = false THEN -- Add this parameter back to definition when this is working -- Turn off autovac for parent, source table if set, and all child tables v_is_autovac_off := @extschema@.autovacuum_off(v_parent_schema, v_parent_tablename, v_source_schema, v_source_tablename); COMMIT; END IF; */ v_sql := format('SELECT partitions_undone, rows_undone FROM %I.undo_partition (%L, p_keep_table := %L, p_lock_wait := %L' , '@extschema@' , p_parent_table , p_keep_table , p_lock_wait); IF p_interval IS NOT NULL THEN v_sql := v_sql || format(', p_batch_interval := %L', p_interval); END IF; IF p_target_table IS NOT NULL THEN v_sql := v_sql || format(', p_target_table := %L', p_target_table); END IF; IF p_ignored_columns IS NOT NULL THEN v_sql := v_sql || format(', p_ignored_columns := %L', p_ignored_columns); END IF; IF p_drop_cascade IS NOT NULL THEN v_sql := v_sql || format(', p_drop_cascade := %L', p_drop_cascade); END IF; v_sql := v_sql || ')'; RAISE DEBUG 'partition_data sql: %', v_sql; LOOP EXECUTE v_sql INTO v_partitions_undone, v_rows_undone; -- If lock wait timeout, do not increment the counter IF v_rows_undone != -1 THEN v_batch_count := v_batch_count + 1; v_partitions_undone_total := v_partitions_undone_total + v_partitions_undone; v_total := v_total + v_rows_undone; v_lockwait_count := 0; ELSE v_lockwait_count := v_lockwait_count + 1; IF v_lockwait_count > p_lock_wait_tries THEN RAISE EXCEPTION 'Quitting due to inability to get lock on next batch of rows to be moved'; END IF; END IF; IF p_quiet = false THEN IF v_rows_undone > 0 THEN RAISE NOTICE 'Batch: %, Partitions undone this batch: %, Rows undone this batch: %', v_batch_count, v_partitions_undone, v_rows_undone; ELSIF v_rows_undone = -1 THEN RAISE NOTICE 'Unable to obtain row locks for data to be moved. Trying again...'; END IF; END IF; COMMIT; -- If no rows left or given batch argument limit is reached IF v_rows_undone = 0 OR (p_batch > 0 AND v_batch_count >= p_batch) THEN EXIT; END IF; -- undo_partition functions will remove config entry once last child is dropped -- Added here to handle edge-case SELECT partition_type INTO v_partition_type FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF NOT FOUND THEN EXIT; END IF; PERFORM pg_sleep(p_wait); RAISE DEBUG 'v_partitions_undone: %, v_rows_undone: %, v_batch_count: %, v_total: %, v_lockwait_count: %, p_wait: %', v_partitions_undone, p_wait, v_rows_undone, v_batch_count, v_total, v_lockwait_count; END LOOP; /* IF v_is_autovac_off = true THEN -- Reset autovac back to default if it was turned off by this procedure PERFORM @extschema@.autovacuum_reset(v_parent_schema, v_parent_tablename, v_source_schema, v_source_tablename); COMMIT; END IF; */ IF p_quiet = false THEN RAISE NOTICE 'Total partitions undone: %, Total rows moved: %', v_partitions_undone_total, v_total; END IF; RAISE NOTICE 'Ensure to VACUUM ANALYZE the old parent & target table after undo has finished'; END $$; $undo_partition_proc$; EXECUTE v_undo_partition_proc_sql; v_reapply_constraints_proc_sql := $reapply_constraints_proc$ CREATE OR REPLACE PROCEDURE @extschema@.reapply_constraints_proc(p_parent_table text, p_drop_constraints boolean DEFAULT false, p_apply_constraints boolean DEFAULT false, p_wait int DEFAULT 0, p_dryrun boolean DEFAULT false) LANGUAGE plpgsql AS $$ DECLARE v_adv_lock boolean; v_child_stop text; v_control text; v_control_type text; v_datetime_string text; v_epoch text; v_last_partition text; v_last_partition_id bigint; v_last_partition_timestamp timestamptz; v_optimize_constraint int; v_parent_schema text; v_parent_tablename text; v_partition_interval text; v_partition_suffix text; v_premake int; v_row record; v_sql text; BEGIN /* * Procedure for reapplying additional constraints managed by pg_partman on child tables. See docs for additional info on this special constraint management. * Procedure can run in two distinct modes: 1) Drop all constraints 2) Apply all constraints. * If both modes are run in a single call, drop is run before apply. * Typical usage would be to run the drop mode, edit the data, then run apply mode to re-create all constraints on a partition set." */ v_adv_lock := pg_try_advisory_lock(hashtext('pg_partman reapply_constraints')); IF v_adv_lock = false THEN RAISE NOTICE 'Partman reapply_constraints_proc already running or another session has not released its advisory lock.'; RETURN; END IF; SELECT control, premake, optimize_constraint, datetime_string, epoch, partition_interval INTO v_control, v_premake, v_optimize_constraint, v_datetime_string, v_epoch, v_partition_interval FROM @extschema@.part_config WHERE parent_table = p_parent_table; IF v_premake IS NULL THEN RAISE EXCEPTION 'Unable to find given parent in pg_partman config: %. This procedure is only meant to be called on pg_partman managed partition sets.', p_parent_table; END IF; SELECT n.nspname, c.relname INTO v_parent_schema, v_parent_tablename 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_tablename IS NULL THEN RAISE EXCEPTION 'Unable to find given parent table in system catalogs. Ensure it is schema qualified: %', p_parent_table; END IF; SELECT general_type INTO v_control_type FROM @extschema@.check_control_type(v_parent_schema, v_parent_tablename, v_control); -- Determine child table to stop creating constraints on based on optimize_constraint value -- Same code in apply_constraints.sql SELECT partition_tablename INTO v_last_partition FROM @extschema@.show_partitions(p_parent_table, 'DESC') LIMIT 1; IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN SELECT child_start_time INTO v_last_partition_timestamp FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_partition_interval, p_parent_table); v_partition_suffix := to_char(v_last_partition_timestamp - (v_partition_interval::interval * (v_optimize_constraint + v_premake + 1) ), v_datetime_string); ELSIF v_control_type = 'id' THEN SELECT child_start_id INTO v_last_partition_id FROM @extschema@.show_partition_info(v_parent_schema||'.'||v_last_partition, v_partition_interval, p_parent_table); v_partition_suffix := (v_last_partition_id - (v_partition_interval::bigint * (v_optimize_constraint + v_premake + 1) ))::text; END IF; v_child_stop := @extschema@.check_name_length(v_parent_tablename, v_partition_suffix, TRUE); v_sql := format('SELECT partition_schemaname, partition_tablename FROM @extschema@.show_partitions(%L, %L)', p_parent_table, 'ASC'); RAISE DEBUG 'reapply_constraint: v_parent_tablename: % , v_partition_suffix: %, v_child_stop: %, v_sql: %', v_parent_tablename, v_partition_suffix, v_child_stop, v_sql; v_row := NULL; FOR v_row IN EXECUTE v_sql LOOP IF p_drop_constraints THEN IF p_dryrun THEN RAISE NOTICE 'DRYRUN NOTICE: Dropping constraints on child table: %.%', v_row.partition_schemaname, v_row.partition_tablename; ELSE RAISE DEBUG 'reapply_constraint drop: %.%', v_row.partition_schemaname, v_row.partition_tablename; PERFORM @extschema@.drop_constraints(p_parent_table, format('%s.%s', v_row.partition_schemaname, v_row.partition_tablename)::text); END IF; END IF; -- end drop COMMIT; IF p_apply_constraints THEN IF p_dryrun THEN RAISE NOTICE 'DRYRUN NOTICE: Applying constraints on child table: %.%', v_row.partition_schemaname, v_row.partition_tablename; ELSE RAISE DEBUG 'reapply_constraint apply: %.%', v_row.partition_schemaname, v_row.partition_tablename; PERFORM @extschema@.apply_constraints(p_parent_table, format('%s.%s', v_row.partition_schemaname, v_row.partition_tablename)::text); END IF; END IF; -- end apply IF v_row.partition_tablename = v_child_stop THEN RAISE DEBUG 'reapply_constraint: Reached stop at %.%', v_row.partition_schemaname, v_row.partition_tablename; EXIT; -- stop creating constraints after optimize target is reached END IF; COMMIT; PERFORM pg_sleep(p_wait); END LOOP; EXECUTE format('ANALYZE %I.%I', v_parent_schema, v_parent_tablename); PERFORM pg_advisory_unlock(hashtext('pg_partman reapply_constraints')); END $$; $reapply_constraints_proc$; EXECUTE v_reapply_constraints_proc_sql; -- Restore dropped object privileges DO $$ DECLARE v_row record; BEGIN FOR v_row IN SELECT statement FROM partman_preserve_privs_temp LOOP IF v_row.statement IS NOT NULL THEN EXECUTE v_row.statement; END IF; END LOOP; END $$; -- ######################## END POSTGRESQL 11 ONLY SECTION ############################## END IF; END $pg11only$; -- ######################## END POSTGRESQL 11 ONLY SECTION ############################## DROP TABLE IF EXISTS partman_preserve_privs_temp;