/
pg_partman--1.5.1--1.6.0.sql
3009 lines (2636 loc) · 129 KB
/
pg_partman--1.5.1--1.6.0.sql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
-- 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;