]> granicus.if.org Git - postgresql/commitdiff
Fix missing values when doing ALTER TABLE ALTER COLUMN TYPE
authorAndrew Dunstan <andrew@dunslane.net>
Thu, 10 Jan 2019 20:53:45 +0000 (15:53 -0500)
committerAndrew Dunstan <andrew@dunslane.net>
Thu, 10 Jan 2019 20:53:45 +0000 (15:53 -0500)
This was an oversight in commit 16828d5c. If the table is going to be
rewritten, we simply clear all the missing values from all the table's
attributes, since there will no longer be any rows with the attributes
missing. Otherwise, we repackage the missing value in an array
constructed with the new type specifications.

Backpatch to release 11.

This fixes bug #15446, reported by Dmitry Molotkov

Reviewed by Dean Rasheed

src/backend/commands/tablecmds.c
src/test/regress/expected/fast_default.out
src/test/regress/sql/fast_default.sql

index c75c5808ba11daeef579eb69db45d02668eb0fd9..e7017e90d108388eac65a605a9b4ddcb1220dd46 100644 (file)
@@ -9285,6 +9285,21 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
        HeapTuple       depTup;
        ObjectAddress address;
 
+       /*
+        * Clear all the missing values if we're rewriting the table, since this
+        * renders them pointless.
+        */
+       if (tab->rewrite)
+       {
+               Relation    newrel;
+
+               newrel = heap_open(RelationGetRelid(rel), NoLock);
+               RelationClearMissing(newrel);
+               relation_close(newrel, NoLock);
+               /* make sure we don't conflict with later attribute modifications */
+               CommandCounterIncrement();
+       }
+
        attrelation = heap_open(AttributeRelationId, RowExclusiveLock);
 
        /* Look up the target column */
@@ -9601,7 +9616,69 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
        /*
         * Here we go --- change the recorded column type and collation.  (Note
         * heapTup is a copy of the syscache entry, so okay to scribble on.)
+        * First fix up the missing value if any.
         */
+       if (attTup->atthasmissing)
+       {
+               Datum       missingval;
+               bool        missingNull;
+
+               /* if rewrite is true the missing value should already be cleared */
+               Assert(tab->rewrite == 0);
+
+               /* Get the missing value datum */
+               missingval = heap_getattr(heapTup,
+                                                                 Anum_pg_attribute_attmissingval,
+                                                                 attrelation->rd_att,
+                                                                 &missingNull);
+
+               /* if it's a null array there is nothing to do */
+
+               if (! missingNull)
+               {
+                       /*
+                        * Get the datum out of the array and repack it in a new array
+                        * built with the new type data. We assume that since the table
+                        * doesn't need rewriting, the actual Datum doesn't need to be
+                        * changed, only the array metadata.
+                        */
+
+                       int one = 1;
+                       bool isNull;
+                       Datum       valuesAtt[Natts_pg_attribute];
+                       bool        nullsAtt[Natts_pg_attribute];
+                       bool        replacesAtt[Natts_pg_attribute];
+
+                       MemSet(valuesAtt, 0, sizeof(valuesAtt));
+                       MemSet(nullsAtt, false, sizeof(nullsAtt));
+                       MemSet(replacesAtt, false, sizeof(replacesAtt));
+
+                       missingval = array_get_element(missingval,
+                                                                                  1,
+                                                                                  &one,
+                                                                                  0,
+                                                                                  attTup->attlen,
+                                                                                  attTup->attbyval,
+                                                                                  attTup->attalign,
+                                                                                  &isNull);
+                       missingval = PointerGetDatum(
+                               construct_array(&missingval,
+                                                               1,
+                                                               targettype,
+                                                               tform->typlen,
+                                                               tform->typbyval,
+                                                               tform->typalign));
+
+                       valuesAtt[Anum_pg_attribute_attmissingval - 1] = missingval;
+                       replacesAtt[Anum_pg_attribute_attmissingval - 1] = true;
+                       nullsAtt[Anum_pg_attribute_attmissingval - 1] = false;
+
+                       heapTup = heap_modify_tuple(heapTup, RelationGetDescr(attrelation),
+                                                                               valuesAtt, nullsAtt, replacesAtt);
+                       attTup = (Form_pg_attribute) GETSTRUCT(heapTup);
+               }
+       }
+
        attTup->atttypid = targettype;
        attTup->atttypmod = targettypmod;
        attTup->attcollation = targetcollid;
index 1c1924cd5c161d66da9fd56591121d1f238c2114..40a15bd2d61c7a491b723cb324d537a8a3660691 100644 (file)
@@ -735,7 +735,44 @@ INSERT INTO leader VALUES (1, 1), (2, 2);
 ALTER TABLE leader ADD c int;
 ALTER TABLE leader DROP c;
 DELETE FROM leader;
+-- check that ALTER TABLE ... ALTER TYPE does the right thing
+CREATE TABLE vtype( a integer);
+INSERT INTO vtype VALUES (1);
+ALTER TABLE vtype ADD COLUMN b DOUBLE PRECISION DEFAULT 0.2;
+ALTER TABLE vtype ADD COLUMN c BOOLEAN DEFAULT true;
+SELECT * FROM vtype;
+ a |  b  | c 
+---+-----+---
+ 1 | 0.2 | t
+(1 row)
+
+ALTER TABLE vtype
+      ALTER b TYPE text USING b::text,
+      ALTER c TYPE text USING c::text;
+NOTICE:  rewriting table vtype for reason 4
+SELECT * FROM vtype;
+ a |  b  |  c   
+---+-----+------
+ 1 | 0.2 | true
+(1 row)
+
+-- also check the case that doesn't rewrite the table
+CREATE TABLE vtype2 (a int);
+INSERT INTO vtype2 VALUES (1);
+ALTER TABLE vtype2 ADD COLUMN b varchar(10) DEFAULT 'xxx';
+ALTER TABLE vtype2 ALTER COLUMN b SET DEFAULT 'yyy';
+INSERT INTO vtype2 VALUES (2);
+ALTER TABLE vtype2 ALTER COLUMN b TYPE varchar(20) USING b::varchar(20);
+SELECT * FROM vtype2;
+ a |  b  
+---+-----
+ 1 | xxx
+ 2 | yyy
+(2 rows)
+
 -- cleanup
+DROP TABLE vtype;
+DROP TABLE vtype2;
 DROP TABLE follower;
 DROP TABLE leader;
 DROP FUNCTION test_trigger();
index 344b5841d7343b9b562669b3af0db71c3699f80d..0f65a79c7fd391a9e3e64ee1fff74c54ef684f60 100644 (file)
@@ -481,7 +481,33 @@ ALTER TABLE leader ADD c int;
 ALTER TABLE leader DROP c;
 DELETE FROM leader;
 
+-- check that ALTER TABLE ... ALTER TYPE does the right thing
+
+CREATE TABLE vtype( a integer);
+INSERT INTO vtype VALUES (1);
+ALTER TABLE vtype ADD COLUMN b DOUBLE PRECISION DEFAULT 0.2;
+ALTER TABLE vtype ADD COLUMN c BOOLEAN DEFAULT true;
+SELECT * FROM vtype;
+ALTER TABLE vtype
+      ALTER b TYPE text USING b::text,
+      ALTER c TYPE text USING c::text;
+SELECT * FROM vtype;
+
+-- also check the case that doesn't rewrite the table
+
+CREATE TABLE vtype2 (a int);
+INSERT INTO vtype2 VALUES (1);
+ALTER TABLE vtype2 ADD COLUMN b varchar(10) DEFAULT 'xxx';
+ALTER TABLE vtype2 ALTER COLUMN b SET DEFAULT 'yyy';
+INSERT INTO vtype2 VALUES (2);
+
+ALTER TABLE vtype2 ALTER COLUMN b TYPE varchar(20) USING b::varchar(20);
+SELECT * FROM vtype2;
+
+
 -- cleanup
+DROP TABLE vtype;
+DROP TABLE vtype2;
 DROP TABLE follower;
 DROP TABLE leader;
 DROP FUNCTION test_trigger();