Hi all,

Sorry for the delay.

Please find attached latest version of UPDATE (*) patch.The patch
implements review comments and Tom's gripe about touching
transformTargetList is addressed now. I have added regression tests and
simplified parser representation a bit.

Regards,

Atri
diff --git a/doc/src/sgml/ref/update.sgml b/doc/src/sgml/ref/update.sgml
index 35b0699..1f68bdf 100644
--- a/doc/src/sgml/ref/update.sgml
+++ b/doc/src/sgml/ref/update.sgml
@@ -25,7 +25,9 @@ PostgreSQL documentation
 UPDATE [ ONLY ] <replaceable class="PARAMETER">table_name</replaceable> [ * ] [ [ AS ] <replaceable class="parameter">alias</replaceable> ]
     SET { <replaceable class="PARAMETER">column_name</replaceable> = { <replaceable class="PARAMETER">expression</replaceable> | DEFAULT } |
           ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) = ( { <replaceable class="PARAMETER">expression</replaceable> | DEFAULT } [, ...] ) |
-          ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) = ( <replaceable class="PARAMETER">sub-SELECT</replaceable> )
+          ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) = ( <replaceable class="PARAMETER">sub-SELECT</replaceable> ) |
+          ( <replaceable class="PARAMETER">*</replaceable> [, ...] ) = ( { <replaceable class="PARAMETER">expression</replaceable> | DEFAULT } [, ...] ) |
+          ( <replaceable class="PARAMETER">*</replaceable> [, ...] ) = ( <replaceable class="PARAMETER">sub-SELECT</replaceable> )
         } [, ...]
     [ FROM <replaceable class="PARAMETER">from_list</replaceable> ]
     [ WHERE <replaceable class="PARAMETER">condition</replaceable> | WHERE CURRENT OF <replaceable class="PARAMETER">cursor_name</replaceable> ]
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index a68f2e8..6d08dbd 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -1937,6 +1937,101 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	nsitem->p_lateral_only = false;
 	nsitem->p_lateral_ok = true;
 
+	/*
+	 * Check if (SET(*) = SELECT ...)  is present. If it is present we
+	 * need to resolve and populate the remaining needed MultiAssignRefs in the
+	 * target list. We modify target list in place and add needed MultiAssignRefs.
+	 */
+	if (list_length(stmt->targetList) == 1)
+	{
+		ResTarget *current_val = linitial(stmt->targetList);
+
+		/* Currently there is no way that ResTarget node's name value in UPDATE command
+		 * is set to NULL except for UPDATE SET (*) case.
+		 * Hence we can safely depend on name value being NULL as a check for
+		 * presence of UPDATE SET (*) case.
+		 */
+		if (current_val->name == NULL)
+		{
+			List *rel_cols_list;
+			List *expanded_tlist = NULL;
+			List *sub_list = NULL;
+			ListCell *lc_val;
+			ListCell *lc_relcol;
+			int rteindex = 0;
+			int sublevels_up = 0;
+			int i = 0;
+
+			rteindex = RTERangeTablePosn(pstate, pstate->p_target_rangetblentry,
+										 &sublevels_up);
+
+			expandRTE(pstate->p_target_rangetblentry, rteindex, sublevels_up,
+					  current_val->location, false,
+					  &(rel_cols_list), NULL);
+
+
+			/* SET(*) = (SELECT ...) case */
+			if (IsA(current_val->val, MultiAssignRef))
+			{
+				MultiAssignRef *orig_val = (MultiAssignRef *) (current_val->val);
+
+				orig_val->ncolumns = list_length(rel_cols_list);
+
+				Assert(sub_list == NULL);
+
+				sub_list = list_make1(orig_val);
+
+				/* Change targetlist to have corresponding ResTarget nodes
+				 * as corresponding to the columns in target relation */
+				for (i = 1;i < list_length(rel_cols_list);i++)
+				{
+					MultiAssignRef *r = makeNode(MultiAssignRef);
+
+					r->source = orig_val->source;
+					r->colno = i + 1;
+					r->ncolumns = orig_val->ncolumns;
+
+					lappend(sub_list, r);
+				}
+			}
+			else if (IsA(current_val->val, List))  /* SET(*) = (val, val,...) case */
+			{
+
+				Assert(sub_list == NULL);
+				sub_list = (List *) (current_val->val);
+			}
+			else
+			{
+				elog(ERROR, "Unknown type in UPDATE command");
+			}
+
+			if (list_length(rel_cols_list) != list_length(sub_list))
+				elog(ERROR, "number of columns does not match number of values");
+
+			/* Change targetlist to have corresponding ResTarget nodes
+			 * as corresponding to the columns in target relation */
+			forboth(lc_val, sub_list, lc_relcol, rel_cols_list)
+			{
+				ResTarget *current_res = makeNode(ResTarget);
+
+				current_res->name = strVal(lfirst(lc_relcol));
+				current_res->val = (Node *) (lfirst(lc_val));
+
+				if (expanded_tlist == NULL)
+				{
+					expanded_tlist = list_make1(current_res);
+				}
+				else
+				{
+					lappend(expanded_tlist, current_res);
+				}
+			}
+
+			stmt->targetList = expanded_tlist;
+		}
+	}
+
+
 	qry->targetList = transformTargetList(pstate, stmt->targetList,
 										  EXPR_KIND_UPDATE_SOURCE);
 
@@ -1982,18 +2077,20 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 			continue;
 		}
 		if (origTargetList == NULL)
-			elog(ERROR, "UPDATE target count mismatch --- internal error");
+				elog(ERROR, "UPDATE target count mismatch --- internal error");
+
 		origTarget = (ResTarget *) lfirst(origTargetList);
 		Assert(IsA(origTarget, ResTarget));
 
 		attrno = attnameAttNum(pstate->p_target_relation,
-							   origTarget->name, true);
+								   origTarget->name, true);
+
 		if (attrno == InvalidAttrNumber)
 			ereport(ERROR,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 errmsg("column \"%s\" of relation \"%s\" does not exist",
 							origTarget->name,
-						 RelationGetRelationName(pstate->p_target_relation)),
+							RelationGetRelationName(pstate->p_target_relation)),
 					 parser_errposition(pstate, origTarget->location)));
 
 		updateTargetListEntry(pstate, tle, origTarget->name,
@@ -2008,7 +2105,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 		origTargetList = lnext(origTargetList);
 	}
 	if (origTargetList != NULL)
-		elog(ERROR, "UPDATE target count mismatch --- internal error");
+	  elog(ERROR, "UPDATE target count mismatch --- internal error");
 
 	assign_query_collations(pstate, qry);
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 36dac29..25aa857 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -9629,6 +9629,58 @@ multiple_set_clause:
 
 					$$ = $2;
 				}
+| '(' '*' ')' '=' ctext_row
+{
+					ResTarget *res_col = makeNode(ResTarget);
+
+					/* We cheat a little here by making a single Restarget node but assigning the entire ctext_row
+					 * value to it. This is done since we cannot resolve attribute names at this stage */
+					res_col->name = NULL;
+					res_col->val = (Node *) $5;
+					res_col->location = @2;
+
+					$$ = list_make1(res_col);
+				}
+| '(' '*' ')' '=' select_with_parens
+{
+					SubLink *sl = makeNode(SubLink);
+					int ncolumns = -1;  /* We do not know the number of columns yet */
+
+					/*
+					 * Create a MultiAssignRef source as representative for the entire set
+					 * since we cannot look up attributes of target relation here.
+					 * A ResTarget node is made and MultiAssignRef source is assigned
+					 * as the ResTarget node's val.
+					 * Since we do not have column names at this stage, we shall have
+					 * column name expanding in parser transformation stage.
+					 * We make Lists at each level so that we can detect we have a
+					 * * in transformation stage.
+					 * We currently do not have any place in the code where we have a ResTarget
+					 * node in UPDATE command with name value as NULL. Hence, we can safely make a dummy ResTarget node
+					 * here and set name value to NULL and check it later in parser transformation
+					 * stage.
+					 */
+					ResTarget *res_col = makeNode(ResTarget);
+					MultiAssignRef *r = makeNode(MultiAssignRef);
+
+					/* First, convert bare SelectStmt into a SubLink */
+					sl->subLinkType = MULTIEXPR_SUBLINK;
+					sl->subLinkId = 0;		/* will be assigned later */
+					sl->testexpr = NULL;
+					sl->operName = NIL;
+					sl->subselect = $5;
+					sl->location = @5;
+
+					r->source = (Node *) sl;
+					r->colno = 1;
+					r->ncolumns = ncolumns;
+					res_col->val = (Node *) r;
+
+					res_col->name = NULL;
+					res_col->location = @2;
+
+					$$ = list_make1(res_col);
+				}
 		;
 
 set_target:
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index f0f0488..953e05e 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1295,7 +1295,7 @@ transformMultiAssignRef(ParseState *pstate, MultiAssignRef *maref)
 		Assert(IsA(qtree, Query));
 
 		/* Check subquery returns required number of columns */
-		if (count_nonjunk_tlist_entries(qtree->targetList) != maref->ncolumns)
+		if ((count_nonjunk_tlist_entries(qtree->targetList) != maref->ncolumns) && maref->ncolumns != -1)
 			ereport(ERROR,
 					(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("number of columns does not match number of values"),
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index b1dfa85..4237a97 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -374,13 +374,17 @@ typedef struct A_ArrayExpr
  * In an UPDATE target list, 'name' is the name of the destination column,
  * 'indirection' stores any subscripts attached to the destination, and
  * 'val' is the expression to assign.
+ * In an UPDATE operation namve value for ResTarget is not expected to be NULL
+ * hence we set name value to NULL in case of SET(*) case. Parser transformation
+ * code depends on name being NULL as a sure shot proof of SET(*) being present.
  *
  * See A_Indirection for more info about what can appear in 'indirection'.
  */
 typedef struct ResTarget
 {
 	NodeTag		type;
-	char	   *name;			/* column name or NULL */
+	char	   *name;			/* column name or NULL. Note that a NULL represents
+								 * an UPDATE SET (*) case here*/
 	List	   *indirection;	/* subscripts, field names, and '*', or NIL */
 	Node	   *val;			/* the value expression to compute or assign */
 	int			location;		/* token location, or -1 if unknown */
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 1de2a86..1307e0e 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -6,8 +6,16 @@ CREATE TABLE update_test (
     b   INT,
     c   TEXT
 );
+CREATE TABLE update_star (
+    a   INT DEFAULT 10,
+    b   INT,
+    c   TEXT
+);
 INSERT INTO update_test VALUES (5, 10, 'foo');
 INSERT INTO update_test(b, a) VALUES (15, 10);
+INSERT INTO update_star VALUES(105, 107, 'test2');
+INSERT INTO update_star VALUES(112, 93, 'test3');
+INSERT INTO update_star VALUES(84, 97, 'test4');
 SELECT * FROM update_test;
  a  | b  |  c  
 ----+----+-----
@@ -15,6 +23,26 @@ SELECT * FROM update_test;
  10 | 15 | 
 (2 rows)
 
+-- Check SET(*) case
+UPDATE update_star SET(*) = (SELECT * FROM update_star WHERE b = 107)  WHERE b=97;
+SELECT a,b, char_length(c) FROM update_star;
+  a  |  b  | char_length 
+-----+-----+-------------
+ 105 | 107 |           5
+ 112 |  93 |           5
+ 105 | 107 |           5
+(3 rows)
+
+UPDATE update_star SET(*) = (DEFAULT, DEFAULT, DEFAULT) WHERE b = 93;
+SELECT * FROM update_star;
+  a  |  b  |   c   
+-----+-----+-------
+ 105 | 107 | test2
+ 105 | 107 | test2
+  10 |     | 
+(3 rows)
+
+DROP TABLE update_star;
 UPDATE update_test SET a = DEFAULT, b = DEFAULT;
 SELECT * FROM update_test;
  a  | b |  c  
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index e71128c..208ac7a 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -8,11 +8,32 @@ CREATE TABLE update_test (
     c   TEXT
 );
 
+CREATE TABLE update_star (
+    a   INT DEFAULT 10,
+    b   INT,
+    c   TEXT
+);
+
 INSERT INTO update_test VALUES (5, 10, 'foo');
 INSERT INTO update_test(b, a) VALUES (15, 10);
 
+INSERT INTO update_star VALUES(105, 107, 'test2');
+INSERT INTO update_star VALUES(112, 93, 'test3');
+INSERT INTO update_star VALUES(84, 97, 'test4');
+
 SELECT * FROM update_test;
 
+-- Check SET(*) case
+UPDATE update_star SET(*) = (SELECT * FROM update_star WHERE b = 107)  WHERE b=97;
+
+SELECT a,b, char_length(c) FROM update_star;
+
+UPDATE update_star SET(*) = (DEFAULT, DEFAULT, DEFAULT) WHERE b = 93;
+
+SELECT * FROM update_star;
+
+DROP TABLE update_star;
+
 UPDATE update_test SET a = DEFAULT, b = DEFAULT;
 
 SELECT * FROM update_test;
@@ -21,7 +42,6 @@ SELECT * FROM update_test;
 UPDATE update_test AS t SET b = 10 WHERE t.a = 10;
 
 SELECT * FROM update_test;
-
 UPDATE update_test t SET b = t.b + 10 WHERE t.a = 10;
 
 SELECT * FROM update_test;
-- 
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