diff --git a/postgresql/procedures/01_maintenance.sql b/postgresql/procedures/01_maintenance.sql index 9be5ada..19263e4 100644 --- a/postgresql/procedures/01_maintenance.sql +++ b/postgresql/procedures/01_maintenance.sql @@ -52,10 +52,15 @@ BEGIN v_partition_name := p_parent_table || '_p' || v_suffix; IF NOT partitions.partition_exists(v_partition_name) THEN - EXECUTE format( - 'CREATE TABLE %I.%I PARTITION OF %I.%I FOR VALUES FROM (%s) TO (%s)', - v_parent_schema, v_partition_name, v_parent_schema, p_parent_table, v_start_ts, v_end_ts - ); + BEGIN + EXECUTE format( + 'CREATE TABLE %I.%I PARTITION OF %I.%I FOR VALUES FROM (%s) TO (%s)', + v_parent_schema, v_partition_name, v_parent_schema, p_parent_table, v_start_ts, v_end_ts + ); + EXCEPTION WHEN invalid_object_definition THEN + -- Ignore overlap errors (e.g., when transitioning from daily to hourly partitioning) + RAISE NOTICE 'Partition % overlaps with an existing partition. Skipping.', v_partition_name; + END; END IF; END; $$; @@ -92,11 +97,11 @@ BEGIN BEGIN IF length(v_suffix) = 6 THEN -- YYYYMM - v_partition_date := to_timestamp(v_suffix || '01', 'YYYYMMDD') AT TIME ZONE 'UTC'; + v_partition_date := timezone('UTC', to_timestamp(v_suffix || '01', 'YYYYMMDD')::timestamp without time zone); ELSIF length(v_suffix) = 8 THEN -- YYYYMMDD - v_partition_date := to_timestamp(v_suffix, 'YYYYMMDD') AT TIME ZONE 'UTC'; + v_partition_date := timezone('UTC', to_timestamp(v_suffix, 'YYYYMMDD')::timestamp without time zone); ELSIF length(v_suffix) = 10 THEN -- YYYYMMDDHH - v_partition_date := to_timestamp(v_suffix, 'YYYYMMDDHH24') AT TIME ZONE 'UTC'; + v_partition_date := timezone('UTC', to_timestamp(v_suffix, 'YYYYMMDDHH24')::timestamp without time zone); ELSE CONTINUE; -- Ignore non-matching suffix lengths END IF; @@ -153,25 +158,25 @@ DECLARE BEGIN IF p_period = 'day' THEN v_period_interval := '1 day'::interval; - v_start_time := date_trunc('day', now() AT TIME ZONE 'UTC'); + v_start_time := date_trunc('day', now(), 'UTC'); -- Calculate how many past days cover the retention period (86400 seconds = 1 day) v_past_iterations := ceil(extract(epoch from p_keep_history) / 86400)::integer; ELSIF p_period = 'week' THEN v_period_interval := '1 week'::interval; - v_start_time := date_trunc('week', now() AT TIME ZONE 'UTC'); + v_start_time := date_trunc('week', now(), 'UTC'); -- 604800 seconds = 1 week v_past_iterations := ceil(extract(epoch from p_keep_history) / 604800)::integer; ELSIF p_period = 'month' THEN v_period_interval := '1 month'::interval; - v_start_time := date_trunc('month', now() AT TIME ZONE 'UTC'); + v_start_time := date_trunc('month', now(), 'UTC'); -- Approximate 30 days per month (2592000 seconds) v_past_iterations := ceil(extract(epoch from p_keep_history) / 2592000)::integer; ELSIF p_period LIKE '%hour%' THEN v_period_interval := p_period::interval; - v_start_time := date_trunc('hour', now() AT TIME ZONE 'UTC'); + v_start_time := to_timestamp(floor(extract(epoch from now()) / extract(epoch from v_period_interval)) * extract(epoch from v_period_interval)); v_past_iterations := ceil(extract(epoch from p_keep_history) / extract(epoch from v_period_interval))::integer; ELSE diff --git a/postgresql/tests/docker/init_scripts/01_30_maintenance.sql b/postgresql/tests/docker/init_scripts/01_30_maintenance.sql index c51a3fd..4d3d832 100644 --- a/postgresql/tests/docker/init_scripts/01_30_maintenance.sql +++ b/postgresql/tests/docker/init_scripts/01_30_maintenance.sql @@ -90,7 +90,7 @@ BEGIN BEGIN IF length(v_suffix) = 6 THEN -- YYYYMM - v_partition_date := to_timestamp(v_suffix || '01', 'YYYYMMDD') AT TIME ZONE 'UTC'; + v_partition_date := timezone('UTC', to_timestamp(v_suffix || '01', 'YYYYMMDD')::timestamp without time zone); -- For monthly, we check if the END of the month is older than retention? -- Or just strict retention. -- To be safe, adding 1 month to check vs cutoff. @@ -100,7 +100,7 @@ BEGIN COMMIT; -- Release lock immediately END IF; ELSIF length(v_suffix) = 8 THEN -- YYYYMMDD - v_partition_date := to_timestamp(v_suffix, 'YYYYMMDD') AT TIME ZONE 'UTC'; + v_partition_date := timezone('UTC', to_timestamp(v_suffix, 'YYYYMMDD')::timestamp without time zone); IF extract(epoch from (v_partition_date + '1 day'::interval)) < v_cutoff_ts THEN RAISE NOTICE 'Dropping old partition %', v_partition.partition_name; EXECUTE format('DROP TABLE %I.%I', v_partition.partition_schema, v_partition.partition_name); @@ -130,19 +130,19 @@ DECLARE BEGIN IF p_period = 'day' THEN v_period_interval := '1 day'::interval; - v_start_time := date_trunc('day', now() AT TIME ZONE 'UTC'); + v_start_time := date_trunc('day', now(), 'UTC'); -- Calculate how many past days cover the retention period (86400 seconds = 1 day) v_past_iterations := ceil(extract(epoch from p_keep_history) / 86400)::integer; ELSIF p_period = 'week' THEN v_period_interval := '1 week'::interval; - v_start_time := date_trunc('week', now() AT TIME ZONE 'UTC'); + v_start_time := date_trunc('week', now(), 'UTC'); -- 604800 seconds = 1 week v_past_iterations := ceil(extract(epoch from p_keep_history) / 604800)::integer; ELSIF p_period = 'month' THEN v_period_interval := '1 month'::interval; - v_start_time := date_trunc('month', now() AT TIME ZONE 'UTC'); + v_start_time := date_trunc('month', now(), 'UTC'); -- Approximate 30 days per month (2592000 seconds) v_past_iterations := ceil(extract(epoch from p_keep_history) / 2592000)::integer; ELSE