The code you are working off is outdated. It is also rather inefficient for any version of Postgres. (I'd be interested where you got that from?)
Completely rewrite the whole function with features of modern-day SQL and PL/pgSQL:
CREATE OR REPLACE FUNCTION manage_partitions(
v_date timestamp
, master_table regclass
, prime_key text
, prime_date text)
RETURNS void
LANGUAGE plpgsql AS
$func$
DECLARE
v_current_date timestamp := COALESCE(v_date, now()); -- fallback for NULL
v_date_from timestamp;
v_partition_name text;
BEGIN
-- drop partition for previous quarter of previous year if exists --
v_date_from := date_trunc('quarter', v_current_date - interval '3 month');
IF v_date_from < date_trunc('year', now()) THEN -- your (odd?) condition
v_partition_name := master_table || to_char(v_date_from, '"_q"Q_YYYY');
EXECUTE format(
'DROP RULE IF EXISTS %I ON %s;
DROP TABLE IF EXISTS %I'
, 'rule_' || master_table || to_char(v_date_from, '"_q"Q_YYYY')
, master_table
, v_partition_name);
END IF;
-- create partition for this quarter --
v_date_from := date_trunc('quarter', v_current_date);
v_partition_name := master_table || to_char(v_date_from, '"_q"Q_YYYY');
IF NOT EXISTS (
SELECT FROM pg_tables t
WHERE t.schemaname = 'public'
AND t.tablename = v_partition_name) THEN
EXECUTE format(
'CREATE TABLE %$1I (
PRIMARY KEY (%$2I), CHECK (%$3L >= %$4L AND %$3L < %$5L))
INHERITS (%$6s);
CREATE RULE %$7I AS ON INSERT TO %$6s DO INSTEAD
INSERT INTO %$1I VALUES (NEW.*)'
, v_partition_name
, prime_key
, prime_date
, v_date_from
, v_date_from + interval '3 month'
, master_table
, 'rule_' || master_table || to_char(v_date_from, '"_q"Q_YYYY')
);
END IF;
-- create partition for next quarter --
v_date_from := date_trunc('quarter', v_current_date + interval '3 month');
v_partition_name := master_table || to_char(v_date_from, '"_q"Q_YYYY');
IF NOT EXISTS (
SELECT FROM pg_tables t
WHERE t.schemaname = 'public'
AND t.tablename = v_partition_name) THEN
EXECUTE format(
'CREATE TABLE %$1I (
PRIMARY KEY (%$2I), CHECK (%$3L >= %$4L AND %$3L < %$5L))
INHERITS (%$6s);
CREATE RULE %$7I AS ON INSERT TO %$6s DO INSTEAD
INSERT INTO %$1I VALUES (NEW.*)'
, v_partition_name
, prime_key
, prime_date
, v_date_from
, v_date_from + interval '3 month'
, master_table
, 'rule_' || master_table || to_char(v_date_from, '"_q"Q_YYYY')
);
END IF;
END
$func$;
You can assign variables at declaration time to simplify code.
Replace:
'_Q' || EXTRACT(QUARTER FROM v_date_from) || '_' || EXTRACT(YEAR FROM v_date_from)
with the simpler and faster:
to_char(v_date_from, '"Q_"Q_YYYY')
to_char()
in the manual.
use the superior for IF EXISTS (...) THEN ...
. We can then drop the useless variable v_exists
. See:
Have a look at the manual page on format()
(Postgres 9.1+). If you are going to work with dynamic SQL you need to know about it.
Use the object identifier type regclass
for master_table
to verify that it exists and is visible with the current setting for search_path and prevent SQL injection at the same time. See:
Don't use the outdated and discouraged ALIAS FOR
clause. Use parameter names instead, like you already do for all other function params.
Note how I replaced capital letters in identifiers (which are a bad idea) with lower case letters ('Q' -> 'q'). More in the last paragraph of this related answer:
I also dropped the variables v_date_to
and v_rule_name
and replaced them with expressions since those are used once only in my code.