Add expression dependencies on composite type / whole row components.
authorAndres Freund <[email protected]>
Tue, 14 Mar 2017 03:22:09 +0000 (20:22 -0700)
committerAndres Freund <[email protected]>
Tue, 14 Mar 2017 06:31:53 +0000 (23:31 -0700)
This allows, in a later commit, to have fewer checks during expression
evaluation.

Author: Andres Freund
Reviewed-By: Peter Eisentraut
Discussion: https://postgr.es/m/20161206034955[email protected]

src/backend/catalog/dependency.c
src/test/regress/expected/create_view.out
src/test/regress/expected/rangefuncs.out
src/test/regress/sql/create_view.sql
src/test/regress/sql/rangefuncs.sql

index fc088b216589ccbb70f3201a128d59e8345b913c..304209dd7c464fe67b48808e66eaa8f3d4813517 100644 (file)
@@ -206,6 +206,9 @@ static bool object_address_present_add_flags(const ObjectAddress *object,
 static bool stack_address_present_add_flags(const ObjectAddress *object,
                                int flags,
                                ObjectAddressStack *stack);
+static void add_type_addresses(Oid typeid, bool include_components, ObjectAddresses *addrs);
+static void add_type_component_addresses(Oid typeid, ObjectAddresses *addrs);
+static void add_class_component_addresses(Oid relid, ObjectAddresses *addrs);
 static void DeleteInitPrivs(const ObjectAddress *object);
 
 
@@ -1364,10 +1367,8 @@ recordDependencyOnExpr(const ObjectAddress *depender,
  * whereas 'behavior' is used for everything else.
  *
  * NOTE: the caller should ensure that a whole-table dependency on the
- * specified relation is created separately, if one is needed.  In particular,
- * a whole-row Var "relation.*" will not cause this routine to emit any
- * dependency item.  This is appropriate behavior for subexpressions of an
- * ordinary query, so other cases need to cope as necessary.
+ * specified relation is created separately, if one is needed.  E.g. SELECT
+ * FROM tbl will not cause this routine to emit any dependency items.
  */
 void
 recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
@@ -1484,19 +1485,23 @@ find_expr_references_walker(Node *node,
            elog(ERROR, "invalid varno %d", var->varno);
        rte = rt_fetch(var->varno, rtable);
 
-       /*
-        * A whole-row Var references no specific columns, so adds no new
-        * dependency.  (We assume that there is a whole-table dependency
-        * arising from each underlying rangetable entry.  While we could
-        * record such a dependency when finding a whole-row Var that
-        * references a relation directly, it's quite unclear how to extend
-        * that to whole-row Vars for JOINs, so it seems better to leave the
-        * responsibility with the range table.  Note that this poses some
-        * risks for identifying dependencies of stand-alone expressions:
-        * whole-table references may need to be created separately.)
-        */
        if (var->varattno == InvalidAttrNumber)
-           return false;
+       {
+           /*
+            * A whole-row Var essentially references all current columns, so
+            * add dependencies for them - that allow adding new columns to
+            * the type, but not removing or altering the type of existing
+            * columns.
+            *
+            * That does not obviate the need for a whole-table dependency
+            * arising from each underlying rangetable entry - this would
+            * e.g. not do the right thing for column-less tables.  Note that
+            * this requires some care for identifying dependencies of
+            * stand-alone expressions: whole-table references may need to be
+            * created separately.
+            */
+           add_class_component_addresses(rte->relid, context->addrs);
+       }
        if (rte->rtekind == RTE_RELATION)
        {
            /* If it's a plain relation, reference this column */
@@ -1529,8 +1534,7 @@ find_expr_references_walker(Node *node,
        Oid         objoid;
 
        /* A constant must depend on the constant's datatype */
-       add_object_address(OCLASS_TYPE, con->consttype, 0,
-                          context->addrs);
+       add_type_addresses(con->consttype, true, context->addrs);
 
        /*
         * We must also depend on the constant's collation: it could be
@@ -1580,8 +1584,7 @@ find_expr_references_walker(Node *node,
                    objoid = DatumGetObjectId(con->constvalue);
                    if (SearchSysCacheExists1(TYPEOID,
                                              ObjectIdGetDatum(objoid)))
-                       add_object_address(OCLASS_TYPE, objoid, 0,
-                                          context->addrs);
+                       add_type_addresses(objoid, false, context->addrs);
                    break;
                case REGCONFIGOID:
                    objoid = DatumGetObjectId(con->constvalue);
@@ -1625,8 +1628,7 @@ find_expr_references_walker(Node *node,
        Param      *param = (Param *) node;
 
        /* A parameter must depend on the parameter's datatype */
-       add_object_address(OCLASS_TYPE, param->paramtype, 0,
-                          context->addrs);
+       add_type_addresses(param->paramtype, true, context->addrs);
        /* and its collation, just as for Consts */
        if (OidIsValid(param->paramcollid) &&
            param->paramcollid != DEFAULT_COLLATION_OID)
@@ -1639,6 +1641,9 @@ find_expr_references_walker(Node *node,
 
        add_object_address(OCLASS_PROC, funcexpr->funcid, 0,
                           context->addrs);
+       /* dependency on type itself already exists via function */
+       add_type_component_addresses(funcexpr->funcresulttype, context->addrs);
+
        /* fall through to examine arguments */
    }
    else if (IsA(node, OpExpr))
@@ -1647,6 +1652,9 @@ find_expr_references_walker(Node *node,
 
        add_object_address(OCLASS_OPERATOR, opexpr->opno, 0,
                           context->addrs);
+       /* dependency on type itself already exists via function */
+       add_type_component_addresses(opexpr->opresulttype, context->addrs);
+
        /* fall through to examine arguments */
    }
    else if (IsA(node, DistinctExpr))
@@ -1655,6 +1663,13 @@ find_expr_references_walker(Node *node,
 
        add_object_address(OCLASS_OPERATOR, distinctexpr->opno, 0,
                           context->addrs);
+       /*
+        * Dependency on type itself already exists via function. Be paranoid
+        * and add deps to return type components (unlikely to matter due to
+        * return type, but ...)
+        */
+       add_type_component_addresses(distinctexpr->opresulttype,
+                                    context->addrs);
        /* fall through to examine arguments */
    }
    else if (IsA(node, NullIfExpr))
@@ -1663,6 +1678,7 @@ find_expr_references_walker(Node *node,
 
        add_object_address(OCLASS_OPERATOR, nullifexpr->opno, 0,
                           context->addrs);
+       /* can't add new type dependencies */
        /* fall through to examine arguments */
    }
    else if (IsA(node, ScalarArrayOpExpr))
@@ -1679,6 +1695,7 @@ find_expr_references_walker(Node *node,
 
        add_object_address(OCLASS_PROC, aggref->aggfnoid, 0,
                           context->addrs);
+       add_type_component_addresses(aggref->aggtype, context->addrs);
        /* fall through to examine arguments */
    }
    else if (IsA(node, WindowFunc))
@@ -1687,6 +1704,7 @@ find_expr_references_walker(Node *node,
 
        add_object_address(OCLASS_PROC, wfunc->winfnoid, 0,
                           context->addrs);
+       add_type_component_addresses(wfunc->wintype, context->addrs);
        /* fall through to examine arguments */
    }
    else if (IsA(node, SubPlan))
@@ -1699,8 +1717,8 @@ find_expr_references_walker(Node *node,
        RelabelType *relab = (RelabelType *) node;
 
        /* since there is no function dependency, need to depend on type */
-       add_object_address(OCLASS_TYPE, relab->resulttype, 0,
-                          context->addrs);
+       add_type_addresses(relab->resulttype, false, context->addrs);
+
        /* the collation might not be referenced anywhere else, either */
        if (OidIsValid(relab->resultcollid) &&
            relab->resultcollid != DEFAULT_COLLATION_OID)
@@ -1712,8 +1730,7 @@ find_expr_references_walker(Node *node,
        CoerceViaIO *iocoerce = (CoerceViaIO *) node;
 
        /* since there is no exposed function, need to depend on type */
-       add_object_address(OCLASS_TYPE, iocoerce->resulttype, 0,
-                          context->addrs);
+       add_type_addresses(iocoerce->resulttype, true, context->addrs);
    }
    else if (IsA(node, ArrayCoerceExpr))
    {
@@ -1722,8 +1739,7 @@ find_expr_references_walker(Node *node,
        if (OidIsValid(acoerce->elemfuncid))
            add_object_address(OCLASS_PROC, acoerce->elemfuncid, 0,
                               context->addrs);
-       add_object_address(OCLASS_TYPE, acoerce->resulttype, 0,
-                          context->addrs);
+       add_type_addresses(acoerce->resulttype, true, context->addrs);
        /* fall through to examine arguments */
    }
    else if (IsA(node, ConvertRowtypeExpr))
@@ -1731,8 +1747,7 @@ find_expr_references_walker(Node *node,
        ConvertRowtypeExpr *cvt = (ConvertRowtypeExpr *) node;
 
        /* since there is no function dependency, need to depend on type */
-       add_object_address(OCLASS_TYPE, cvt->resulttype, 0,
-                          context->addrs);
+       add_type_addresses(cvt->resulttype, true, context->addrs);
    }
    else if (IsA(node, CollateExpr))
    {
@@ -1745,8 +1760,7 @@ find_expr_references_walker(Node *node,
    {
        RowExpr    *rowexpr = (RowExpr *) node;
 
-       add_object_address(OCLASS_TYPE, rowexpr->row_typeid, 0,
-                          context->addrs);
+       add_type_addresses(rowexpr->row_typeid, true, context->addrs);
    }
    else if (IsA(node, RowCompareExpr))
    {
@@ -1769,8 +1783,7 @@ find_expr_references_walker(Node *node,
    {
        CoerceToDomain *cd = (CoerceToDomain *) node;
 
-       add_object_address(OCLASS_TYPE, cd->resulttype, 0,
-                          context->addrs);
+       add_type_addresses(cd->resulttype, true, context->addrs);
    }
    else if (IsA(node, OnConflictExpr))
    {
@@ -1898,13 +1911,13 @@ find_expr_references_walker(Node *node,
 
        /*
         * Add refs for any datatypes and collations used in a column
-        * definition list for a RECORD function.  (For other cases, it should
-        * be enough to depend on the function itself.)
+        * definition list for a RECORD function.  Additional dependencies
+        * will possibly be added when recursing to the contained function
+        * expression.
         */
        foreach(ct, rtfunc->funccoltypes)
        {
-           add_object_address(OCLASS_TYPE, lfirst_oid(ct), 0,
-                              context->addrs);
+           add_type_addresses(lfirst_oid(ct), true, context->addrs);
        }
        foreach(ct, rtfunc->funccolcollations)
        {
@@ -2227,6 +2240,62 @@ object_address_present_add_flags(const ObjectAddress *object,
    return result;
 }
 
+/*
+ * Add ObjectAddresses entry for typeid and, if include_components = true and
+ * and the type is a composite type, for it's columns.
+ */
+static void
+add_type_addresses(Oid typeid, bool include_components, ObjectAddresses *addrs)
+{
+   add_object_address(OCLASS_TYPE, typeid, 0, addrs);
+
+   if (include_components)
+   {
+       add_type_component_addresses(typeid, addrs);
+   }
+}
+
+/*
+ * Add ObjectAddresses entry for the type's columns if it's a composite type.
+ *
+ * Besides being a helper for add_type_addresses it can make sense to add
+ * dependencies on the components if, as e.g. the case for a function return
+ * type, there already exists a dependency on the type, but not the typ's
+ * components.
+ */
+static void
+add_type_component_addresses(Oid typeid, ObjectAddresses *addrs)
+{
+   Oid typerelid = get_typ_typrelid(typeid);
+
+   if (typerelid != InvalidOid)
+   {
+       add_class_component_addresses(typerelid, addrs);
+   }
+}
+
+/*
+ * Add ObjectAddresses entries for the relation's columns.
+ */
+static void
+add_class_component_addresses(Oid relid, ObjectAddresses *addrs)
+{
+   Relation rel = relation_open(relid, NoLock);
+   TupleDesc tupDesc = RelationGetDescr(rel);
+   int i;
+
+   for (i = 0; i < tupDesc->natts; i++)
+   {
+       Form_pg_attribute att = tupDesc->attrs[i];
+
+       if (att->attisdropped)
+           continue;
+
+       add_object_address(OCLASS_CLASS, relid, att->attnum, addrs);
+   }
+   relation_close(rel, NoLock);
+}
+
 /*
  * Similar to above, except we search an ObjectAddressStack.
  */
index ce0c8cedf89f670cd18a4cedf1369dbdc8ecf123..379c070ca6f1dec6cb6c27c2ac88927582159dca 100644 (file)
@@ -1429,24 +1429,11 @@ select * from tt14v;
  foo | baz | quux
 (1 row)
 
--- this perhaps should be rejected, but it isn't:
+-- this will be rejected as the column is referenced in the view:
 alter table tt14t drop column f3;
--- f3 is still in the view but will read as nulls
-select pg_get_viewdef('tt14v', true);
-         pg_get_viewdef         
---------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
-    FROM tt14f() t(f1, f3, f4);
-(1 row)
-
-select * from tt14v;
- f1  | f3 |  f4  
------+----+------
- foo |    | quux
-(1 row)
-
+ERROR:  cannot drop table tt14t column f3 because other objects depend on it
+DETAIL:  view tt14v depends on table tt14t column f3
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
 -- check display of whole-row variables in some corner cases
 create type nestedcomposite as (x int8_tbl);
 create view tt15v as select row(i)::nestedcomposite from int8_tbl i;
index 526a4aed0a20c22384a2a1a9598daf308e46fc24..33b462126a68ed2d0023211da4d4123ef47b7aa7 100644 (file)
@@ -1899,9 +1899,13 @@ SELECT * FROM ROWS FROM(get_users(), generate_series(10,11)) WITH ORDINALITY;
  id2    |   2 | email2 |       12 | t       |              11 |          2
 (2 rows)
 
--- check that we can cope with post-parsing changes in rowtypes
 create temp view usersview as
 SELECT * FROM ROWS FROM(get_users(), generate_series(10,11)) WITH ORDINALITY;
+create temp view usersview2 as
+SELECT * FROM (SELECT get_users()) f;
+create temp view usersview3 as
+SELECT * FROM (SELECT users FROM users) f;
+-- check that rowtypes referenced in views can't be dropped / changed
 select * from usersview;
  userid | seq | email  | moredrop | enabled | generate_series | ordinality 
 --------+-----+--------+----------+---------+-----------------+------------
@@ -1909,27 +1913,79 @@ select * from usersview;
  id2    |   2 | email2 |       12 | t       |              11 |          2
 (2 rows)
 
+select * from usersview2;
+      get_users      
+---------------------
+ (id,1,email,11,t)
+ (id2,2,email2,12,t)
+(2 rows)
+
 alter table users drop column moredrop;
+ERROR:  cannot drop table users column moredrop because other objects depend on it
+DETAIL:  view usersview depends on table users column moredrop
+view usersview2 depends on table users column moredrop
+view usersview3 depends on table users column moredrop
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+alter table users alter column seq type numeric;
+ERROR:  cannot alter type of a column used by a view or rule
+DETAIL:  rule _RETURN on view usersview3 depends on column "seq"
 select * from usersview;
  userid | seq | email  | moredrop | enabled | generate_series | ordinality 
 --------+-----+--------+----------+---------+-----------------+------------
- id     |   1 | email  |          | t       |              10 |          1
- id2    |   2 | email2 |          | t       |              11 |          2
+ id     |   1 | email  |       11 | t       |              10 |          1
+ id2    |   2 | email2 |       12 | t       |              11 |          2
+(2 rows)
+
+select * from usersview2;
+      get_users      
+---------------------
+ (id,1,email,11,t)
+ (id2,2,email2,12,t)
 (2 rows)
 
+select * from usersview3;
+        users        
+---------------------
+ (id,1,email,11,t)
+ (id2,2,email2,12,t)
+(2 rows)
+
+-- check that column additions are handled properly
 alter table users add column junk text;
 select * from usersview;
  userid | seq | email  | moredrop | enabled | generate_series | ordinality 
 --------+-----+--------+----------+---------+-----------------+------------
- id     |   1 | email  |          | t       |              10 |          1
- id2    |   2 | email2 |          | t       |              11 |          2
+ id     |   1 | email  |       11 | t       |              10 |          1
+ id2    |   2 | email2 |       12 | t       |              11 |          2
 (2 rows)
 
-alter table users alter column seq type numeric;
-select * from usersview;  -- expect clean failure
-ERROR:  attribute 2 has wrong type
-DETAIL:  Table has type numeric, but query expects integer.
-drop view usersview;
+select * from usersview2;
+      get_users       
+----------------------
+ (id,1,email,11,t,)
+ (id2,2,email2,12,t,)
+(2 rows)
+
+select * from usersview3;
+        users         
+----------------------
+ (id,1,email,11,t,)
+ (id2,2,email2,12,t,)
+(2 rows)
+
+-- check that cascade is handled properly
+alter table users drop column moredrop CASCADE;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to view usersview
+drop cascades to view usersview2
+drop cascades to view usersview3
+-- should all be gone now
+\dv usersview*
+      List of relations
+ Schema | Name | Type | Owner 
+--------+------+------+-------
+(0 rows)
+
 drop function get_first_user();
 drop function get_users();
 drop table users;
index c27f1034e1384ac6341da943aa6dae2bb326c2ac..da4236766c05e2c4fa0cd8e138ed2eb761751a65 100644 (file)
@@ -483,13 +483,9 @@ create view tt14v as select t.* from tt14f() t;
 select pg_get_viewdef('tt14v', true);
 select * from tt14v;
 
--- this perhaps should be rejected, but it isn't:
+-- this will be rejected as the column is referenced in the view:
 alter table tt14t drop column f3;
 
--- f3 is still in the view but will read as nulls
-select pg_get_viewdef('tt14v', true);
-select * from tt14v;
-
 -- check display of whole-row variables in some corner cases
 
 create type nestedcomposite as (x int8_tbl);
index 09ac8fbdb4df216521a0434b41105a1ca8da3ec3..902457f5f128d41303311c3459be0c36f52feed9 100644 (file)
@@ -551,19 +551,35 @@ SELECT * FROM get_users() WITH ORDINALITY;   -- make sure ordinality copes
 SELECT * FROM ROWS FROM(generate_series(10,11), get_users()) WITH ORDINALITY;
 SELECT * FROM ROWS FROM(get_users(), generate_series(10,11)) WITH ORDINALITY;
 
--- check that we can cope with post-parsing changes in rowtypes
 create temp view usersview as
 SELECT * FROM ROWS FROM(get_users(), generate_series(10,11)) WITH ORDINALITY;
 
+create temp view usersview2 as
+SELECT * FROM (SELECT get_users()) f;
+
+create temp view usersview3 as
+SELECT * FROM (SELECT users FROM users) f;
+
+-- check that rowtypes referenced in views can't be dropped / changed
 select * from usersview;
+select * from usersview2;
 alter table users drop column moredrop;
+alter table users alter column seq type numeric;
 select * from usersview;
+select * from usersview2;
+select * from usersview3;
+
+-- check that column additions are handled properly
 alter table users add column junk text;
 select * from usersview;
-alter table users alter column seq type numeric;
-select * from usersview;  -- expect clean failure
+select * from usersview2;
+select * from usersview3;
+
+-- check that cascade is handled properly
+alter table users drop column moredrop CASCADE;
+-- should all be gone now
+\dv usersview*
 
-drop view usersview;
 drop function get_first_user();
 drop function get_users();
 drop table users;