]> granicus.if.org Git - postgresql/commitdiff
Fix handling of extended statistics during ALTER COLUMN TYPE.
authorTom Lane <tgl@sss.pgh.pa.us>
Sun, 14 May 2017 16:22:16 +0000 (12:22 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Sun, 14 May 2017 16:22:25 +0000 (12:22 -0400)
ALTER COLUMN TYPE on a column used by a statistics object fails since
commit 928c4de30, because the relevant switch in ATExecAlterColumnType
is unprepared for columns to have dependencies from OCLASS_STATISTIC_EXT
objects.

Although the existing types of extended statistics don't actually need us
to do any work for a column type change, it seems completely indefensible
that that assumption is hidden behind the failure of an unrelated module
to contain any code for the case.  Hence, create and call an API function
in statscmds.c where the assumption can be explained, and where we could
add code to deal with the problem when it inevitably becomes real.

Also, the reason this wasn't handled before, neither for extended stats
nor for the last half-dozen new OCLASS kinds :-(, is that the default:
in that switch suppresses compiler warnings, allowing people to miss the
need to consider it when adding an OCLASS.  We don't really need a default
because surely getObjectClass should only return valid values of the enum;
so remove it, and add the missed OCLASS entries where they should be.

Discussion: https://postgr.es/m/20170512221010.nglatgt5azzdxjlj@alvherre.pgsql

src/backend/commands/statscmds.c
src/backend/commands/tablecmds.c
src/include/commands/defrem.h
src/test/regress/expected/stats_ext.out
src/test/regress/sql/stats_ext.sql

index cab54c962c60ae0477d8ea6bb79943834ebaf101..94865b395b7db06ed18a871c7aa429950e9baa96 100644 (file)
@@ -378,3 +378,31 @@ RemoveStatisticsById(Oid statsOid)
 
        heap_close(relation, RowExclusiveLock);
 }
+
+/*
+ * Update a statistics object for ALTER COLUMN TYPE on a source column.
+ *
+ * This could throw an error if the type change can't be supported.
+ * If it can be supported, but the stats must be recomputed, a likely choice
+ * would be to set the relevant column(s) of the pg_statistic_ext tuple to
+ * null until the next ANALYZE.  (Note that the type change hasn't actually
+ * happened yet, so one option that's *not* on the table is to recompute
+ * immediately.)
+ */
+void
+UpdateStatisticsForTypeChange(Oid statsOid, Oid relationOid, int attnum,
+                                                         Oid oldColumnType, Oid newColumnType)
+{
+       /*
+        * Currently, we don't actually need to do anything here.  For both
+        * ndistinct and functional-dependencies stats, the on-disk representation
+        * is independent of the source column data types, and it is plausible to
+        * assume that the old statistic values will still be good for the new
+        * column contents.  (Obviously, if the ALTER COLUMN TYPE has a USING
+        * expression that substantially alters the semantic meaning of the column
+        * values, this assumption could fail.  But that seems like a corner case
+        * that doesn't justify zapping the stats in common cases.)
+        *
+        * Future types of extended stats will likely require us to work harder.
+        */
+}
index cdcb94929aff5a066c9363e24027bd6d8f70dc1c..b94db89b257d3eed9cf79962790aca12b73b2cbe 100644 (file)
@@ -9236,6 +9236,17 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
                                Assert(defaultexpr);
                                break;
 
+                       case OCLASS_STATISTIC_EXT:
+
+                               /*
+                                * Give the extended-stats machinery a chance to fix anything
+                                * that this column type change would break.
+                                */
+                               UpdateStatisticsForTypeChange(foundObject.objectId,
+                                                                                         RelationGetRelid(rel), attnum,
+                                                                                         attTup->atttypid, targettype);
+                               break;
+
                        case OCLASS_PROC:
                        case OCLASS_TYPE:
                        case OCLASS_CAST:
@@ -9246,6 +9257,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
                        case OCLASS_OPERATOR:
                        case OCLASS_OPCLASS:
                        case OCLASS_OPFAMILY:
+                       case OCLASS_AM:
                        case OCLASS_AMOP:
                        case OCLASS_AMPROC:
                        case OCLASS_SCHEMA:
@@ -9261,6 +9273,11 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
                        case OCLASS_USER_MAPPING:
                        case OCLASS_DEFACL:
                        case OCLASS_EXTENSION:
+                       case OCLASS_EVENT_TRIGGER:
+                       case OCLASS_PUBLICATION:
+                       case OCLASS_PUBLICATION_REL:
+                       case OCLASS_SUBSCRIPTION:
+                       case OCLASS_TRANSFORM:
 
                                /*
                                 * We don't expect any of these sorts of objects to depend on
@@ -9270,9 +9287,10 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
                                         getObjectDescription(&foundObject));
                                break;
 
-                       default:
-                               elog(ERROR, "unrecognized object class: %u",
-                                        foundObject.classId);
+                               /*
+                                * There's intentionally no default: case here; we want the
+                                * compiler to warn if a new OCLASS hasn't been handled above.
+                                */
                }
        }
 
index c323e81e6c5011e6d2b0bf0ffaf3f28904b51991..79f3be36e496dcadc8007d556042562b1e45ae39 100644 (file)
@@ -80,6 +80,9 @@ extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 /* commands/statscmds.c */
 extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
+extern void UpdateStatisticsForTypeChange(Oid statsOid,
+                                                         Oid relationOid, int attnum,
+                                                         Oid oldColumnType, Oid newColumnType);
 
 /* commands/aggregatecmds.c */
 extern ObjectAddress DefineAggregate(ParseState *pstate, List *name, List *args, bool oldstyle,
index 564521957f2f2faf4269e22534d2a5d991c9d59f..5fd1244bcb6c2797c962c938000825e757ef2af4 100644 (file)
@@ -479,4 +479,29 @@ EXPLAIN (COSTS OFF)
          Index Cond: ((a = 1) AND (b = '1'::text))
 (5 rows)
 
+-- check change of column type doesn't break it
+ALTER TABLE functional_dependencies ALTER COLUMN c TYPE numeric;
+EXPLAIN (COSTS OFF)
+ SELECT * FROM functional_dependencies WHERE a = 1 AND b = '1' AND c = 1;
+                    QUERY PLAN                     
+---------------------------------------------------
+ Bitmap Heap Scan on functional_dependencies
+   Recheck Cond: ((a = 1) AND (b = '1'::text))
+   Filter: (c = '1'::numeric)
+   ->  Bitmap Index Scan on fdeps_ab_idx
+         Index Cond: ((a = 1) AND (b = '1'::text))
+(5 rows)
+
+ANALYZE functional_dependencies;
+EXPLAIN (COSTS OFF)
+ SELECT * FROM functional_dependencies WHERE a = 1 AND b = '1' AND c = 1;
+                    QUERY PLAN                     
+---------------------------------------------------
+ Bitmap Heap Scan on functional_dependencies
+   Recheck Cond: ((a = 1) AND (b = '1'::text))
+   Filter: (c = '1'::numeric)
+   ->  Bitmap Index Scan on fdeps_ab_idx
+         Index Cond: ((a = 1) AND (b = '1'::text))
+(5 rows)
+
 RESET random_page_cost;
index a9dda051c22e01ee5920b6c3782a1f142abf3aa1..4c2f514b933aac91ef8ea4218c8c2d46a4a8d37b 100644 (file)
@@ -266,6 +266,17 @@ ANALYZE functional_dependencies;
 EXPLAIN (COSTS OFF)
  SELECT * FROM functional_dependencies WHERE a = 1 AND b = '1';
 
+EXPLAIN (COSTS OFF)
+ SELECT * FROM functional_dependencies WHERE a = 1 AND b = '1' AND c = 1;
+
+-- check change of column type doesn't break it
+ALTER TABLE functional_dependencies ALTER COLUMN c TYPE numeric;
+
+EXPLAIN (COSTS OFF)
+ SELECT * FROM functional_dependencies WHERE a = 1 AND b = '1' AND c = 1;
+
+ANALYZE functional_dependencies;
+
 EXPLAIN (COSTS OFF)
  SELECT * FROM functional_dependencies WHERE a = 1 AND b = '1' AND c = 1;