1-- Test basic TRUNCATE functionality.
2CREATE TABLE truncate_a (col1 integer primary key);
3INSERT INTO truncate_a VALUES (1);
4INSERT INTO truncate_a VALUES (2);
5SELECT * FROM truncate_a;
6 col1
7------
8    1
9    2
10(2 rows)
11
12-- Roll truncate back
13BEGIN;
14TRUNCATE truncate_a;
15ROLLBACK;
16SELECT * FROM truncate_a;
17 col1
18------
19    1
20    2
21(2 rows)
22
23-- Commit the truncate this time
24BEGIN;
25TRUNCATE truncate_a;
26COMMIT;
27SELECT * FROM truncate_a;
28 col1
29------
30(0 rows)
31
32-- Test foreign-key checks
33CREATE TABLE trunc_b (a int REFERENCES truncate_a);
34CREATE TABLE trunc_c (a serial PRIMARY KEY);
35CREATE TABLE trunc_d (a int REFERENCES trunc_c);
36CREATE TABLE trunc_e (a int REFERENCES truncate_a, b int REFERENCES trunc_c);
37TRUNCATE TABLE truncate_a;		-- fail
38ERROR:  cannot truncate a table referenced in a foreign key constraint
39DETAIL:  Table "trunc_b" references "truncate_a".
40HINT:  Truncate table "trunc_b" at the same time, or use TRUNCATE ... CASCADE.
41TRUNCATE TABLE truncate_a,trunc_b;		-- fail
42ERROR:  cannot truncate a table referenced in a foreign key constraint
43DETAIL:  Table "trunc_e" references "truncate_a".
44HINT:  Truncate table "trunc_e" at the same time, or use TRUNCATE ... CASCADE.
45TRUNCATE TABLE truncate_a,trunc_b,trunc_e;	-- ok
46TRUNCATE TABLE truncate_a,trunc_e;		-- fail
47ERROR:  cannot truncate a table referenced in a foreign key constraint
48DETAIL:  Table "trunc_b" references "truncate_a".
49HINT:  Truncate table "trunc_b" at the same time, or use TRUNCATE ... CASCADE.
50TRUNCATE TABLE trunc_c;		-- fail
51ERROR:  cannot truncate a table referenced in a foreign key constraint
52DETAIL:  Table "trunc_d" references "trunc_c".
53HINT:  Truncate table "trunc_d" at the same time, or use TRUNCATE ... CASCADE.
54TRUNCATE TABLE trunc_c,trunc_d;		-- fail
55ERROR:  cannot truncate a table referenced in a foreign key constraint
56DETAIL:  Table "trunc_e" references "trunc_c".
57HINT:  Truncate table "trunc_e" at the same time, or use TRUNCATE ... CASCADE.
58TRUNCATE TABLE trunc_c,trunc_d,trunc_e;	-- ok
59TRUNCATE TABLE trunc_c,trunc_d,trunc_e,truncate_a;	-- fail
60ERROR:  cannot truncate a table referenced in a foreign key constraint
61DETAIL:  Table "trunc_b" references "truncate_a".
62HINT:  Truncate table "trunc_b" at the same time, or use TRUNCATE ... CASCADE.
63TRUNCATE TABLE trunc_c,trunc_d,trunc_e,truncate_a,trunc_b;	-- ok
64TRUNCATE TABLE truncate_a RESTRICT; -- fail
65ERROR:  cannot truncate a table referenced in a foreign key constraint
66DETAIL:  Table "trunc_b" references "truncate_a".
67HINT:  Truncate table "trunc_b" at the same time, or use TRUNCATE ... CASCADE.
68TRUNCATE TABLE truncate_a CASCADE;  -- ok
69NOTICE:  truncate cascades to table "trunc_b"
70NOTICE:  truncate cascades to table "trunc_e"
71-- circular references
72ALTER TABLE truncate_a ADD FOREIGN KEY (col1) REFERENCES trunc_c;
73-- Add some data to verify that truncating actually works ...
74INSERT INTO trunc_c VALUES (1);
75INSERT INTO truncate_a VALUES (1);
76INSERT INTO trunc_b VALUES (1);
77INSERT INTO trunc_d VALUES (1);
78INSERT INTO trunc_e VALUES (1,1);
79TRUNCATE TABLE trunc_c;
80ERROR:  cannot truncate a table referenced in a foreign key constraint
81DETAIL:  Table "truncate_a" references "trunc_c".
82HINT:  Truncate table "truncate_a" at the same time, or use TRUNCATE ... CASCADE.
83TRUNCATE TABLE trunc_c,truncate_a;
84ERROR:  cannot truncate a table referenced in a foreign key constraint
85DETAIL:  Table "trunc_d" references "trunc_c".
86HINT:  Truncate table "trunc_d" at the same time, or use TRUNCATE ... CASCADE.
87TRUNCATE TABLE trunc_c,truncate_a,trunc_d;
88ERROR:  cannot truncate a table referenced in a foreign key constraint
89DETAIL:  Table "trunc_e" references "trunc_c".
90HINT:  Truncate table "trunc_e" at the same time, or use TRUNCATE ... CASCADE.
91TRUNCATE TABLE trunc_c,truncate_a,trunc_d,trunc_e;
92ERROR:  cannot truncate a table referenced in a foreign key constraint
93DETAIL:  Table "trunc_b" references "truncate_a".
94HINT:  Truncate table "trunc_b" at the same time, or use TRUNCATE ... CASCADE.
95TRUNCATE TABLE trunc_c,truncate_a,trunc_d,trunc_e,trunc_b;
96-- Verify that truncating did actually work
97SELECT * FROM truncate_a
98   UNION ALL
99 SELECT * FROM trunc_c
100   UNION ALL
101 SELECT * FROM trunc_b
102   UNION ALL
103 SELECT * FROM trunc_d;
104 col1
105------
106(0 rows)
107
108SELECT * FROM trunc_e;
109 a | b
110---+---
111(0 rows)
112
113-- Add data again to test TRUNCATE ... CASCADE
114INSERT INTO trunc_c VALUES (1);
115INSERT INTO truncate_a VALUES (1);
116INSERT INTO trunc_b VALUES (1);
117INSERT INTO trunc_d VALUES (1);
118INSERT INTO trunc_e VALUES (1,1);
119TRUNCATE TABLE trunc_c CASCADE;  -- ok
120NOTICE:  truncate cascades to table "truncate_a"
121NOTICE:  truncate cascades to table "trunc_d"
122NOTICE:  truncate cascades to table "trunc_e"
123NOTICE:  truncate cascades to table "trunc_b"
124SELECT * FROM truncate_a
125   UNION ALL
126 SELECT * FROM trunc_c
127   UNION ALL
128 SELECT * FROM trunc_b
129   UNION ALL
130 SELECT * FROM trunc_d;
131 col1
132------
133(0 rows)
134
135SELECT * FROM trunc_e;
136 a | b
137---+---
138(0 rows)
139
140DROP TABLE truncate_a,trunc_c,trunc_b,trunc_d,trunc_e CASCADE;
141-- Test TRUNCATE with inheritance
142CREATE TABLE trunc_f (col1 integer primary key);
143INSERT INTO trunc_f VALUES (1);
144INSERT INTO trunc_f VALUES (2);
145CREATE TABLE trunc_fa (col2a text) INHERITS (trunc_f);
146INSERT INTO trunc_fa VALUES (3, 'three');
147CREATE TABLE trunc_fb (col2b int) INHERITS (trunc_f);
148INSERT INTO trunc_fb VALUES (4, 444);
149CREATE TABLE trunc_faa (col3 text) INHERITS (trunc_fa);
150INSERT INTO trunc_faa VALUES (5, 'five', 'FIVE');
151BEGIN;
152SELECT * FROM trunc_f;
153 col1
154------
155    1
156    2
157    3
158    4
159    5
160(5 rows)
161
162TRUNCATE trunc_f;
163SELECT * FROM trunc_f;
164 col1
165------
166(0 rows)
167
168ROLLBACK;
169BEGIN;
170SELECT * FROM trunc_f;
171 col1
172------
173    1
174    2
175    3
176    4
177    5
178(5 rows)
179
180TRUNCATE ONLY trunc_f;
181SELECT * FROM trunc_f;
182 col1
183------
184    3
185    4
186    5
187(3 rows)
188
189ROLLBACK;
190BEGIN;
191SELECT * FROM trunc_f;
192 col1
193------
194    1
195    2
196    3
197    4
198    5
199(5 rows)
200
201SELECT * FROM trunc_fa;
202 col1 | col2a
203------+-------
204    3 | three
205    5 | five
206(2 rows)
207
208SELECT * FROM trunc_faa;
209 col1 | col2a | col3
210------+-------+------
211    5 | five  | FIVE
212(1 row)
213
214TRUNCATE ONLY trunc_fb, ONLY trunc_fa;
215SELECT * FROM trunc_f;
216 col1
217------
218    1
219    2
220    5
221(3 rows)
222
223SELECT * FROM trunc_fa;
224 col1 | col2a
225------+-------
226    5 | five
227(1 row)
228
229SELECT * FROM trunc_faa;
230 col1 | col2a | col3
231------+-------+------
232    5 | five  | FIVE
233(1 row)
234
235ROLLBACK;
236BEGIN;
237SELECT * FROM trunc_f;
238 col1
239------
240    1
241    2
242    3
243    4
244    5
245(5 rows)
246
247SELECT * FROM trunc_fa;
248 col1 | col2a
249------+-------
250    3 | three
251    5 | five
252(2 rows)
253
254SELECT * FROM trunc_faa;
255 col1 | col2a | col3
256------+-------+------
257    5 | five  | FIVE
258(1 row)
259
260TRUNCATE ONLY trunc_fb, trunc_fa;
261SELECT * FROM trunc_f;
262 col1
263------
264    1
265    2
266(2 rows)
267
268SELECT * FROM trunc_fa;
269 col1 | col2a
270------+-------
271(0 rows)
272
273SELECT * FROM trunc_faa;
274 col1 | col2a | col3
275------+-------+------
276(0 rows)
277
278ROLLBACK;
279DROP TABLE trunc_f CASCADE;
280NOTICE:  drop cascades to 3 other objects
281DETAIL:  drop cascades to table trunc_fa
282drop cascades to table trunc_faa
283drop cascades to table trunc_fb
284-- Test ON TRUNCATE triggers
285CREATE TABLE trunc_trigger_test (f1 int, f2 text, f3 text);
286CREATE TABLE trunc_trigger_log (tgop text, tglevel text, tgwhen text,
287        tgargv text, tgtable name, rowcount bigint);
288CREATE FUNCTION trunctrigger() RETURNS trigger as $$
289declare c bigint;
290begin
291    execute 'select count(*) from ' || quote_ident(tg_table_name) into c;
292    insert into trunc_trigger_log values
293      (TG_OP, TG_LEVEL, TG_WHEN, TG_ARGV[0], tg_table_name, c);
294    return null;
295end;
296$$ LANGUAGE plpgsql;
297-- basic before trigger
298INSERT INTO trunc_trigger_test VALUES(1, 'foo', 'bar'), (2, 'baz', 'quux');
299CREATE TRIGGER t
300BEFORE TRUNCATE ON trunc_trigger_test
301FOR EACH STATEMENT
302EXECUTE PROCEDURE trunctrigger('before trigger truncate');
303SELECT count(*) as "Row count in test table" FROM trunc_trigger_test;
304 Row count in test table
305-------------------------
306                       2
307(1 row)
308
309SELECT * FROM trunc_trigger_log;
310 tgop | tglevel | tgwhen | tgargv | tgtable | rowcount
311------+---------+--------+--------+---------+----------
312(0 rows)
313
314TRUNCATE trunc_trigger_test;
315SELECT count(*) as "Row count in test table" FROM trunc_trigger_test;
316 Row count in test table
317-------------------------
318                       0
319(1 row)
320
321SELECT * FROM trunc_trigger_log;
322   tgop   |  tglevel  | tgwhen |         tgargv          |      tgtable       | rowcount
323----------+-----------+--------+-------------------------+--------------------+----------
324 TRUNCATE | STATEMENT | BEFORE | before trigger truncate | trunc_trigger_test |        2
325(1 row)
326
327DROP TRIGGER t ON trunc_trigger_test;
328truncate trunc_trigger_log;
329-- same test with an after trigger
330INSERT INTO trunc_trigger_test VALUES(1, 'foo', 'bar'), (2, 'baz', 'quux');
331CREATE TRIGGER tt
332AFTER TRUNCATE ON trunc_trigger_test
333FOR EACH STATEMENT
334EXECUTE PROCEDURE trunctrigger('after trigger truncate');
335SELECT count(*) as "Row count in test table" FROM trunc_trigger_test;
336 Row count in test table
337-------------------------
338                       2
339(1 row)
340
341SELECT * FROM trunc_trigger_log;
342 tgop | tglevel | tgwhen | tgargv | tgtable | rowcount
343------+---------+--------+--------+---------+----------
344(0 rows)
345
346TRUNCATE trunc_trigger_test;
347SELECT count(*) as "Row count in test table" FROM trunc_trigger_test;
348 Row count in test table
349-------------------------
350                       0
351(1 row)
352
353SELECT * FROM trunc_trigger_log;
354   tgop   |  tglevel  | tgwhen |         tgargv         |      tgtable       | rowcount
355----------+-----------+--------+------------------------+--------------------+----------
356 TRUNCATE | STATEMENT | AFTER  | after trigger truncate | trunc_trigger_test |        0
357(1 row)
358
359DROP TABLE trunc_trigger_test;
360DROP TABLE trunc_trigger_log;
361DROP FUNCTION trunctrigger();
362-- test TRUNCATE ... RESTART IDENTITY
363CREATE SEQUENCE truncate_a_id1 START WITH 33;
364CREATE TABLE truncate_a (id serial,
365                         id1 integer default nextval('truncate_a_id1'));
366ALTER SEQUENCE truncate_a_id1 OWNED BY truncate_a.id1;
367INSERT INTO truncate_a DEFAULT VALUES;
368INSERT INTO truncate_a DEFAULT VALUES;
369SELECT * FROM truncate_a;
370 id | id1
371----+-----
372  1 |  33
373  2 |  34
374(2 rows)
375
376TRUNCATE truncate_a;
377INSERT INTO truncate_a DEFAULT VALUES;
378INSERT INTO truncate_a DEFAULT VALUES;
379SELECT * FROM truncate_a;
380 id | id1
381----+-----
382  3 |  35
383  4 |  36
384(2 rows)
385
386TRUNCATE truncate_a RESTART IDENTITY;
387INSERT INTO truncate_a DEFAULT VALUES;
388INSERT INTO truncate_a DEFAULT VALUES;
389SELECT * FROM truncate_a;
390 id | id1
391----+-----
392  1 |  33
393  2 |  34
394(2 rows)
395
396CREATE TABLE truncate_b (id int GENERATED ALWAYS AS IDENTITY (START WITH 44));
397INSERT INTO truncate_b DEFAULT VALUES;
398INSERT INTO truncate_b DEFAULT VALUES;
399SELECT * FROM truncate_b;
400 id
401----
402 44
403 45
404(2 rows)
405
406TRUNCATE truncate_b;
407INSERT INTO truncate_b DEFAULT VALUES;
408INSERT INTO truncate_b DEFAULT VALUES;
409SELECT * FROM truncate_b;
410 id
411----
412 46
413 47
414(2 rows)
415
416TRUNCATE truncate_b RESTART IDENTITY;
417INSERT INTO truncate_b DEFAULT VALUES;
418INSERT INTO truncate_b DEFAULT VALUES;
419SELECT * FROM truncate_b;
420 id
421----
422 44
423 45
424(2 rows)
425
426-- check rollback of a RESTART IDENTITY operation
427BEGIN;
428TRUNCATE truncate_a RESTART IDENTITY;
429INSERT INTO truncate_a DEFAULT VALUES;
430SELECT * FROM truncate_a;
431 id | id1
432----+-----
433  1 |  33
434(1 row)
435
436ROLLBACK;
437INSERT INTO truncate_a DEFAULT VALUES;
438INSERT INTO truncate_a DEFAULT VALUES;
439SELECT * FROM truncate_a;
440 id | id1
441----+-----
442  1 |  33
443  2 |  34
444  3 |  35
445  4 |  36
446(4 rows)
447
448DROP TABLE truncate_a;
449SELECT nextval('truncate_a_id1'); -- fail, seq should have been dropped
450ERROR:  relation "truncate_a_id1" does not exist
451LINE 1: SELECT nextval('truncate_a_id1');
452                       ^
453-- partitioned table
454CREATE TABLE truncparted (a int, b char) PARTITION BY LIST (a);
455-- error, can't truncate a partitioned table
456TRUNCATE ONLY truncparted;
457ERROR:  cannot truncate only a partitioned table
458HINT:  Do not specify the ONLY keyword, or use TRUNCATE ONLY on the partitions directly.
459CREATE TABLE truncparted1 PARTITION OF truncparted FOR VALUES IN (1);
460INSERT INTO truncparted VALUES (1, 'a');
461-- error, must truncate partitions
462TRUNCATE ONLY truncparted;
463ERROR:  cannot truncate only a partitioned table
464HINT:  Do not specify the ONLY keyword, or use TRUNCATE ONLY on the partitions directly.
465TRUNCATE truncparted;
466DROP TABLE truncparted;
467-- foreign key on partitioned table: partition key is referencing column.
468-- Make sure truncate did execute on all tables
469CREATE FUNCTION tp_ins_data() RETURNS void LANGUAGE plpgsql AS $$
470  BEGIN
471	INSERT INTO truncprim VALUES (1), (100), (150);
472	INSERT INTO truncpart VALUES (1), (100), (150);
473  END
474$$;
475CREATE FUNCTION tp_chk_data(OUT pktb regclass, OUT pkval int, OUT fktb regclass, OUT fkval int)
476  RETURNS SETOF record LANGUAGE plpgsql AS $$
477  BEGIN
478    RETURN QUERY SELECT
479      pk.tableoid::regclass, pk.a, fk.tableoid::regclass, fk.a
480    FROM truncprim pk FULL JOIN truncpart fk USING (a)
481    ORDER BY 2, 4;
482  END
483$$;
484CREATE TABLE truncprim (a int PRIMARY KEY);
485CREATE TABLE truncpart (a int REFERENCES truncprim)
486  PARTITION BY RANGE (a);
487CREATE TABLE truncpart_1 PARTITION OF truncpart FOR VALUES FROM (0) TO (100);
488CREATE TABLE truncpart_2 PARTITION OF truncpart FOR VALUES FROM (100) TO (200)
489  PARTITION BY RANGE (a);
490CREATE TABLE truncpart_2_1 PARTITION OF truncpart_2 FOR VALUES FROM (100) TO (150);
491CREATE TABLE truncpart_2_d PARTITION OF truncpart_2 DEFAULT;
492TRUNCATE TABLE truncprim;	-- should fail
493ERROR:  cannot truncate a table referenced in a foreign key constraint
494DETAIL:  Table "truncpart" references "truncprim".
495HINT:  Truncate table "truncpart" at the same time, or use TRUNCATE ... CASCADE.
496select tp_ins_data();
497 tp_ins_data
498-------------
499
500(1 row)
501
502-- should truncate everything
503TRUNCATE TABLE truncprim, truncpart;
504select * from tp_chk_data();
505 pktb | pkval | fktb | fkval
506------+-------+------+-------
507(0 rows)
508
509select tp_ins_data();
510 tp_ins_data
511-------------
512
513(1 row)
514
515-- should truncate everything
516TRUNCATE TABLE truncprim CASCADE;
517NOTICE:  truncate cascades to table "truncpart"
518NOTICE:  truncate cascades to table "truncpart_1"
519NOTICE:  truncate cascades to table "truncpart_2"
520NOTICE:  truncate cascades to table "truncpart_2_1"
521NOTICE:  truncate cascades to table "truncpart_2_d"
522SELECT * FROM tp_chk_data();
523 pktb | pkval | fktb | fkval
524------+-------+------+-------
525(0 rows)
526
527SELECT tp_ins_data();
528 tp_ins_data
529-------------
530
531(1 row)
532
533-- should truncate all partitions
534TRUNCATE TABLE truncpart;
535SELECT * FROM tp_chk_data();
536   pktb    | pkval | fktb | fkval
537-----------+-------+------+-------
538 truncprim |     1 |      |
539 truncprim |   100 |      |
540 truncprim |   150 |      |
541(3 rows)
542
543DROP TABLE truncprim, truncpart;
544DROP FUNCTION tp_ins_data(), tp_chk_data();
545-- test cascade when referencing a partitioned table
546CREATE TABLE trunc_a (a INT PRIMARY KEY) PARTITION BY RANGE (a);
547CREATE TABLE trunc_a1 PARTITION OF trunc_a FOR VALUES FROM (0) TO (10);
548CREATE TABLE trunc_a2 PARTITION OF trunc_a FOR VALUES FROM (10) TO (20)
549  PARTITION BY RANGE (a);
550CREATE TABLE trunc_a21 PARTITION OF trunc_a2 FOR VALUES FROM (10) TO (12);
551CREATE TABLE trunc_a22 PARTITION OF trunc_a2 FOR VALUES FROM (12) TO (16);
552CREATE TABLE trunc_a2d PARTITION OF trunc_a2 DEFAULT;
553CREATE TABLE trunc_a3 PARTITION OF trunc_a FOR VALUES FROM (20) TO (30);
554INSERT INTO trunc_a VALUES (0), (5), (10), (15), (20), (25);
555-- truncate a partition cascading to a table
556CREATE TABLE ref_b (
557    b INT PRIMARY KEY,
558    a INT REFERENCES trunc_a(a) ON DELETE CASCADE
559);
560INSERT INTO ref_b VALUES (10, 0), (50, 5), (100, 10), (150, 15);
561TRUNCATE TABLE trunc_a1 CASCADE;
562NOTICE:  truncate cascades to table "ref_b"
563SELECT a FROM ref_b;
564 a
565---
566(0 rows)
567
568DROP TABLE ref_b;
569-- truncate a partition cascading to a partitioned table
570CREATE TABLE ref_c (
571    c INT PRIMARY KEY,
572    a INT REFERENCES trunc_a(a) ON DELETE CASCADE
573) PARTITION BY RANGE (c);
574CREATE TABLE ref_c1 PARTITION OF ref_c FOR VALUES FROM (100) TO (200);
575CREATE TABLE ref_c2 PARTITION OF ref_c FOR VALUES FROM (200) TO (300);
576INSERT INTO ref_c VALUES (100, 10), (150, 15), (200, 20), (250, 25);
577TRUNCATE TABLE trunc_a21 CASCADE;
578NOTICE:  truncate cascades to table "ref_c"
579NOTICE:  truncate cascades to table "ref_c1"
580NOTICE:  truncate cascades to table "ref_c2"
581SELECT a as "from table ref_c" FROM ref_c;
582 from table ref_c
583------------------
584(0 rows)
585
586SELECT a as "from table trunc_a" FROM trunc_a ORDER BY a;
587 from table trunc_a
588--------------------
589                 15
590                 20
591                 25
592(3 rows)
593
594DROP TABLE trunc_a, ref_c;
595