Make update lists like 'UPDATE tab SET foo[1] = bar, foo[3] = baz'
authorTom Lane
Sat, 22 Jul 2000 06:19:04 +0000 (06:19 +0000)
committerTom Lane
Sat, 22 Jul 2000 06:19:04 +0000 (06:19 +0000)
work as expected.  THe underlying implementation is essentially
'SET foo = array_set(foo, 1, bar)', so we have to turn the items
into nested invocations of array_set() to make it work correctly.
Side effect: we now complain about 'UPDATE tab SET foo = bar, foo = baz'
which is illegal per SQL92 but we didn't detect it before.

src/backend/optimizer/prep/preptlist.c

index 22f06bb61a94f4ddbbcd2985af50cda9aa7334f4..a782203cd9b95595f4913c5fe30a7eb5de0072f4 100644 (file)
@@ -15,7 +15,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/optimizer/prep/preptlist.c,v 1.36 2000/04/12 17:15:23 momjian Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/optimizer/prep/preptlist.c,v 1.37 2000/07/22 06:19:04 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -32,6 +32,9 @@
 
 static List *expand_targetlist(List *tlist, int command_type,
                  Index result_relation, List *range_table);
+static TargetEntry *process_matched_tle(TargetEntry *src_tle,
+                                       TargetEntry *prior_tle,
+                                       int attrno);
 
 
 /*
@@ -119,8 +122,9 @@ expand_targetlist(List *tlist, int command_type,
    List       *temp;
 
    /*
-    * Keep a map of which tlist items we have transferred to new list. +1
-    * here keeps palloc from complaining if old_tlist_len=0.
+    * Keep a map of which tlist items we have transferred to new list.
+    *
+    * +1 here just keeps palloc from complaining if old_tlist_len==0.
     */
    tlistentry_used = (bool *) palloc((old_tlist_len + 1) * sizeof(bool));
    memset(tlistentry_used, 0, (old_tlist_len + 1) * sizeof(bool));
@@ -141,6 +145,7 @@ expand_targetlist(List *tlist, int command_type,
 
        /*
         * We match targetlist entries to attributes using the resname.
+        * Junk attributes are not candidates to be matched.
         */
        old_tlist_index = 0;
        foreach(temp, tlist)
@@ -149,26 +154,11 @@ expand_targetlist(List *tlist, int command_type,
            Resdom     *resdom = old_tle->resdom;
 
            if (!tlistentry_used[old_tlist_index] &&
-               strcmp(resdom->resname, attrname) == 0 &&
-               !resdom->resjunk)
+               !resdom->resjunk &&
+               strcmp(resdom->resname, attrname) == 0)
            {
-
-               /*
-                * We can recycle the old TLE+resdom if right resno; else
-                * make a new one to avoid modifying the old tlist
-                * structure. (Is preserving old tlist actually
-                * necessary?)
-                */
-               if (resdom->resno == attrno)
-                   new_tle = old_tle;
-               else
-               {
-                   resdom = (Resdom *) copyObject((Node *) resdom);
-                   resdom->resno = attrno;
-                   new_tle = makeTargetEntry(resdom, old_tle->expr);
-               }
+               new_tle = process_matched_tle(old_tle, new_tle, attrno);
                tlistentry_used[old_tlist_index] = true;
-               break;
            }
            old_tlist_index++;
        }
@@ -192,22 +182,15 @@ expand_targetlist(List *tlist, int command_type,
            {
                case CMD_INSERT:
                    {
-#ifdef _DROP_COLUMN_HACK__
-                       Datum       typedefault;
-
-#else
                        Datum       typedefault = get_typdefault(atttype);
-
-#endif  /* _DROP_COLUMN_HACK__ */
                        int         typlen;
                        Const      *temp_const;
 
 #ifdef _DROP_COLUMN_HACK__
                        if (COLUMN_IS_DROPPED(att_tup))
                            typedefault = PointerGetDatum(NULL);
-                       else
-                           typedefault = get_typdefault(atttype);
 #endif  /* _DROP_COLUMN_HACK__ */
+
                        if (typedefault == PointerGetDatum(NULL))
                            typlen = 0;
                        else
@@ -247,11 +230,9 @@ expand_targetlist(List *tlist, int command_type,
                        Var        *temp_var;
 
 #ifdef _DROP_COLUMN_HACK__
-                       Node       *temp_node = (Node *) NULL;
-
                        if (COLUMN_IS_DROPPED(att_tup))
                        {
-                           temp_node = (Node *) makeConst(atttype, 0,
+                           temp_var = (Var *) makeConst(atttype, 0,
                                                   PointerGetDatum(NULL),
                                                           true,
                                                           false,
@@ -260,25 +241,20 @@ expand_targetlist(List *tlist, int command_type,
                        }
                        else
 #endif  /* _DROP_COLUMN_HACK__ */
-                           temp_var = makeVar(result_relation, attrno, atttype,
-                                              atttypmod, 0);
-#ifdef _DROP_COLUMN_HACK__
-                       if (!temp_node)
-                           temp_node = (Node *) temp_var;
-#endif  /* _DROP_COLUMN_HACK__ */
+                           temp_var = makeVar(result_relation,
+                                              attrno,
+                                              atttype,
+                                              atttypmod,
+                                              0);
 
                        new_tle = makeTargetEntry(makeResdom(attrno,
                                                             atttype,
                                                             atttypmod,
-                                                      pstrdup(attrname),
+                                                            pstrdup(attrname),
                                                             0,
                                                             (Oid) 0,
                                                             false),
-#ifdef _DROP_COLUMN_HACK__
-                                                 temp_node);
-#else
                                                  (Node *) temp_var);
-#endif  /* _DROP_COLUMN_HACK__ */
                        break;
                    }
                default:
@@ -304,13 +280,20 @@ expand_targetlist(List *tlist, int command_type,
 
        if (!tlistentry_used[old_tlist_index])
        {
-           Resdom     *resdom;
+           Resdom     *resdom = old_tle->resdom;
 
-           resdom = (Resdom *) copyObject((Node *) old_tle->resdom);
-           resdom->resno = attrno++;
-           resdom->resjunk = true;
-           new_tlist = lappend(new_tlist,
-                               makeTargetEntry(resdom, old_tle->expr));
+           if (! resdom->resjunk)
+               elog(ERROR, "Unexpected assignment to attribute \"%s\"",
+                    resdom->resname);
+           /* Get the resno right, but don't copy unnecessarily */
+           if (resdom->resno != attrno)
+           {
+               resdom = (Resdom *) copyObject((Node *) resdom);
+               resdom->resno = attrno;
+               old_tle = makeTargetEntry(resdom, old_tle->expr);
+           }
+           new_tlist = lappend(new_tlist, old_tle);
+           attrno++;
        }
        old_tlist_index++;
    }
@@ -321,3 +304,72 @@ expand_targetlist(List *tlist, int command_type,
 
    return new_tlist;
 }
+
+
+/*
+ * Convert a matched TLE from the original tlist into a correct new TLE.
+ *
+ * This routine checks for multiple assignments to the same target attribute,
+ * such as "UPDATE table SET foo = 42, foo = 43".  This is OK only if they
+ * are array assignments, ie, "UPDATE table SET foo[2] = 42, foo[4] = 43".
+ * If so, we need to merge the operations into a single assignment op.
+ * Essentially, the expression we want to produce in this case is like
+ *     foo = array_set(array_set(foo, 2, 42), 4, 43)
+ */
+static TargetEntry *process_matched_tle(TargetEntry *src_tle,
+                                       TargetEntry *prior_tle,
+                                       int attrno)
+{
+   Resdom     *resdom = src_tle->resdom;
+   Node       *priorbottom;
+   ArrayRef   *newexpr;
+
+   if (prior_tle == NULL)
+   {
+       /*
+        * Normal case where this is the first assignment to the attribute.
+        *
+        * We can recycle the old TLE+resdom if right resno; else make a
+        * new one to avoid modifying the old tlist structure. (Is preserving
+        * old tlist actually necessary?  Not sure, be safe.)
+        */
+       if (resdom->resno == attrno)
+           return src_tle;
+       resdom = (Resdom *) copyObject((Node *) resdom);
+       resdom->resno = attrno;
+       return makeTargetEntry(resdom, src_tle->expr);
+   }
+
+   /*
+    * Multiple assignments to same attribute.  Allow only if all are
+    * array-assign operators with same bottom array object.
+    */
+   if (src_tle->expr == NULL || !IsA(src_tle->expr, ArrayRef) ||
+       ((ArrayRef *) src_tle->expr)->refassgnexpr == NULL ||
+       prior_tle->expr == NULL || !IsA(prior_tle->expr, ArrayRef) ||
+       ((ArrayRef *) prior_tle->expr)->refassgnexpr == NULL ||
+       ((ArrayRef *) src_tle->expr)->refelemtype !=
+       ((ArrayRef *) prior_tle->expr)->refelemtype)
+       elog(ERROR, "Multiple assignments to same attribute \"%s\"",
+            resdom->resname);
+   /*
+    * Prior TLE could be a nest of ArrayRefs if we do this more than once.
+    */
+   priorbottom = ((ArrayRef *) prior_tle->expr)->refexpr;
+   while (priorbottom != NULL && IsA(priorbottom, ArrayRef) &&
+          ((ArrayRef *) priorbottom)->refassgnexpr != NULL)
+       priorbottom = ((ArrayRef *) priorbottom)->refexpr;
+   if (! equal(priorbottom, ((ArrayRef *) src_tle->expr)->refexpr))
+       elog(ERROR, "Multiple assignments to same attribute \"%s\"",
+            resdom->resname);
+   /*
+    * Looks OK to nest 'em.
+    */
+   newexpr = makeNode(ArrayRef);
+   memcpy(newexpr, src_tle->expr, sizeof(ArrayRef));
+   newexpr->refexpr = prior_tle->expr;
+
+   resdom = (Resdom *) copyObject((Node *) resdom);
+   resdom->resno = attrno;
+   return makeTargetEntry(resdom, (Node *) newexpr);
+}