On Mon, Feb 14, 2011 at 04:06:59PM -0500, Stephen Frost wrote:
> * Noah Misch (n...@leadboat.com) wrote:
> > On Mon, Feb 14, 2011 at 01:12:21PM -0500, Stephen Frost wrote:
> > > In ATColumnChangeSetWorkLevel(), I'm really not a huge fan of using a
> > > passed-in argument to move through a list with..  I'd suggest using a
> > > local variable that is set from what's passed in.  I do see that's
> > > inheirited, but still, you've pretty much redefined that entire
> > > function anyway..
> > 
> > Could do that.  However, the function would reference the original argument 
> > just
> > once, to make that copy.  I'm not seeing a win.
> 
> Perhaps I'm just deficient in this area, but I think of arguments,
> unless specifically intended otherwise, to be 'read-only' and based on
> that assumption, seeing it come up as an lvalue hits me as wrong.
> 
> I'm happy enough to let someone else decide if they agree with me or you
> though. :)

Same here.  If there's a general project tendency either way, I'll comply.  I
haven't noticed one, but I haven't been looking.


I've attached a new version of the patch that attempts to flesh out the comments
based on your feedback.  Does it improve things?

> > The validity of this optimization does not
> > rely on any user-settable property of a data type, but it does lean heavily 
> > on
> > the execution semantics of specific nodes.
> 
> After thinking through this and diving into coerce_to_target_type() a
> bit, I'm finally coming to understand how this is working.  The gist of
> it, if I follow correctly, is that if the planner doesn't think we have
> to do anything but copy this value to make it the new data type, then
> we're good to go.  That makes sense, when you think about it, but boy
> does it go the long way around to get there.

Essentially.  The planner is not yet involved.  We're looking at an expression
tree after parse analysis, before planning.

> Essentially, coerce_to_target_type() is returning an expression tree and
> ATColumnChangeSetWorkLevel() is checking to see if that expression tree
> is "copy the value".  Maybe this is a bit much, but if
> coerce_to_target_type() knows the expression given to it is a
> straight-up copy, perhaps it could pass that information along in an
> easier to use format than an expression tree?  That would obviate the
> need for ATColumnChangeSetWorkLevel() entirely, if I understand
> correctly.

PostgreSQL has a strong tradition of passing around expression trees and walking
them to (re-)discover facts.  See the various clauses.h functions.  Also, when
you have a USING clause, coerce_to_target_type() doesn't know the whole picture.
We could teach it to discover the whole picture, but that would amount to a very
similar tree walk in a different place.

> Of course, coerce_to_target_type() is used by lots of other places,
> almost all of which probably have to have an expression tree to stuff
> into a plan, so maybe a simpler function could be defined which operates
> at the level that ATColumnChangeSetWorkLevel() needs?

Offhand, I can't think of any concrete implementation along those lines that
would simplify the overall task.  Did you have something more specific in mind?

> Or perhaps other
> places would benefit from knowing that a given conversion is an actual
> no-op rather than copying the value?

RelabelType itself does not cause a copy; the existing datum passes through.  In
the case of ALTER TABLE, we do eventually copy the datum via heap_form_tuple.

There may be other places that would benefit from similar analysis.  For that
reason, I originally had ATColumnChangeSetWorkLevel() in parse_coerce.c with the
name GetCoerceExemptions().  I'm not aware of any specific applications, though.
For now it seemed like premature abstraction.

Thanks again,
nm
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 452ced6..466be25 100644
diff --git a/src/backend/commands/index 1db42d0..ab0bcda 100644
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 71,76 ****
--- 71,77 ----
  #include "storage/smgr.h"
  #include "utils/acl.h"
  #include "utils/builtins.h"
+ #include "utils/datum.h"
  #include "utils/fmgroids.h"
  #include "utils/inval.h"
  #include "utils/lsyscache.h"
***************
*** 142,147 **** typedef struct AlteredTableInfo
--- 143,149 ----
        List       *newvals;            /* List of NewColumnValue */
        bool            new_notnull;    /* T if we added new NOT NULL 
constraints */
        bool            rewrite;                /* T if we a rewrite is forced 
*/
+       bool            verify;                 /* T if we shall verify 
constraints */
        Oid                     newTableSpace;  /* new tablespace; 0 means no 
change */
        /* Objects to rebuild after completing ALTER TYPE operations */
        List       *changedConstraintOids;      /* OIDs of constraints to 
rebuild */
***************
*** 336,342 **** static void ATPrepAlterColumnType(List **wqueue,
                                          AlteredTableInfo *tab, Relation rel,
                                          bool recurse, bool recursing,
                                          AlterTableCmd *cmd, LOCKMODE 
lockmode);
! static bool ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno);
  static void ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
                                          const char *colName, TypeName 
*typeName, LOCKMODE lockmode);
  static void ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, 
LOCKMODE lockmode);
--- 338,345 ----
                                          AlteredTableInfo *tab, Relation rel,
                                          bool recurse, bool recursing,
                                          AlterTableCmd *cmd, LOCKMODE 
lockmode);
! static void ATColumnChangeSetWorkLevel(AlteredTableInfo *tab,
!                                                  Node *expr, AttrNumber 
varattno);
  static void ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
                                          const char *colName, TypeName 
*typeName, LOCKMODE lockmode);
  static void ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, 
LOCKMODE lockmode);
***************
*** 3313,3319 **** ATRewriteTables(List **wqueue, LOCKMODE lockmode)
                         * Test the current data within the table against new 
constraints
                         * generated by ALTER TABLE commands, but don't rebuild 
data.
                         */
!                       if (tab->constraints != NIL || tab->new_notnull)
                                ATRewriteTable(tab, InvalidOid, lockmode);
  
                        /*
--- 3316,3322 ----
                         * Test the current data within the table against new 
constraints
                         * generated by ALTER TABLE commands, but don't rebuild 
data.
                         */
!                       if (tab->verify)
                                ATRewriteTable(tab, InvalidOid, lockmode);
  
                        /*
***************
*** 3387,3393 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, 
LOCKMODE lockmode)
        Relation        newrel;
        TupleDesc       oldTupDesc;
        TupleDesc       newTupDesc;
-       bool            needscan = false;
        List       *notnull_attrs;
        int                     i;
        ListCell   *l;
--- 3390,3395 ----
***************
*** 3446,3452 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, 
LOCKMODE lockmode)
                switch (con->contype)
                {
                        case CONSTR_CHECK:
-                               needscan = true;
                                con->qualstate = (List *)
                                        ExecPrepareExpr((Expr *) con->qual, 
estate);
                                break;
--- 3448,3453 ----
***************
*** 3481,3491 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, 
LOCKMODE lockmode)
                                !newTupDesc->attrs[i]->attisdropped)
                                notnull_attrs = lappend_int(notnull_attrs, i);
                }
-               if (notnull_attrs)
-                       needscan = true;
        }
  
-       if (newrel || needscan)
        {
                ExprContext *econtext;
                Datum      *values;
--- 3482,3489 ----
***************
*** 3549,3558 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, 
LOCKMODE lockmode)
  
                while ((tuple = heap_getnext(scan, ForwardScanDirection)) != 
NULL)
                {
!                       if (tab->rewrite)
!                       {
!                               Oid                     tupOid = InvalidOid;
  
                                /* Extract data from old tuple */
                                heap_deform_tuple(tuple, oldTupDesc, values, 
isnull);
                                if (oldTupDesc->tdhasoid)
--- 3547,3560 ----
  
                while ((tuple = heap_getnext(scan, ForwardScanDirection)) != 
NULL)
                {
!                       Oid                     tupOid = InvalidOid;
  
+                       if (newrel
+ #ifdef USE_ASSERT_CHECKING
+                               || assert_enabled
+ #endif
+                               )
+                       {
                                /* Extract data from old tuple */
                                heap_deform_tuple(tuple, oldTupDesc, values, 
isnull);
                                if (oldTupDesc->tdhasoid)
***************
*** 3561,3570 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, 
LOCKMODE lockmode)
                                /* Set dropped attributes to null in new tuple 
*/
                                foreach(lc, dropped_attrs)
                                        isnull[lfirst_int(lc)] = true;
  
                                /*
                                 * Process supplied expressions to replace 
selected columns.
!                                * Expression inputs come from the old tuple.
                                 */
                                ExecStoreTuple(tuple, oldslot, InvalidBuffer, 
false);
                                econtext->ecxt_scantuple = oldslot;
--- 3563,3577 ----
                                /* Set dropped attributes to null in new tuple 
*/
                                foreach(lc, dropped_attrs)
                                        isnull[lfirst_int(lc)] = true;
+                       }
  
+                       if (tab->newvals != NIL)
+                       {
                                /*
                                 * Process supplied expressions to replace 
selected columns.
!                                * Expression inputs come from the old tuple.  
If !newrel, we've
!                                * proven that no expression will change a 
tuple value, but they
!                                * may throw errors.
                                 */
                                ExecStoreTuple(tuple, oldslot, InvalidBuffer, 
false);
                                econtext->ecxt_scantuple = oldslot;
***************
*** 3577,3584 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, 
LOCKMODE lockmode)
--- 3584,3618 ----
                                                                                
                                  econtext,
                                                                                
                         &isnull[ex->attnum - 1],
                                                                                
                                  NULL);
+ 
+ #ifdef USE_ASSERT_CHECKING
+                                       if (assert_enabled)
+                                       {
+                                               Datum           oldval = 
values[ex->attnum - 1];
+                                               bool            oldisnull = 
isnull[ex->attnum - 1];
+                                               Form_pg_attribute f = 
newTupDesc->attrs[ex->attnum - 1];
+ 
+                                               if (f->attbyval && f->attlen == 
-1)
+                                                       oldval = 
PointerGetDatum(PG_DETOAST_DATUM(oldval));
+ 
+                                               /*
+                                                * We don't detect the gross 
error of !newrel when the
+                                                * typlen actually changed.  
attbyval could differ in
+                                                * theory, but we assume it 
does not.
+                                                */
+                                               Assert(newrel ||
+                                                          (isnull[ex->attnum - 
1] == oldisnull
+                                                               && (oldisnull ||
+                                                                       
datumIsEqual(oldval,
+                                                                               
                 values[ex->attnum - 1],
+                                                                               
                 f->attbyval, f->attlen))));
+                                       }
+ #endif
                                }
+                       }
  
+                       if (newrel)
+                       {
                                /*
                                 * Form the new tuple. Note that we don't 
explicitly pfree it,
                                 * since the per-tuple memory context will be 
reset shortly.
***************
*** 4357,4362 **** ATExecAddColumn(AlteredTableInfo *tab, Relation rel,
--- 4391,4397 ----
                 * anything.)
                 */
                tab->new_notnull |= colDef->is_not_null;
+               tab->verify |= tab->new_notnull;
        }
  
        /*
***************
*** 4553,4558 **** ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
--- 4588,4594 ----
  
                /* Tell Phase 3 it needs to test the constraint */
                tab->new_notnull = true;
+               tab->verify = true;
        }
  
        heap_close(attr_rel, RowExclusiveLock);
***************
*** 5270,5275 **** ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, 
Relation rel,
--- 5306,5312 ----
                newcon->qual = (Node *) make_ands_implicit((Expr *) ccon->expr);
  
                tab->constraints = lappend(tab->constraints, newcon);
+               tab->verify = true;
  
                /* Save the actually assigned name if it was defaulted */
                if (constr->conname == NULL)
***************
*** 5618,5623 **** ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation 
rel,
--- 5655,5664 ----
                newcon->qual = (Node *) fkconstraint;
  
                tab->constraints = lappend(tab->constraints, newcon);
+               /*
+                * No need to set tab->verify; foreign key validation is a 
distinct
+                * aspect of Phase 3.
+                */
        }
  
        /*
***************
*** 6581,6588 **** ATPrepAlterColumnType(List **wqueue,
                newval->expr = (Expr *) transform;
  
                tab->newvals = lappend(tab->newvals, newval);
!               if (ATColumnChangeRequiresRewrite(transform, attnum))
!                       tab->rewrite = true;
        }
        else if (tab->relkind == RELKIND_FOREIGN_TABLE)
        {
--- 6622,6628 ----
                newval->expr = (Expr *) transform;
  
                tab->newvals = lappend(tab->newvals, newval);
!               ATColumnChangeSetWorkLevel(tab, transform, attnum);
        }
        else if (tab->relkind == RELKIND_FOREIGN_TABLE)
        {
***************
*** 6623,6647 **** ATPrepAlterColumnType(List **wqueue,
  }
  
  /*
!  * When the data type of a column is changed, a rewrite might not be require
!  * if the data type is being changed to its current type, or more 
interestingly
!  * to a type to which it is binary coercible.  But we must check carefully 
that
!  * the USING clause isn't trying to insert some other value.
   */
! static bool
! ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno)
  {
        Assert(expr != NULL);
  
!       for (;;)
        {
!               /* only one varno, so no need to check that */
                if (IsA(expr, Var) && ((Var *) expr)->varattno == varattno)
!                       return false;
                else if (IsA(expr, RelabelType))
                        expr = (Node *) ((RelabelType *) expr)->arg;
                else
!                       return true;
        }
  }
  
--- 6663,6722 ----
  }
  
  /*
!  * Decide how much phase-3 per-tuple work this column type change entails, 
based
!  * on its transformation expression tree.  There are three possibilities:
!  *
!  *  1. Rewrite; always sufficient.  Example: int2 -> int4.
!  *  2. Verification scan.  Example: text -> constrained domain over text.
!  *  3. No per-tuple work.  Example: varchar(N) -> text.
!  *
!  * To certify #2 or #3 as sufficient, the transformation expression tree must
!  * consist solely of nodes from this list:
!  *
!  *    A. Var with the varattno of the column under alteration.  Allows #2 or 
#3.
!  *    B. RelabelType.  Allows #2 or #3.
!  *    C. CoerceToDomain.  Allows #2; allows #3 when GetDomainConstraints() == 
NIL.
!  *
!  * (A) primordially conveys the current column data.  (B) and (C) are 
acceptable
!  * because they always pass a datum through unchanged; any other node having
!  * that property could potentially join this list.
   */
! static void
! ATColumnChangeSetWorkLevel(AlteredTableInfo *tab,
!                                                  Node *expr, AttrNumber 
varattno)
  {
        Assert(expr != NULL);
  
!       /*
!        * Walking an expression tree is normally a recursive operation.  Since 
all
!        * node types on our current whitelist have no more than one descendent 
node
!        * requiring assessment, the recursion degenerates to iteration.
!        *
!        * When tab->rewrite becomes true, no optimizations are possible, and 
we're
!        * done.  It may be set already from analysis of an earlier subcommand 
in
!        * the same ALTER TABLE operation, and that's fine.
!        */
!       while (!tab->rewrite)
        {
!               /*
!                * Non-matching varattno arises when an explicit USING clause 
references
!                * a different column.  Only one varno, so no need to check 
that.  When
!                * we reach this node, we're done, and some optimizations do 
apply.
!                */
                if (IsA(expr, Var) && ((Var *) expr)->varattno == varattno)
!                       break;
                else if (IsA(expr, RelabelType))
                        expr = (Node *) ((RelabelType *) expr)->arg;
+               else if (IsA(expr, CoerceToDomain))
+               {
+                       CoerceToDomain *d = (CoerceToDomain *) expr;
+ 
+                       if (GetDomainConstraints(d->resulttype) != NIL)
+                               tab->verify = true;
+                       expr = (Node *) d->arg;
+               }
                else
!                       tab->rewrite = true;
        }
  }
  
diff --git a/src/test/regress/GNUmakefiindex 4c8af0f..eeb9487 100644
diff --git a/src/test/regress/expecnew file mode 100644
index 0000000..248fb39
diff --git a/src/test/regress/parallel_schedule b/srindex 3b99e86..0420922 
100644
diff --git a/src/test/regress/serial_scheindex b348f0e..9954cf7 100644
diff --git a/src/test/regress/sql/big_anew file mode 100644
index 0000000..8302d87
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to