Hi,
As per discussion on -hackers, a patch which allows updates to use
subselects is attached with this mail.
As per discussion with Tom, I have adopted the following approach:
* Introduce ROWEXPR_SUBLINK type for subqueries that allows multiple column
outputs.
* Populate the targetList with PARAM_SUBLINK entries dependent on the
subselects.
* Modify the targets in-place into PARAM_EXEC entries in the make_subplan
phase.
The above does not require any kluges in the targetList processing code path
at all.
UPDATEs seem to work fine using subselects with this patch. I have modified
the update.sql regression test to include possible variations .
No documentation changes are present in this patch.
Feedback, comments appreciated.
Regards,
Nikhils
--
EnterpriseDB http://www.enterprisedb.com
? GNUmakefile
? config.log
? config.status
? cscope.out
? src/Makefile.global
? src/backend/postgres
? src/backend/catalog/postgres.bki
? src/backend/catalog/postgres.description
? src/backend/catalog/postgres.shdescription
? src/backend/utils/mb/conversion_procs/conversion_create.sql
? src/backend/utils/mb/conversion_procs/ascii_and_mic/libascii_and_mic.so.0.0
? src/backend/utils/mb/conversion_procs/cyrillic_and_mic/libcyrillic_and_mic.so.0.0
? src/backend/utils/mb/conversion_procs/euc_cn_and_mic/libeuc_cn_and_mic.so.0.0
? src/backend/utils/mb/conversion_procs/euc_jis_2004_and_shift_jis_2004/libeuc_jis_2004_and_shift_jis_2004.so.0.0
? src/backend/utils/mb/conversion_procs/euc_jp_and_sjis/libeuc_jp_and_sjis.so.0.0
? src/backend/utils/mb/conversion_procs/euc_kr_and_mic/libeuc_kr_and_mic.so.0.0
? src/backend/utils/mb/conversion_procs/euc_tw_and_big5/libeuc_tw_and_big5.so.0.0
? src/backend/utils/mb/conversion_procs/latin2_and_win1250/liblatin2_and_win1250.so.0.0
? src/backend/utils/mb/conversion_procs/latin_and_mic/liblatin_and_mic.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_ascii/libutf8_and_ascii.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_big5/libutf8_and_big5.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_cyrillic/libutf8_and_cyrillic.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_euc_cn/libutf8_and_euc_cn.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_euc_jis_2004/libutf8_and_euc_jis_2004.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_euc_jp/libutf8_and_euc_jp.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_euc_kr/libutf8_and_euc_kr.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_euc_tw/libutf8_and_euc_tw.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_gb18030/libutf8_and_gb18030.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_gbk/libutf8_and_gbk.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_iso8859/libutf8_and_iso8859.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_iso8859_1/libutf8_and_iso8859_1.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_johab/libutf8_and_johab.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_shift_jis_2004/libutf8_and_shift_jis_2004.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_sjis/libutf8_and_sjis.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_uhc/libutf8_and_uhc.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_win/libutf8_and_win.so.0.0
? src/bin/initdb/initdb
? src/bin/ipcclean/ipcclean
? src/bin/pg_config/pg_config
? src/bin/pg_controldata/pg_controldata
? src/bin/pg_ctl/pg_ctl
? src/bin/pg_dump/pg_dump
? src/bin/pg_dump/pg_dumpall
? src/bin/pg_dump/pg_restore
? src/bin/pg_resetxlog/pg_resetxlog
? src/bin/psql/psql
? src/bin/scripts/clusterdb
? src/bin/scripts/createdb
? src/bin/scripts/createlang
? src/bin/scripts/createuser
? src/bin/scripts/dropdb
? src/bin/scripts/droplang
? src/bin/scripts/dropuser
? src/bin/scripts/reindexdb
? src/bin/scripts/vacuumdb
? src/include/pg_config.h
? src/include/stamp-h
? src/interfaces/ecpg/compatlib/libecpg_compat.so.2.3
? src/interfaces/ecpg/ecpglib/libecpg.so.5.3
? src/interfaces/ecpg/include/ecpg_config.h
? src/interfaces/ecpg/pgtypeslib/libpgtypes.so.2.3
? src/interfaces/ecpg/preproc/.pgc.c.swp
? src/interfaces/ecpg/preproc/ecpg
? src/interfaces/libpq/exports.list
? src/interfaces/libpq/libpq.so.5.1
? src/pl/plpgsql/src/libplpgsql.so.1.0
? src/port/pg_config_paths.h
? src/test/regress/libregress.so.0.0
? src/test/regress/pg_regress
? src/test/regress/results
? src/test/regress/testtablespace
? src/test/regress/expected/constraints.out
? src/test/regress/expected/copy.out
? src/test/regress/expected/create_function_1.out
? src/test/regress/expected/create_function_2.out
? src/test/regress/expected/largeobject.out
? src/test/regress/expected/largeobject_1.out
? src/test/regress/expected/misc.out
? src/test/regress/expected/tablespace.out
? src/test/regress/sql/constraints.sql
? src/test/regress/sql/copy.sql
? src/test/regress/sql/create_function_1.sql
? src/test/regress/sql/create_function_2.sql
? src/test/regress/sql/largeobject.sql
? src/test/regress/sql/misc.sql
? src/test/regress/sql/tablespace.sql
? src/timezone/zic
Index: src/backend/executor/nodeSubplan.c
===================================================================
RCS file: /repositories/postgreshome/cvs/pgsql/src/backend/executor/nodeSubplan.c,v
retrieving revision 1.87
diff -c -r1.87 nodeSubplan.c
*** src/backend/executor/nodeSubplan.c 27 Feb 2007 01:11:25 -0000 1.87
--- src/backend/executor/nodeSubplan.c 11 Apr 2007 08:04:32 -0000
***************
*** 322,329 ****
continue;
}
! /* cannot allow multiple input tuples for ROWCOMPARE sublink either */
! if (subLinkType == ROWCOMPARE_SUBLINK && found)
ereport(ERROR,
(errcode(ERRCODE_CARDINALITY_VIOLATION),
errmsg("more than one row returned by a subquery used as an expression")));
--- 322,330 ----
continue;
}
! /* cannot allow multiple input tuples for ROWCOMPARE/ROWEXPR sublink either */
! if ((subLinkType == ROWCOMPARE_SUBLINK || subLinkType == ROWEXPR_SUBLINK)
! && found)
ereport(ERROR,
(errcode(ERRCODE_CARDINALITY_VIOLATION),
errmsg("more than one row returned by a subquery used as an expression")));
***************
*** 376,382 ****
}
else
{
! /* must be ROWCOMPARE_SUBLINK */
result = rowresult;
*isNull = rownull;
}
--- 377,383 ----
}
else
{
! /* must be ROWCOMPARE_SUBLINK or ROWEXPR_SUBLINK */
result = rowresult;
*isNull = rownull;
}
***************
*** 391,397 ****
*/
if (subLinkType == EXPR_SUBLINK ||
subLinkType == ARRAY_SUBLINK ||
! subLinkType == ROWCOMPARE_SUBLINK)
{
result = (Datum) 0;
*isNull = true;
--- 392,399 ----
*/
if (subLinkType == EXPR_SUBLINK ||
subLinkType == ARRAY_SUBLINK ||
! subLinkType == ROWCOMPARE_SUBLINK ||
! subLinkType == ROWEXPR_SUBLINK)
{
result = (Datum) 0;
*isNull = true;
***************
*** 937,942 ****
--- 939,945 ----
if (found &&
(subLinkType == EXPR_SUBLINK ||
+ subLinkType == ROWEXPR_SUBLINK ||
subLinkType == ROWCOMPARE_SUBLINK))
ereport(ERROR,
(errcode(ERRCODE_CARDINALITY_VIOLATION),
Index: src/backend/optimizer/plan/planner.c
===================================================================
RCS file: /repositories/postgreshome/cvs/pgsql/src/backend/optimizer/plan/planner.c,v
retrieving revision 1.216
diff -c -r1.216 planner.c
*** src/backend/optimizer/plan/planner.c 27 Feb 2007 01:11:25 -0000 1.216
--- src/backend/optimizer/plan/planner.c 11 Apr 2007 08:04:32 -0000
***************
*** 72,77 ****
--- 72,78 ----
List *sub_tlist,
AttrNumber *groupColIdx);
static List *postprocess_setop_tlist(List *new_tlist, List *orig_tlist);
+ static void preprocess_subselects(PlannerInfo *root, Node *expr);
/*****************************************************************************
***************
*** 273,278 ****
--- 274,284 ----
expand_inherited_tables(root);
/*
+ * preprocess subselects if present in the Query
+ */
+ preprocess_subselects(root, (Node *)parse->subSelects);
+
+ /*
* Set hasHavingQual to remember if HAVING clause is present. Needed
* because preprocess_expression will reduce a constant-true condition to
* an empty qual list ... but "HAVING TRUE" is not a semantic no-op.
***************
*** 1752,1754 ****
--- 1758,1776 ----
elog(ERROR, "resjunk output columns are not implemented");
return new_tlist;
}
+
+ /*
+ * preprocess_subselects
+ */
+ static void
+ preprocess_subselects(PlannerInfo *root, Node *expr)
+ {
+ /*
+ * Fall out quickly if expression is empty.
+ */
+ if (expr == NULL)
+ return;
+ SS_process_sublinks(root, expr, false);
+
+ return;
+ }
Index: src/backend/optimizer/plan/subselect.c
===================================================================
RCS file: /repositories/postgreshome/cvs/pgsql/src/backend/optimizer/plan/subselect.c,v
retrieving revision 1.122
diff -c -r1.122 subselect.c
*** src/backend/optimizer/plan/subselect.c 27 Feb 2007 01:11:25 -0000 1.122
--- src/backend/optimizer/plan/subselect.c 11 Apr 2007 08:04:32 -0000
***************
*** 68,73 ****
--- 68,79 ----
Bitmapset *outer_params,
Bitmapset *valid_params);
static bool finalize_primnode(Node *node, finalize_primnode_context *context);
+ static Node *convert_rowexpr(PlannerInfo *root,
+ Node *testexpr,
+ int rtindex,
+ List **righthandIds);
+ static bool convert_rowexpr_walker(Node *node,
+ convert_testexpr_context *context);
/*
***************
*** 385,390 ****
--- 391,406 ----
* plan's expression tree; it is not kept in the initplan node.
*/
}
+ else if (splan->parParam == NIL && slink->subLinkType == ROWEXPR_SUBLINK)
+ {
+ /* Adjust the Params */
+ result = convert_rowexpr(root,
+ slink->testexpr,
+ 0,
+ &splan->paramIds);
+ splan->setParam = list_copy(splan->paramIds);
+ isInitPlan = true;
+ }
else
{
List *args;
***************
*** 1376,1378 ****
--- 1392,1459 ----
return prm;
}
+
+ /*
+ * convert_rowexpr:
+ * Use a walker to do an in-situ change to the PARAM entries involved in a
+ * ROWEXPR_SUBLINK subquery
+ */
+ static Node *
+ convert_rowexpr(PlannerInfo *root,
+ Node *testexpr,
+ int rtindex,
+ List **righthandIds)
+ {
+ convert_testexpr_context context;
+
+ context.root = root;
+ context.rtindex = rtindex;
+ context.righthandIds = NIL;
+ convert_rowexpr_walker(testexpr, &context);
+ *righthandIds = context.righthandIds;
+ return testexpr;
+ }
+
+ static bool
+ convert_rowexpr_walker(Node *node,
+ convert_testexpr_context *context)
+ {
+ if (node == NULL)
+ return false;
+
+ if (IsA(node, Param))
+ {
+ Param *param = (Param *) node;
+
+ if (param->paramkind == PARAM_SUBLINK)
+ {
+ PlannerParamItem *pitem;
+
+ /*
+ * We expect to encounter the Params in column-number sequence. We
+ * could handle non-sequential order if necessary, but for now
+ * there's no need. (This is also a useful cross-check that we
+ * aren't finding any unexpected Params.)
+ */
+ if (param->paramid != list_length(context->righthandIds) + 1)
+ elog(ERROR, "unexpected PARAM_SUBLINK ID: %d", param->paramid);
+
+ /* Modify the Param node in-situ representing the subplan's result */
+ param->paramkind = PARAM_EXEC;
+ param->paramid = list_length(context->root->glob->paramlist);
+
+ pitem = makeNode(PlannerParamItem);
+ pitem->item = (Node *) param;
+ pitem->abslevel = context->root->query_level;
+
+ context->root->glob->paramlist =
+ lappend(context->root->glob->paramlist, pitem);
+
+ context->righthandIds = lappend_int(context->righthandIds,
+ param->paramid);
+ }
+ }
+ return expression_tree_walker((Node *)node,
+ convert_rowexpr_walker,
+ (void *) context);
+ }
Index: src/backend/parser/analyze.c
===================================================================
RCS file: /repositories/postgreshome/cvs/pgsql/src/backend/parser/analyze.c,v
retrieving revision 1.362
diff -c -r1.362 analyze.c
*** src/backend/parser/analyze.c 13 Mar 2007 00:33:41 -0000 1.362
--- src/backend/parser/analyze.c 11 Apr 2007 08:04:32 -0000
***************
*** 110,116 ****
static Query *transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt);
static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt,
List **extras_before, List **extras_after);
! static List *transformInsertRow(ParseState *pstate, List *exprlist,
List *stmtcols, List *icolumns, List *attrnos);
static List *transformReturningList(ParseState *pstate, List *returningList);
static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt);
--- 110,116 ----
static Query *transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt);
static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt,
List **extras_before, List **extras_after);
! static List *transformRow(ParseState *pstate, List *exprlist,
List *stmtcols, List *icolumns, List *attrnos);
static List *transformReturningList(ParseState *pstate, List *returningList);
static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt);
***************
*** 497,503 ****
false, false, ACL_INSERT);
/* Validate stmt->cols list, or build default list if no list given */
! icolumns = checkInsertTargets(pstate, stmt->cols, &attrnos);
Assert(list_length(icolumns) == list_length(attrnos));
/*
--- 497,503 ----
false, false, ACL_INSERT);
/* Validate stmt->cols list, or build default list if no list given */
! icolumns = checkTargets(pstate, stmt->cols, &attrnos);
Assert(list_length(icolumns) == list_length(attrnos));
/*
***************
*** 603,609 ****
}
/* Prepare row for assignment to target table */
! exprList = transformInsertRow(pstate, exprList,
stmt->cols,
icolumns, attrnos);
}
--- 603,609 ----
}
/* Prepare row for assignment to target table */
! exprList = transformRow(pstate, exprList,
stmt->cols,
icolumns, attrnos);
}
***************
*** 643,649 ****
}
/* Prepare row for assignment to target table */
! sublist = transformInsertRow(pstate, sublist,
stmt->cols,
icolumns, attrnos);
--- 643,649 ----
}
/* Prepare row for assignment to target table */
! sublist = transformRow(pstate, sublist,
stmt->cols,
icolumns, attrnos);
***************
*** 710,716 ****
(List *) linitial(valuesLists));
/* Prepare row for assignment to target table */
! exprList = transformInsertRow(pstate, exprList,
stmt->cols,
icolumns, attrnos);
}
--- 710,716 ----
(List *) linitial(valuesLists));
/* Prepare row for assignment to target table */
! exprList = transformRow(pstate, exprList,
stmt->cols,
icolumns, attrnos);
}
***************
*** 771,783 ****
}
/*
! * Prepare an INSERT row for assignment to the target table.
*
* The row might be either a VALUES row, or variables referencing a
* sub-SELECT output.
*/
static List *
! transformInsertRow(ParseState *pstate, List *exprlist,
List *stmtcols, List *icolumns, List *attrnos)
{
List *result;
--- 771,783 ----
}
/*
! * Prepare an INSERT/UPDATE row for assignment to the target table.
*
* The row might be either a VALUES row, or variables referencing a
* sub-SELECT output.
*/
static List *
! transformRow(ParseState *pstate, List *exprlist,
List *stmtcols, List *icolumns, List *attrnos)
{
List *result;
***************
*** 795,806 ****
if (list_length(exprlist) > list_length(icolumns))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
! errmsg("INSERT has more expressions than target columns")));
if (stmtcols != NIL &&
list_length(exprlist) < list_length(icolumns))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
! errmsg("INSERT has more target columns than expressions")));
/*
* Prepare columns for assignment to target table.
--- 795,806 ----
if (list_length(exprlist) > list_length(icolumns))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
! errmsg("INSERT/UPDATE has more expressions than target columns")));
if (stmtcols != NIL &&
list_length(exprlist) < list_length(icolumns))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
! errmsg("INSERT/UPDATE has more target columns than expressions")));
/*
* Prepare columns for assignment to target table.
***************
*** 2784,2790 ****
--- 2784,2802 ----
Node *qual;
ListCell *origTargetList;
ListCell *tl;
+ ListCell *lc, *lcell;
+ List *exprList = NIL;
+ List *icolumns;
+ List *attrnos;
+ List *otherColsList = NIL;
+ List *selectColsList = NIL;
+ List *selectQryList;
+ List *subselectQryList = NIL;
+ ListCell *icols;
+ ListCell *attnos;
+ SubLink *subselect;
+ qry->subSelects = NIL;
qry->commandType = CMD_UPDATE;
pstate->p_is_update = true;
***************
*** 2792,2797 ****
--- 2804,2890 ----
interpretInhOption(stmt->relation->inhOpt),
true,
ACL_UPDATE);
+ /*
+ * check if the targetList contains subselects and if so process each of
+ * them
+ */
+ qry->targetList = NIL;
+ subselectQryList = NIL;
+ foreach(lcell, stmt->targetList)
+ {
+ Query *selectQuery;
+
+ if (!IsA(lfirst(lcell), SubLink))
+ {
+ otherColsList = lappend(otherColsList, lfirst(lcell));
+ continue;
+ }
+
+ subselect = (SubLink *)lfirst(lcell);
+
+ /* Validate subselect->cols list */
+ icolumns = checkTargets(pstate, subselect->cols, &attrnos);
+ Assert(list_length(icolumns) == list_length(attrnos));
+
+ /*
+ * Maintain sub select based target cols in another list
+ */
+ selectColsList = list_concat(selectColsList, subselect->cols);
+
+ /*
+ * Process the SELECT subquery.
+ */
+
+ transformExpr(pstate, (Node *)subselect);
+ selectQuery = (Query *)subselect->subselect;
+
+ Assert(IsA(selectQuery, Query));
+ Assert(selectQuery->commandType == CMD_SELECT);
+ if (selectQuery->into)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("UPDATE ... SELECT cannot specify INTO")));
+
+ /* Prepare row for assignment to target table */
+ exprList = transformRow(pstate, (List *)subselect->testexpr,
+ subselect->cols,
+ icolumns, attrnos);
+ /*
+ * Generate query's target list using the computed list of expressions.
+ */
+ selectQryList = NIL;
+ icols = list_head(icolumns);
+ attnos = list_head(attrnos);
+ foreach(lc, exprList)
+ {
+ Expr *expr = (Expr *) lfirst(lc);
+ ResTarget *col;
+ TargetEntry *tle;
+
+ col = (ResTarget *) lfirst(icols);
+ Assert(IsA(col, ResTarget));
+
+ tle = makeTargetEntry(expr,
+ (AttrNumber) lfirst_int(attnos),
+ col->name,
+ false);
+ selectQryList = lappend(selectQryList, tle);
+
+ icols = lnext(icols);
+ attnos = lnext(attnos);
+ }
+ subselect->testexpr = (Node *)selectQryList;
+ /*
+ * Need to use list_copy otherwise targets of multiple subselects get
+ * chained to one another
+ */
+ subselectQryList = list_concat(subselectQryList, list_copy(selectQryList));
+
+ /*
+ * Add this sublink to the Query for further processing by the planner
+ */
+ qry->subSelects = lappend(qry->subSelects, subselect);
+ }
/*
* the FROM clause is non-standard SQL syntax. We used to be able to do
***************
*** 2799,2806 ****
*/
transformFromClause(pstate, stmt->fromClause);
! qry->targetList = transformTargetList(pstate, stmt->targetList);
!
qual = transformWhereClause(pstate, stmt->whereClause, "WHERE");
qry->returningList = transformReturningList(pstate, stmt->returningList);
--- 2892,2901 ----
*/
transformFromClause(pstate, stmt->fromClause);
!
! if (otherColsList)
! qry->targetList = transformTargetList(pstate, otherColsList);
!
qual = transformWhereClause(pstate, stmt->whereClause, "WHERE");
qry->returningList = transformReturningList(pstate, stmt->returningList);
***************
*** 2829,2836 ****
if (pstate->p_next_resno <= pstate->p_target_relation->rd_rel->relnatts)
pstate->p_next_resno = pstate->p_target_relation->rd_rel->relnatts + 1;
! /* Prepare non-junk columns for assignment to target table */
! origTargetList = list_head(stmt->targetList);
foreach(tl, qry->targetList)
{
--- 2924,2935 ----
if (pstate->p_next_resno <= pstate->p_target_relation->rd_rel->relnatts)
pstate->p_next_resno = pstate->p_target_relation->rd_rel->relnatts + 1;
! /*
! * Prepare non-junk columns for assignment to target table.
! * The subselect based targets have already been processed, process the
! * other remaining entries here
! */
! origTargetList = list_head(otherColsList);
foreach(tl, qry->targetList)
{
***************
*** 2875,2880 ****
--- 2974,2984 ----
if (origTargetList != NULL)
elog(ERROR, "UPDATE target count mismatch --- internal error");
+ /*
+ * subselect based target entries need to be made a part of the query's
+ * targetList here too
+ */
+ qry->targetList = list_concat(qry->targetList, subselectQryList);
return qry;
}
Index: src/backend/parser/gram.y
===================================================================
RCS file: /repositories/postgreshome/cvs/pgsql/src/backend/parser/gram.y,v
retrieving revision 2.587
diff -c -r2.587 gram.y
*** src/backend/parser/gram.y 8 Apr 2007 00:26:34 -0000 2.587
--- src/backend/parser/gram.y 11 Apr 2007 08:04:32 -0000
***************
*** 5774,5779 ****
--- 5774,5789 ----
$$ = $2;
}
+ | '(' set_target_list ')' '=' select_with_parens
+ {
+ SubLink *n = makeNode(SubLink);
+ n->subLinkType = ROWEXPR_SUBLINK;
+ n->testexpr = NULL;
+ n->operName = NIL;
+ n->subselect = $5;
+ n->cols = $2;
+ $$ = list_make1((Node *)n);
+ }
;
set_target:
Index: src/backend/parser/parse_expr.c
===================================================================
RCS file: /repositories/postgreshome/cvs/pgsql/src/backend/parser/parse_expr.c,v
retrieving revision 1.216
diff -c -r1.216 parse_expr.c
*** src/backend/parser/parse_expr.c 2 Apr 2007 03:49:39 -0000 1.216
--- src/backend/parser/parse_expr.c 11 Apr 2007 08:04:32 -0000
***************
*** 1159,1164 ****
--- 1159,1204 ----
sublink->testexpr = NULL;
sublink->operName = NIL;
}
+ else if (sublink->subLinkType == ROWEXPR_SUBLINK)
+ {
+ List *left_list;
+ List *right_list;
+ ListCell *l;
+
+ left_list = sublink->cols;
+ Assert(left_list != NULL);
+ /*
+ * Build a list of PARAM_SUBLINK nodes representing the output columns
+ * of the subquery.
+ */
+ right_list = NIL;
+ foreach(l, qtree->targetList)
+ {
+ TargetEntry *tent = (TargetEntry *) lfirst(l);
+ Param *param;
+
+ if (tent->resjunk)
+ continue;
+
+ param = makeNode(Param);
+ param->paramkind = PARAM_SUBLINK;
+ param->paramid = tent->resno;
+ param->paramtype = exprType((Node *) tent->expr);
+ param->paramtypmod = exprTypmod((Node *) tent->expr);
+
+ right_list = lappend(right_list, param);
+ }
+
+ if (list_length(left_list) < list_length(right_list))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("subquery has too many columns")));
+ if (list_length(left_list) > list_length(right_list))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("subquery has too few columns")));
+ sublink->testexpr = (Node *)right_list;
+ }
else
{
/* ALL, ANY, or ROWCOMPARE: generate row-comparing expression */
Index: src/backend/parser/parse_target.c
===================================================================
RCS file: /repositories/postgreshome/cvs/pgsql/src/backend/parser/parse_target.c,v
retrieving revision 1.154
diff -c -r1.154 parse_target.c
*** src/backend/parser/parse_target.c 3 Feb 2007 14:06:54 -0000 1.154
--- src/backend/parser/parse_target.c 11 Apr 2007 08:04:33 -0000
***************
*** 706,725 ****
/*
! * checkInsertTargets -
! * generate a list of INSERT column targets if not supplied, or
* test supplied column names to make sure they are in target table.
* Also return an integer list of the columns' attribute numbers.
*/
List *
! checkInsertTargets(ParseState *pstate, List *cols, List **attrnos)
{
*attrnos = NIL;
if (cols == NIL)
{
/*
! * Generate default column list for INSERT.
*/
Form_pg_attribute *attr = pstate->p_target_relation->rd_att->attrs;
int numcol = pstate->p_target_relation->rd_rel->relnatts;
--- 706,725 ----
/*
! * checkTargets -
! * generate a list of INSERT/UPDATE column targets if not supplied, or
* test supplied column names to make sure they are in target table.
* Also return an integer list of the columns' attribute numbers.
*/
List *
! checkTargets(ParseState *pstate, List *cols, List **attrnos)
{
*attrnos = NIL;
if (cols == NIL)
{
/*
! * Generate default column list for INSERT/UPDATE.
*/
Form_pg_attribute *attr = pstate->p_target_relation->rd_att->attrs;
int numcol = pstate->p_target_relation->rd_rel->relnatts;
***************
*** 744,750 ****
else
{
/*
! * Do initial validation of user-supplied INSERT column list.
*/
Bitmapset *wholecols = NULL;
Bitmapset *partialcols = NULL;
--- 744,750 ----
else
{
/*
! * Do initial validation of user-supplied INSERT/UPDATE column list.
*/
Bitmapset *wholecols = NULL;
Bitmapset *partialcols = NULL;
***************
*** 768,774 ****
/*
* Check for duplicates, but only of whole columns --- we allow
! * INSERT INTO foo (col.subcol1, col.subcol2)
*/
if (col->indirection == NIL)
{
--- 768,775 ----
/*
* Check for duplicates, but only of whole columns --- we allow
! * INSERT INTO foo (col.subcol1, col.subcol2).
! * Above applies for UPDATEs too
*/
if (col->indirection == NIL)
{
Index: src/include/nodes/parsenodes.h
===================================================================
RCS file: /repositories/postgreshome/cvs/pgsql/src/include/nodes/parsenodes.h,v
retrieving revision 1.344
diff -c -r1.344 parsenodes.h
*** src/include/nodes/parsenodes.h 2 Apr 2007 03:49:41 -0000 1.344
--- src/include/nodes/parsenodes.h 11 Apr 2007 08:04:33 -0000
***************
*** 129,134 ****
--- 129,135 ----
Node *setOperations; /* set-operation tree if this is top level of
* a UNION/INTERSECT/EXCEPT query */
+ List *subSelects; /* a list of subselects used in UPDATEs */
} Query;
Index: src/include/nodes/primnodes.h
===================================================================
RCS file: /repositories/postgreshome/cvs/pgsql/src/include/nodes/primnodes.h,v
retrieving revision 1.129
diff -c -r1.129 primnodes.h
*** src/include/nodes/primnodes.h 27 Mar 2007 23:21:12 -0000 1.129
--- src/include/nodes/primnodes.h 11 Apr 2007 08:04:33 -0000
***************
*** 377,382 ****
--- 377,383 ----
* ANY_SUBLINK (lefthand) op ANY (SELECT ...)
* ROWCOMPARE_SUBLINK (lefthand) op (SELECT ...)
* EXPR_SUBLINK (SELECT with single targetlist item ...)
+ * ROWEXPR_SUBLINK (SELECT with multiple targetlist items ...)
* ARRAY_SUBLINK ARRAY(SELECT with single targetlist item ...)
* For ALL, ANY, and ROWCOMPARE, the lefthand is a list of expressions of the
* same length as the subselect's targetlist. ROWCOMPARE will *always* have
***************
*** 384,392 ****
* then the parser will create an EXPR_SUBLINK instead (and any operator
* above the subselect will be represented separately). Note that both
* ROWCOMPARE and EXPR require the subselect to deliver only one row.
! * ALL, ANY, and ROWCOMPARE require the combining operators to deliver boolean
! * results. ALL and ANY combine the per-row results using AND and OR
! * semantics respectively.
* ARRAY requires just one target column, and creates an array of the target
* column's type using one or more rows resulting from the subselect.
*
--- 385,394 ----
* then the parser will create an EXPR_SUBLINK instead (and any operator
* above the subselect will be represented separately). Note that both
* ROWCOMPARE and EXPR require the subselect to deliver only one row.
! * For subselects with multiple targets, the parser will create a
! * ROWEXPR sublink. ALL, ANY, and ROWCOMPARE require the combining operators
! * to deliver boolean results. ALL and ANY combine the per-row results using
! * AND and OR semantics respectively.
* ARRAY requires just one target column, and creates an array of the target
* column's type using one or more rows resulting from the subselect.
*
***************
*** 412,417 ****
--- 414,420 ----
ANY_SUBLINK,
ROWCOMPARE_SUBLINK,
EXPR_SUBLINK,
+ ROWEXPR_SUBLINK,
ARRAY_SUBLINK
} SubLinkType;
***************
*** 422,427 ****
--- 425,431 ----
SubLinkType subLinkType; /* see above */
Node *testexpr; /* outer-query test for ALL/ANY/ROWCOMPARE */
List *operName; /* originally specified operator name */
+ List *cols; /* names of the target columns for ROWEXPR_SUBLINK */
Node *subselect; /* subselect as Query* or parsetree */
} SubLink;
Index: src/include/parser/parse_target.h
===================================================================
RCS file: /repositories/postgreshome/cvs/pgsql/src/include/parser/parse_target.h,v
retrieving revision 1.42
diff -c -r1.42 parse_target.h
*** src/include/parser/parse_target.h 5 Jan 2007 22:19:57 -0000 1.42
--- src/include/parser/parse_target.h 11 Apr 2007 08:04:33 -0000
***************
*** 32,38 ****
char *colname, int attrno,
List *indirection,
int location);
! extern List *checkInsertTargets(ParseState *pstate, List *cols,
List **attrnos);
extern TupleDesc expandRecordVariable(ParseState *pstate, Var *var,
int levelsup);
--- 32,38 ----
char *colname, int attrno,
List *indirection,
int location);
! extern List *checkTargets(ParseState *pstate, List *cols,
List **attrnos);
extern TupleDesc expandRecordVariable(ParseState *pstate, Var *var,
int levelsup);
Index: src/test/regress/expected/alter_table.out
===================================================================
RCS file: /repositories/postgreshome/cvs/pgsql/src/test/regress/expected/alter_table.out,v
retrieving revision 1.101
diff -c -r1.101 alter_table.out
*** src/test/regress/expected/alter_table.out 14 Feb 2007 01:58:58 -0000 1.101
--- src/test/regress/expected/alter_table.out 11 Apr 2007 08:04:33 -0000
***************
*** 765,773 ****
^
-- INSERTs
insert into atacc1 values (10, 11, 12, 13);
! ERROR: INSERT has more expressions than target columns
insert into atacc1 values (default, 11, 12, 13);
! ERROR: INSERT has more expressions than target columns
insert into atacc1 values (11, 12, 13);
insert into atacc1 (a) values (10);
ERROR: column "a" of relation "atacc1" does not exist
--- 765,773 ----
^
-- INSERTs
insert into atacc1 values (10, 11, 12, 13);
! ERROR: INSERT/UPDATE has more expressions than target columns
insert into atacc1 values (default, 11, 12, 13);
! ERROR: INSERT/UPDATE has more expressions than target columns
insert into atacc1 values (11, 12, 13);
insert into atacc1 (a) values (10);
ERROR: column "a" of relation "atacc1" does not exist
Index: src/test/regress/expected/insert.out
===================================================================
RCS file: /repositories/postgreshome/cvs/pgsql/src/test/regress/expected/insert.out,v
retrieving revision 1.8
diff -c -r1.8 insert.out
*** src/test/regress/expected/insert.out 3 Aug 2006 14:54:44 -0000 1.8
--- src/test/regress/expected/insert.out 11 Apr 2007 08:04:33 -0000
***************
*** 21,33 ****
-- insert with similar expression / target_list values (all fail)
--
insert into inserttest (col1, col2, col3) values (DEFAULT, DEFAULT);
! ERROR: INSERT has more target columns than expressions
insert into inserttest (col1, col2, col3) values (1, 2);
! ERROR: INSERT has more target columns than expressions
insert into inserttest (col1) values (1, 2);
! ERROR: INSERT has more expressions than target columns
insert into inserttest (col1) values (DEFAULT, DEFAULT);
! ERROR: INSERT has more expressions than target columns
select * from inserttest;
col1 | col2 | col3
------+------+---------
--- 21,33 ----
-- insert with similar expression / target_list values (all fail)
--
insert into inserttest (col1, col2, col3) values (DEFAULT, DEFAULT);
! ERROR: INSERT/UPDATE has more target columns than expressions
insert into inserttest (col1, col2, col3) values (1, 2);
! ERROR: INSERT/UPDATE has more target columns than expressions
insert into inserttest (col1) values (1, 2);
! ERROR: INSERT/UPDATE has more expressions than target columns
insert into inserttest (col1) values (DEFAULT, DEFAULT);
! ERROR: INSERT/UPDATE has more expressions than target columns
select * from inserttest;
col1 | col2 | col3
------+------+---------
Index: src/test/regress/expected/update.out
===================================================================
RCS file: /repositories/postgreshome/cvs/pgsql/src/test/regress/expected/update.out,v
retrieving revision 1.5
diff -c -r1.5 update.out
*** src/test/regress/expected/update.out 3 Sep 2006 22:37:06 -0000 1.5
--- src/test/regress/expected/update.out 11 Apr 2007 08:04:33 -0000
***************
*** 74,85 ****
-- fail, multi assignment to same column:
UPDATE update_test SET (c,b) = ('car', a+b), b = a + 1 WHERE a = 10;
ERROR: multiple assignments to same column "b"
! -- XXX this should work, but doesn't yet:
! UPDATE update_test SET (a,b) = (select a,b FROM update_test where c = 'foo')
! WHERE a = 10;
! ERROR: syntax error at or near "select"
! LINE 1: UPDATE update_test SET (a,b) = (select a,b FROM update_test ...
! ^
-- if an alias for the target table is specified, don't allow references
-- to the original table name
BEGIN;
--- 74,131 ----
-- fail, multi assignment to same column:
UPDATE update_test SET (c,b) = ('car', a+b), b = a + 1 WHERE a = 10;
ERROR: multiple assignments to same column "b"
! INSERT INTO update_test VALUES (5, 10, 'foo');
! -- update using subselect:
! UPDATE update_test SET (a,b) = (select a+5,b FROM update_test where c = 'foo')
! WHERE a = 5;
! SELECT * FROM update_test;
! a | b | c
! -----+----+-----
! 100 | 20 |
! 11 | 41 | car
! 10 | 10 | foo
! (3 rows)
!
! -- fail update when subselect returns multiple rows:
! UPDATE update_test SET (a,b) = (select a,b FROM update_test);
! ERROR: more than one row returned by a subquery used as an expression
! -- combination of multiple subselects in UPDATEs
! UPDATE update_test SET a= (select a FROM update_test where c = 'foo'),
! b = (select b+100 FROM update_test where c = 'foo') WHERE a = 10;
! SELECT * FROM update_test;
! a | b | c
! -----+-----+-----
! 100 | 20 |
! 11 | 41 | car
! 10 | 110 | foo
! (3 rows)
!
! -- combination of normal SET with subselects
! UPDATE update_test SET a= (select a+5 FROM update_test where c = 'foo'),
! b = 100 WHERE a = 10;
! SELECT * FROM update_test;
! a | b | c
! -----+-----+-----
! 100 | 20 |
! 11 | 41 | car
! 15 | 100 | foo
! (3 rows)
!
! -- fail, multi assignment to same column when subselects are involved
! UPDATE update_test SET (c,b) = ('car', a+b),
! b = (select b from update_test where c = 'foo') WHERE a = 10;
! ERROR: multiple assignments to same column "b"
! -- update using subselect, subquery should return NULLs if there are no matches
! UPDATE update_test SET (a,b) = (select a,b FROM update_test where c = 'bar')
! WHERE a = 15;
! SELECT * FROM update_test;
! a | b | c
! -----+----+-----
! 100 | 20 |
! 11 | 41 | car
! | | foo
! (3 rows)
!
-- if an alias for the target table is specified, don't allow references
-- to the original table name
BEGIN;
Index: src/test/regress/sql/update.sql
===================================================================
RCS file: /repositories/postgreshome/cvs/pgsql/src/test/regress/sql/update.sql,v
retrieving revision 1.5
diff -c -r1.5 update.sql
*** src/test/regress/sql/update.sql 3 Sep 2006 22:37:06 -0000 1.5
--- src/test/regress/sql/update.sql 11 Apr 2007 08:04:33 -0000
***************
*** 46,54 ****
-- fail, multi assignment to same column:
UPDATE update_test SET (c,b) = ('car', a+b), b = a + 1 WHERE a = 10;
! -- XXX this should work, but doesn't yet:
! UPDATE update_test SET (a,b) = (select a,b FROM update_test where c = 'foo')
! WHERE a = 10;
-- if an alias for the target table is specified, don't allow references
-- to the original table name
--- 46,78 ----
-- fail, multi assignment to same column:
UPDATE update_test SET (c,b) = ('car', a+b), b = a + 1 WHERE a = 10;
! INSERT INTO update_test VALUES (5, 10, 'foo');
! -- update using subselect:
! UPDATE update_test SET (a,b) = (select a+5,b FROM update_test where c = 'foo')
! WHERE a = 5;
! SELECT * FROM update_test;
!
! -- fail update when subselect returns multiple rows:
! UPDATE update_test SET (a,b) = (select a,b FROM update_test);
!
! -- combination of multiple subselects in UPDATEs
! UPDATE update_test SET a= (select a FROM update_test where c = 'foo'),
! b = (select b+100 FROM update_test where c = 'foo') WHERE a = 10;
! SELECT * FROM update_test;
!
! -- combination of normal SET with subselects
! UPDATE update_test SET a= (select a+5 FROM update_test where c = 'foo'),
! b = 100 WHERE a = 10;
! SELECT * FROM update_test;
!
! -- fail, multi assignment to same column when subselects are involved
! UPDATE update_test SET (c,b) = ('car', a+b),
! b = (select b from update_test where c = 'foo') WHERE a = 10;
!
! -- update using subselect, subquery should return NULLs if there are no matches
! UPDATE update_test SET (a,b) = (select a,b FROM update_test where c = 'bar')
! WHERE a = 15;
! SELECT * FROM update_test;
-- if an alias for the target table is specified, don't allow references
-- to the original table name
---------------------------(end of broadcast)---------------------------
TIP 4: Have you searched our list archives?
http://archives.postgresql.org