diff options
| author | Tom Lane <tgl@sss.pgh.pa.us> | 2004-06-09 19:08:20 +0000 |
|---|---|---|
| committer | Tom Lane <tgl@sss.pgh.pa.us> | 2004-06-09 19:08:20 +0000 |
| commit | 7e64dbc6b5e516a2510ae41c8c7999d1d8d25872 (patch) | |
| tree | c819b78903b490e720b4c20969ed6cf8816889d1 /src/backend/parser | |
| parent | 3a0df651da253879bf133a8556853acfb1f664fd (diff) | |
| download | postgresql-7e64dbc6b5e516a2510ae41c8c7999d1d8d25872.tar.gz | |
Support assignment to subfields of composite columns in UPDATE and INSERT.
As a side effect, cause subscripts in INSERT targetlists to do something
more or less sensible; previously we evaluated such subscripts and then
effectively ignored them. Another side effect is that UPDATE-ing an
element or slice of an array value that is NULL now produces a non-null
result, namely an array containing just the assigned-to positions.
Diffstat (limited to 'src/backend/parser')
| -rw-r--r-- | src/backend/parser/analyze.c | 3 | ||||
| -rw-r--r-- | src/backend/parser/gram.y | 275 | ||||
| -rw-r--r-- | src/backend/parser/parse_clause.c | 5 | ||||
| -rw-r--r-- | src/backend/parser/parse_expr.c | 105 | ||||
| -rw-r--r-- | src/backend/parser/parse_node.c | 125 | ||||
| -rw-r--r-- | src/backend/parser/parse_target.c | 400 |
6 files changed, 626 insertions, 287 deletions
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 104716bf45..f6cdf96e29 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.303 2004/06/04 03:24:04 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.304 2004/06/09 19:08:16 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -2461,6 +2461,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt) if (origTargetList == NULL) elog(ERROR, "UPDATE target count mismatch --- internal error"); origTarget = (ResTarget *) lfirst(origTargetList); + Assert(IsA(origTarget, ResTarget)); updateTargetListEntry(pstate, tle, origTarget->name, attnameAttNum(pstate->p_target_relation, diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 61a3bd477e..07a882f3db 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.460 2004/06/02 21:01:09 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.461 2004/06/09 19:08:17 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -75,6 +75,7 @@ static bool QueryIsRule = FALSE; */ /*#define __YYSCLASS*/ +static Node *makeColumnRef(char *relname, List *indirection); static Node *makeTypeCast(Node *arg, TypeName *typename); static Node *makeStringConst(char *str, TypeName *typename); static Node *makeIntConst(int val); @@ -84,6 +85,7 @@ static Node *makeRowNullTest(NullTestType test, RowExpr *row); static DefElem *makeDefElem(char *name, Node *arg); static A_Const *makeBoolAConst(bool state); static FuncCall *makeOverlaps(List *largs, List *rargs); +static List *check_func_name(List *names); static List *extractArgTypes(List *parameters); static SelectStmt *findLeftmostSelect(SelectStmt *node); static void insertSelectOptions(SelectStmt *stmt, @@ -110,7 +112,6 @@ static void doNegateFloat(Value *v); List *list; Node *node; Value *value; - ColumnRef *columnref; ObjectType objtype; TypeName *typnam; @@ -221,9 +222,9 @@ static void doNegateFloat(Value *v); sort_clause opt_sort_clause sortby_list index_params name_list from_clause from_list opt_array_bounds qualified_name_list any_name any_name_list - any_operator expr_list dotted_name attrs + any_operator expr_list attrs target_list update_target_list insert_column_list - insert_target_list def_list opt_indirection + insert_target_list def_list indirection opt_indirection group_clause TriggerFuncArgs select_limit opt_select_limit opclass_item_list transaction_mode_list transaction_mode_list_or_empty @@ -274,8 +275,8 @@ static void doNegateFloat(Value *v); %type <node> TableElement ConstraintElem TableFuncElement %type <node> columnDef %type <defelt> def_elem -%type <node> def_arg columnElem where_clause insert_column_item - a_expr b_expr c_expr AexprConst +%type <node> def_arg columnElem where_clause + a_expr b_expr c_expr AexprConst indirection_el columnref in_expr having_clause func_table array_expr %type <list> row type_list array_expr_list %type <node> case_expr case_arg when_clause case_default @@ -284,14 +285,13 @@ static void doNegateFloat(Value *v); %type <list> OptCreateAs CreateAsList %type <node> CreateAsElement %type <value> NumericOnly FloatOnly IntegerOnly -%type <columnref> columnref %type <alias> alias_clause %type <sortby> sortby %type <ielem> index_elem %type <node> table_ref %type <jexpr> joined_table %type <range> relation_expr -%type <target> target_el insert_target_el update_target_el +%type <target> target_el insert_target_el update_target_el insert_column_item %type <typnam> Typename SimpleTypename ConstTypename GenericType Numeric opt_float @@ -2102,12 +2102,11 @@ opt_trusted: /* This ought to be just func_name, but that causes reduce/reduce conflicts * (CREATE LANGUAGE is the only place where func_name isn't followed by '('). - * Work around by using name and dotted_name separately. + * Work around by using simple names, instead. */ handler_name: - name - { $$ = list_make1(makeString($1)); } - | dotted_name { $$ = $1; } + name { $$ = list_make1(makeString($1)); } + | name attrs { $$ = lcons(makeString($1), $2); } ; opt_lancompiler: @@ -2578,9 +2577,20 @@ any_name_list: ; any_name: ColId { $$ = list_make1(makeString($1)); } - | dotted_name { $$ = $1; } + | ColId attrs { $$ = lcons(makeString($1), $2); } ; +/* + * The slightly convoluted way of writing this production avoids reduce/reduce + * errors against indirection_el. + */ +attrs: '.' attr_name + { $$ = list_make1(makeString($2)); } + | '.' attr_name attrs + { $$ = lcons(makeString($2), $3); } + ; + + /***************************************************************************** * * QUERY: @@ -4387,7 +4397,8 @@ insert_rest: ; insert_column_list: - insert_column_item { $$ = list_make1($1); } + insert_column_item + { $$ = list_make1($1); } | insert_column_list ',' insert_column_item { $$ = lappend($1, $3); } ; @@ -4395,11 +4406,10 @@ insert_column_list: insert_column_item: ColId opt_indirection { - ResTarget *n = makeNode(ResTarget); - n->name = $1; - n->indirection = $2; - n->val = NULL; - $$ = (Node *)n; + $$ = makeNode(ResTarget); + $$->name = $1; + $$->indirection = $2; + $$->val = NULL; } ; @@ -6203,35 +6213,28 @@ b_expr: c_expr * inside parentheses, such as function arguments; that cannot introduce * ambiguity to the b_expr syntax. */ -c_expr: columnref { $$ = (Node *) $1; } +c_expr: columnref { $$ = $1; } | AexprConst { $$ = $1; } - | PARAM attrs opt_indirection - { - /* - * PARAM without field names is considered a constant, - * but with 'em, it is not. Not very consistent ... - */ - ParamRef *n = makeNode(ParamRef); - n->number = $1; - n->fields = $2; - n->indirection = $3; - $$ = (Node *)n; - } - | '(' a_expr ')' attrs opt_indirection + | PARAM opt_indirection { - ExprFieldSelect *n = makeNode(ExprFieldSelect); - n->arg = $2; - n->fields = $4; - n->indirection = $5; - $$ = (Node *)n; + ParamRef *p = makeNode(ParamRef); + p->number = $1; + if ($2) + { + A_Indirection *n = makeNode(A_Indirection); + n->arg = (Node *) p; + n->indirection = $2; + $$ = (Node *) n; + } + else + $$ = (Node *) p; } | '(' a_expr ')' opt_indirection { if ($4) { - ExprFieldSelect *n = makeNode(ExprFieldSelect); + A_Indirection *n = makeNode(A_Indirection); n->arg = $2; - n->fields = NIL; n->indirection = $4; $$ = (Node *)n; } @@ -6806,25 +6809,6 @@ subquery_Op: */ ; -opt_indirection: - opt_indirection '[' a_expr ']' - { - A_Indices *ai = makeNode(A_Indices); - ai->lidx = NULL; - ai->uidx = $3; - $$ = lappend($1, ai); - } - | opt_indirection '[' a_expr ':' a_expr ']' - { - A_Indices *ai = makeNode(A_Indices); - ai->lidx = $3; - ai->uidx = $5; - $$ = lappend($1, ai); - } - | /*EMPTY*/ - { $$ = NIL; } - ; - expr_list: a_expr { $$ = list_make1($1); @@ -7050,42 +7034,58 @@ case_arg: a_expr { $$ = $1; } * references can be accepted. Note that when there are more than two * dotted names, the first name is not actually a relation name... */ -columnref: relation_name opt_indirection +columnref: relation_name { - $$ = makeNode(ColumnRef); - $$->fields = list_make1(makeString($1)); - $$->indirection = $2; + $$ = makeColumnRef($1, NIL); } - | dotted_name opt_indirection + | relation_name indirection { - $$ = makeNode(ColumnRef); - $$->fields = $1; - $$->indirection = $2; + $$ = makeColumnRef($1, $2); } ; -dotted_name: - relation_name attrs - { $$ = lcons(makeString($1), $2); } +indirection_el: + '.' attr_name + { + $$ = (Node *) makeString($2); + } + | '.' '*' + { + $$ = (Node *) makeString("*"); + } + | '[' a_expr ']' + { + A_Indices *ai = makeNode(A_Indices); + ai->lidx = NULL; + ai->uidx = $2; + $$ = (Node *) ai; + } + | '[' a_expr ':' a_expr ']' + { + A_Indices *ai = makeNode(A_Indices); + ai->lidx = $2; + ai->uidx = $4; + $$ = (Node *) ai; + } ; -attrs: '.' attr_name - { $$ = list_make1(makeString($2)); } - | '.' '*' - { $$ = list_make1(makeString("*")); } - | '.' attr_name attrs - { $$ = lcons(makeString($2), $3); } +indirection: + indirection_el { $$ = list_make1($1); } + | indirection indirection_el { $$ = lappend($1, $2); } + ; + +opt_indirection: + /*EMPTY*/ { $$ = NIL; } + | opt_indirection indirection_el { $$ = lappend($1, $2); } ; /***************************************************************************** * - * target lists + * target lists for SELECT, UPDATE, INSERT * *****************************************************************************/ -/* Target lists as found in SELECT ... and INSERT VALUES ( ... ) */ - target_list: target_el { $$ = list_make1($1); } | target_list ',' target_el { $$ = lappend($1, $3); } @@ -7110,7 +7110,7 @@ target_el: a_expr AS ColLabel { ColumnRef *n = makeNode(ColumnRef); n->fields = list_make1(makeString("*")); - n->indirection = NIL; + $$ = makeNode(ResTarget); $$->name = NULL; $$->indirection = NIL; @@ -7118,12 +7118,6 @@ target_el: a_expr AS ColLabel } ; -/* Target list as found in UPDATE table SET ... -| '(' row_ ')' = '(' row_ ')' -{ - $$ = NULL; -} - */ update_target_list: update_target_el { $$ = list_make1($1); } | update_target_list ',' update_target_el { $$ = lappend($1,$3); } @@ -7153,7 +7147,13 @@ insert_target_list: ; insert_target_el: - target_el { $$ = $1; } + a_expr + { + $$ = makeNode(ResTarget); + $$->name = NULL; + $$->indirection = NIL; + $$->val = (Node *)$1; + } | DEFAULT { $$ = makeNode(ResTarget); @@ -7188,26 +7188,26 @@ qualified_name: $$->schemaname = NULL; $$->relname = $1; } - | dotted_name + | relation_name attrs { $$ = makeNode(RangeVar); - switch (list_length($1)) + switch (list_length($2)) { - case 2: + case 1: $$->catalogname = NULL; - $$->schemaname = strVal(linitial($1)); - $$->relname = strVal(lsecond($1)); + $$->schemaname = $1; + $$->relname = strVal(linitial($2)); break; - case 3: - $$->catalogname = strVal(linitial($1)); - $$->schemaname = strVal(lsecond($1)); - $$->relname = strVal(lthird($1)); + case 2: + $$->catalogname = $1; + $$->schemaname = strVal(linitial($2)); + $$->relname = strVal(lsecond($2)); break; default: ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("improper qualified name (too many dotted names): %s", - NameListToString($1)))); + NameListToString(lcons(makeString($1), $2))))); break; } } @@ -7234,9 +7234,18 @@ index_name: ColId { $$ = $1; }; file_name: Sconst { $$ = $1; }; +/* + * The production for a qualified func_name has to exactly match the + * production for a qualified columnref, because we cannot tell which we + * are parsing until we see what comes after it ('(' for a func_name, + * anything else for a columnref). Therefore we allow 'indirection' which + * may contain subscripts, and reject that case in the C code. (If we + * ever implement SQL99-like methods, such syntax may actually become legal!) + */ func_name: function_name { $$ = list_make1(makeString($1)); } - | dotted_name { $$ = $1; } + | relation_name indirection + { $$ = check_func_name(lcons(makeString($1), $2)); } ; @@ -7325,14 +7334,6 @@ AexprConst: Iconst n->typename->typmod = INTERVAL_TYPMOD($3, $6); $$ = (Node *)n; } - | PARAM opt_indirection - { - ParamRef *n = makeNode(ParamRef); - n->number = $1; - n->fields = NIL; - n->indirection = $2; - $$ = (Node *)n; - } | TRUE_P { $$ = (Node *)makeBoolAConst(TRUE); @@ -7782,6 +7783,48 @@ SpecialRuleRelation: %% static Node * +makeColumnRef(char *relname, List *indirection) +{ + /* + * Generate a ColumnRef node, with an A_Indirection node added if there + * is any subscripting in the specified indirection list. However, + * any field selection at the start of the indirection list must be + * transposed into the "fields" part of the ColumnRef node. + */ + ColumnRef *c = makeNode(ColumnRef); + int nfields = 0; + ListCell *l; + + foreach(l, indirection) + { + if (IsA(lfirst(l), A_Indices)) + { + A_Indirection *i = makeNode(A_Indirection); + + if (nfields == 0) + { + /* easy case - all indirection goes to A_Indirection */ + c->fields = list_make1(makeString(relname)); + i->indirection = indirection; + } + else + { + /* got to split the list in two */ + i->indirection = list_copy_tail(indirection, nfields); + indirection = list_truncate(indirection, nfields); + c->fields = lcons(makeString(relname), indirection); + } + i->arg = (Node *) c; + return (Node *) i; + } + nfields++; + } + /* No subscripting, so all indirection gets added to field list */ + c->fields = lcons(makeString(relname), indirection); + return (Node *) c; +} + +static Node * makeTypeCast(Node *arg, TypeName *typename) { /* @@ -7945,6 +7988,26 @@ makeOverlaps(List *largs, List *rargs) return n; } +/* check_func_name --- check the result of func_name production + * + * It's easiest to let the grammar production for func_name allow subscripts + * and '*', which we then must reject here. + */ +static List * +check_func_name(List *names) +{ + ListCell *i; + + foreach(i, names) + { + if (!IsA(lfirst(i), String)) + yyerror("syntax error"); + else if (strcmp(strVal(lfirst(i)), "*") == 0) + yyerror("syntax error"); + } + return names; +} + /* extractArgTypes() * Given a list of FunctionParameter nodes, extract a list of just the * argument types (TypeNames). Most of the productions using func_args diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index a0901662b8..a2cd7dccc1 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.131 2004/05/30 23:40:34 neilc Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.132 2004/06/09 19:08:17 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1122,8 +1122,7 @@ findTargetlistEntry(ParseState *pstate, Node *node, List **tlist, int clause) *---------- */ if (IsA(node, ColumnRef) && - list_length(((ColumnRef *) node)->fields) == 1 && - ((ColumnRef *) node)->indirection == NIL) + list_length(((ColumnRef *) node)->fields) == 1) { char *name = strVal(linitial(((ColumnRef *) node)->fields)); diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 5dbac6338b..3b4ad7cf8a 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.172 2004/05/30 23:40:35 neilc Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.173 2004/06/09 19:08:17 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -100,7 +100,6 @@ transformExpr(ParseState *pstate, Node *expr) int paramno = pref->number; ParseState *toppstate; Param *param; - ListCell *fields; /* * Find topmost ParseState, which is where paramtype info @@ -148,18 +147,6 @@ transformExpr(ParseState *pstate, Node *expr) param->paramid = (AttrNumber) paramno; param->paramtype = toppstate->p_paramtypes[paramno - 1]; result = (Node *) param; - - /* handle qualification, if any */ - foreach(fields, pref->fields) - { - result = ParseFuncOrColumn(pstate, - list_make1(lfirst(fields)), - list_make1(result), - false, false, true); - } - /* handle subscripts, if any */ - result = transformIndirection(pstate, result, - pref->indirection); break; } case T_A_Const: @@ -173,23 +160,13 @@ transformExpr(ParseState *pstate, Node *expr) con->typename); break; } - case T_ExprFieldSelect: + case T_A_Indirection: { - ExprFieldSelect *efs = (ExprFieldSelect *) expr; - ListCell *fields; + A_Indirection *ind = (A_Indirection *) expr; - result = transformExpr(pstate, efs->arg); - /* handle qualification, if any */ - foreach(fields, efs->fields) - { - result = ParseFuncOrColumn(pstate, - list_make1(lfirst(fields)), - list_make1(result), - false, false, true); - } - /* handle subscripts, if any */ + result = transformExpr(pstate, ind->arg); result = transformIndirection(pstate, result, - efs->indirection); + ind->indirection); break; } case T_TypeCast: @@ -961,6 +938,7 @@ transformExpr(ParseState *pstate, Node *expr) case T_NullIfExpr: case T_BoolExpr: case T_FieldSelect: + case T_FieldStore: case T_RelabelType: case T_CaseTestExpr: case T_CoerceToDomain: @@ -983,15 +961,55 @@ transformExpr(ParseState *pstate, Node *expr) static Node * transformIndirection(ParseState *pstate, Node *basenode, List *indirection) { - if (indirection == NIL) - return basenode; - return (Node *) transformArraySubscripts(pstate, - basenode, - exprType(basenode), - exprTypmod(basenode), - indirection, - false, - NULL); + Node *result = basenode; + List *subscripts = NIL; + ListCell *i; + + /* + * We have to split any field-selection operations apart from + * subscripting. Adjacent A_Indices nodes have to be treated + * as a single multidimensional subscript operation. + */ + foreach(i, indirection) + { + Node *n = lfirst(i); + + if (IsA(n, A_Indices)) + { + subscripts = lappend(subscripts, n); + } + else + { + Assert(IsA(n, String)); + + /* process subscripts before this field selection */ + if (subscripts) + result = (Node *) transformArraySubscripts(pstate, + result, + exprType(result), + InvalidOid, + -1, + subscripts, + NULL); + subscripts = NIL; + + result = ParseFuncOrColumn(pstate, + list_make1(n), + list_make1(result), + false, false, true); + } + } + /* process trailing subscripts, if any */ + if (subscripts) + result = (Node *) transformArraySubscripts(pstate, + result, + exprType(result), + InvalidOid, + -1, + subscripts, + NULL); + + return result; } static Node * @@ -1051,17 +1069,15 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref) } /* - * Try to find the name as a relation ... but not if - * subscripts appear. Note also that only relations - * already entered into the rangetable will be + * Try to find the name as a relation. Note that only + * relations already entered into the rangetable will be * recognized. * * This is a hack for backwards compatibility with * PostQUEL-inspired syntax. The preferred form now * is "rel.*". */ - if (cref->indirection == NIL && - refnameRangeTblEntry(pstate, NULL, name, + if (refnameRangeTblEntry(pstate, NULL, name, &levels_up) != NULL) node = transformWholeRowRef(pstate, NULL, name); else @@ -1172,7 +1188,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref) break; } - return transformIndirection(pstate, node, cref->indirection); + return node; } /* @@ -1385,6 +1401,9 @@ exprType(Node *expr) case T_FieldSelect: type = ((FieldSelect *) expr)->resulttype; break; + case T_FieldStore: + type = ((FieldStore *) expr)->resulttype; + break; case T_RelabelType: type = ((RelabelType *) expr)->resulttype; break; diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c index c46c27481a..c95fe6650d 100644 --- a/src/backend/parser/parse_node.c +++ b/src/backend/parser/parse_node.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_node.c,v 1.83 2004/05/26 04:41:30 neilc Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_node.c,v 1.84 2004/06/09 19:08:17 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -68,6 +68,39 @@ make_var(ParseState *pstate, RangeTblEntry *rte, int attrno) } /* + * transformArrayType() + * Get the element type of an array type in preparation for subscripting + */ +Oid +transformArrayType(Oid arrayType) +{ + Oid elementType; + HeapTuple type_tuple_array; + Form_pg_type type_struct_array; + + /* Get the type tuple for the array */ + type_tuple_array = SearchSysCache(TYPEOID, + ObjectIdGetDatum(arrayType), + 0, 0, 0); + if (!HeapTupleIsValid(type_tuple_array)) + elog(ERROR, "cache lookup failed for type %u", arrayType); + type_struct_array = (Form_pg_type) GETSTRUCT(type_tuple_array); + + /* needn't check typisdefined since this will fail anyway */ + + elementType = type_struct_array->typelem; + if (elementType == InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot subscript type %s because it is not an array", + format_type_be(arrayType)))); + + ReleaseSysCache(type_tuple_array); + + return elementType; +} + +/* * transformArraySubscripts() * Transform array subscripting. This is used for both * array fetch and array assignment. @@ -83,68 +116,49 @@ make_var(ParseState *pstate, RangeTblEntry *rte, int attrno) * * pstate Parse state * arrayBase Already-transformed expression for the array as a whole - * (may be NULL if we are handling an INSERT) - * arrayType OID of array's datatype - * arrayTypMod typmod to be applied to array elements + * arrayType OID of array's datatype (should match type of arrayBase) + * elementType OID of array's element type (fetch with transformArrayType, + * or pass InvalidOid to do it here) + * elementTypMod typmod to be applied to array elements (if storing) * indirection Untransformed list of subscripts (must not be NIL) - * forceSlice If true, treat subscript as array slice in all cases * assignFrom NULL for array fetch, else transformed expression for source. */ ArrayRef * transformArraySubscripts(ParseState *pstate, Node *arrayBase, Oid arrayType, - int32 arrayTypMod, + Oid elementType, + int32 elementTypMod, List *indirection, - bool forceSlice, Node *assignFrom) { - Oid elementType, - resultType; - HeapTuple type_tuple_array; - Form_pg_type type_struct_array; - bool isSlice = forceSlice; + Oid resultType; + bool isSlice = false; List *upperIndexpr = NIL; List *lowerIndexpr = NIL; ListCell *idx; ArrayRef *aref; - /* Get the type tuple for the array */ - type_tuple_array = SearchSysCache(TYPEOID, - ObjectIdGetDatum(arrayType), - 0, 0, 0); - if (!HeapTupleIsValid(type_tuple_array)) - elog(ERROR, "cache lookup failed for type %u", arrayType); - type_struct_array = (Form_pg_type) GETSTRUCT(type_tuple_array); - - elementType = type_struct_array->typelem; - if (elementType == InvalidOid) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("cannot subscript type %s because it is not an array", - format_type_be(arrayType)))); + /* Caller may or may not have bothered to determine elementType */ + if (!OidIsValid(elementType)) + elementType = transformArrayType(arrayType); /* * A list containing only single subscripts refers to a single array * element. If any of the items are double subscripts (lower:upper), * then the subscript expression means an array slice operation. In * this case, we supply a default lower bound of 1 for any items that - * contain only a single subscript. The forceSlice parameter forces us - * to treat the operation as a slice, even if no lower bounds are - * mentioned. Otherwise, we have to prescan the indirection list to - * see if there are any double subscripts. + * contain only a single subscript. We have to prescan the indirection + * list to see if there are any double subscripts. */ - if (!isSlice) + foreach(idx, indirection) { - foreach(idx, indirection) - { - A_Indices *ai = (A_Indices *) lfirst(idx); + A_Indices *ai = (A_Indices *) lfirst(idx); - if (ai->lidx != NULL) - { - isSlice = true; - break; - } + if (ai->lidx != NULL) + { + isSlice = true; + break; } } @@ -166,6 +180,7 @@ transformArraySubscripts(ParseState *pstate, A_Indices *ai = (A_Indices *) lfirst(idx); Node *subexpr; + Assert(IsA(ai, A_Indices)); if (isSlice) { if (ai->lidx) @@ -209,28 +224,26 @@ transformArraySubscripts(ParseState *pstate, /* * If doing an array store, coerce the source value to the right type. + * (This should agree with the coercion done by updateTargetListEntry.) */ if (assignFrom != NULL) { Oid typesource = exprType(assignFrom); Oid typeneeded = isSlice ? arrayType : elementType; - if (typesource != InvalidOid) - { - assignFrom = coerce_to_target_type(pstate, - assignFrom, typesource, - typeneeded, arrayTypMod, - COERCION_ASSIGNMENT, - COERCE_IMPLICIT_CAST); - if (assignFrom == NULL) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("array assignment requires type %s" - " but expression is of type %s", - format_type_be(typeneeded), - format_type_be(typesource)), - errhint("You will need to rewrite or cast the expression."))); - } + assignFrom = coerce_to_target_type(pstate, + assignFrom, typesource, + typeneeded, elementTypMod, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST); + if (assignFrom == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("array assignment requires type %s" + " but expression is of type %s", + format_type_be(typeneeded), + format_type_be(typesource)), + errhint("You will need to rewrite or cast the expression."))); } /* @@ -245,8 +258,6 @@ transformArraySubscripts(ParseState *pstate, aref->refexpr = (Expr *) arrayBase; aref->refassgnexpr = (Expr *) assignFrom; - ReleaseSysCache(type_tuple_array); - return aref; } diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 3856005fab..e0f0e6c930 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_target.c,v 1.120 2004/06/01 03:28:48 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_target.c,v 1.121 2004/06/09 19:08:17 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -25,9 +25,18 @@ #include "parser/parse_target.h" #include "parser/parse_type.h" #include "utils/builtins.h" +#include "utils/lsyscache.h" static void markTargetListOrigin(ParseState *pstate, Resdom *res, Var *var); +static Node *transformAssignmentIndirection(ParseState *pstate, + Node *basenode, + const char *targetName, + bool targetIsArray, + Oid targetTypeId, + int32 targetTypMod, + ListCell *indirection, + Node *rhs); static List *ExpandAllTables(ParseState *pstate); static char *FigureColname(Node *node); static int FigureColnameInternal(Node *node, char **name); @@ -87,7 +96,7 @@ transformTargetEntry(ParseState *pstate, * Turns a list of ResTarget's into a list of TargetEntry's. * * At this point, we don't care whether we are doing SELECT, INSERT, - * or UPDATE; we just transform the given expressions. + * or UPDATE; we just transform the given expressions (the "val" fields). */ List * transformTargetList(ParseState *pstate, List *targetlist) @@ -284,14 +293,14 @@ markTargetListOrigin(ParseState *pstate, Resdom *res, Var *var) * This is used in INSERT and UPDATE statements only. It prepares a * TargetEntry for assignment to a column of the target table. * This includes coercing the given value to the target column's type - * (if necessary), and dealing with any subscripts attached to the target - * column itself. + * (if necessary), and dealing with any subfield names or subscripts + * attached to the target column itself. * * pstate parse state * tle target list entry to be modified * colname target column name (ie, name of attribute to be assigned to) * attrno target attribute number - * indirection subscripts for target column, if any + * indirection subscripts/field names for target column, if any */ void updateTargetListEntry(ParseState *pstate, @@ -320,8 +329,8 @@ updateTargetListEntry(ParseState *pstate, * type/typmod into it so that exprType will report the right things. * (We expect that the eventually substituted default expression will * in fact have this type and typmod.) Also, reject trying to update - * an array element with DEFAULT, since there can't be any default for - * individual elements of a column. + * a subfield or array element with DEFAULT, since there can't be any + * default for portions of a column. */ if (tle->expr && IsA(tle->expr, SetToDefault)) { @@ -330,82 +339,81 @@ updateTargetListEntry(ParseState *pstate, def->typeId = attrtype; def->typeMod = attrtypmod; if (indirection) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot set an array element to DEFAULT"))); + { + if (IsA(linitial(indirection), A_Indices)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot set an array element to DEFAULT"))); + else + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot set a subfield to DEFAULT"))); + } } /* Now we can use exprType() safely. */ type_id = exprType((Node *) tle->expr); /* - * If there are subscripts on the target column, prepare an array - * assignment expression. This will generate an array value that the - * source value has been inserted into, which can then be placed in - * the new tuple constructed by INSERT or UPDATE. Note that - * transformArraySubscripts takes care of type coercion. + * If there is indirection on the target column, prepare an array or + * subfield assignment expression. This will generate a new column value + * that the source value has been inserted into, which can then be placed + * in the new tuple constructed by INSERT or UPDATE. */ if (indirection) { - Node *arrayBase; - ArrayRef *aref; + Node *colVar; if (pstate->p_is_insert) { /* - * The command is INSERT INTO table (arraycol[subscripts]) ... - * so there is not really a source array value to work with. - * Let the executor do something reasonable, if it can. Notice - * that we force transformArraySubscripts to treat the - * subscripting op as an array-slice op below, so the source - * data will have been coerced to the array type. + * The command is INSERT INTO table (col.something) ... + * so there is not really a source value to work with. + * Insert a NULL constant as the source value. */ - arrayBase = NULL; /* signal there is no source array */ + colVar = (Node *) makeNullConst(attrtype); } else { /* - * Build a Var for the array to be updated. + * Build a Var for the column to be updated. */ - arrayBase = (Node *) make_var(pstate, - pstate->p_target_rangetblentry, - attrno); + colVar = (Node *) make_var(pstate, + pstate->p_target_rangetblentry, + attrno); } - aref = transformArraySubscripts(pstate, - arrayBase, - attrtype, - attrtypmod, - indirection, - pstate->p_is_insert, - (Node *) tle->expr); - tle->expr = (Expr *) aref; + tle->expr = (Expr *) + transformAssignmentIndirection(pstate, + colVar, + colname, + false, + attrtype, + attrtypmod, + list_head(indirection), + (Node *) tle->expr); } else { /* - * For normal non-subscripted target column, do type checking and - * coercion. But accept InvalidOid, which indicates the source is - * a NULL constant. (XXX is that still true?) + * For normal non-qualified target column, do type checking and + * coercion. */ - if (type_id != InvalidOid) - { - tle->expr = (Expr *) - coerce_to_target_type(pstate, - (Node *) tle->expr, type_id, - attrtype, attrtypmod, - COERCION_ASSIGNMENT, - COERCE_IMPLICIT_CAST); - if (tle->expr == NULL) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("column \"%s\" is of type %s" - " but expression is of type %s", - colname, - format_type_be(attrtype), - format_type_be(type_id)), - errhint("You will need to rewrite or cast the expression."))); - } + tle->expr = (Expr *) + coerce_to_target_type(pstate, + (Node *) tle->expr, type_id, + attrtype, attrtypmod, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST); + if (tle->expr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("column \"%s\" is of type %s" + " but expression is of type %s", + colname, + format_type_be(attrtype), + format_type_be(type_id)), + errhint("You will need to rewrite or cast the expression."))); } /* @@ -425,6 +433,208 @@ updateTargetListEntry(ParseState *pstate, resnode->resname = colname; } +/* + * Process indirection (field selection or subscripting) of the target + * column in INSERT/UPDATE. This routine recurses for multiple levels + * of indirection --- but note that several adjacent A_Indices nodes in + * the indirection list are treated as a single multidimensional subscript + * operation. + * + * In the initial call, basenode is a Var for the target column in UPDATE, + * or a null Const of the target's type in INSERT. In recursive calls, + * basenode is NULL, indicating that a substitute node should be consed up if + * needed. + * + * targetName is the name of the field or subfield we're assigning to, and + * targetIsArray is true if we're subscripting it. These are just for + * error reporting. + * + * targetTypeId and targetTypMod indicate the datatype of the object to + * be assigned to (initially the target column, later some subobject). + * + * indirection is the sublist remaining to process. When it's NULL, we're + * done recursing and can just coerce and return the RHS. + * + * rhs is the already-transformed value to be assigned; note it has not been + * coerced to any particular type. + */ +static Node * +transformAssignmentIndirection(ParseState *pstate, + Node *basenode, + const char *targetName, + bool targetIsArray, + Oid targetTypeId, + int32 targetTypMod, + ListCell *indirection, + Node *rhs) +{ + Node *result; + List *subscripts = NIL; + bool isSlice = false; + ListCell *i; + + if (indirection && !basenode) + { + /* Set up a substitution. We reuse CaseTestExpr for this. */ + CaseTestExpr *ctest = makeNode(CaseTestExpr); + + ctest->typeId = targetTypeId; + ctest->typeMod = targetTypMod; + basenode = (Node *) ctest; + } + + /* + * We have to split any field-selection operations apart from + * subscripting. Adjacent A_Indices nodes have to be treated + * as a single multidimensional subscript operation. + */ + for_each_cell(i, indirection) + { + Node *n = lfirst(i); + + if (IsA(n, A_Indices)) + { + subscripts = lappend(subscripts, n); + if (((A_Indices *) n)->lidx != NULL) + isSlice = true; + } + else + { + FieldStore *fstore; + Oid typrelid; + AttrNumber attnum; + Oid fieldTypeId; + int32 fieldTypMod; + + Assert(IsA(n, String)); + + /* process subscripts before this field selection */ + if (subscripts) + { + Oid elementTypeId = transformArrayType(targetTypeId); + Oid typeNeeded = isSlice ? targetTypeId : elementTypeId; + + /* recurse to create appropriate RHS for array assign */ + rhs = transformAssignmentIndirection(pstate, + NULL, + targetName, + true, + typeNeeded, + targetTypMod, + i, + rhs); + /* process subscripts */ + return (Node *) transformArraySubscripts(pstate, + basenode, + targetTypeId, + elementTypeId, + targetTypMod, + subscripts, + rhs); + } + + /* No subscripts, so can process field selection here */ + + typrelid = typeidTypeRelid(targetTypeId); + if (!typrelid) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot assign to a column of type %s because it is not a composite type", + format_type_be(targetTypeId)))); + + attnum = get_attnum(typrelid, strVal(n)); + if (attnum == InvalidAttrNumber) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" not found in data type %s", + strVal(n), format_type_be(targetTypeId)))); + if (attnum < 0) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("cannot assign to system column \"%s\"", + strVal(n)))); + + get_atttypetypmod(typrelid, attnum, + &fieldTypeId, &fieldTypMod); + + /* recurse to create appropriate RHS for field assign */ + rhs = transformAssignmentIndirection(pstate, + NULL, + strVal(n), + false, + fieldTypeId, + fieldTypMod, + lnext(i), + rhs); + + /* and build a FieldStore node */ + fstore = makeNode(FieldStore); + fstore->arg = (Expr *) basenode; + fstore->newvals = list_make1(rhs); + fstore->fieldnums = list_make1_int(attnum); + fstore->resulttype = targetTypeId; + + return (Node *) fstore; + } + } + + /* process trailing subscripts, if any */ + if (subscripts) + { + Oid elementTypeId = transformArrayType(targetTypeId); + Oid typeNeeded = isSlice ? targetTypeId : elementTypeId; + + /* recurse to create appropriate RHS for array assign */ + rhs = transformAssignmentIndirection(pstate, + NULL, + targetName, + true, + typeNeeded, + targetTypMod, + NULL, + rhs); + /* process subscripts */ + return (Node *) transformArraySubscripts(pstate, + basenode, + targetTypeId, + elementTypeId, + targetTypMod, + subscripts, + rhs); + } + + /* base case: just coerce RHS to match target type ID */ + + result = coerce_to_target_type(pstate, + rhs, exprType(rhs), + targetTypeId, targetTypMod, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST); + if (result == NULL) + { + if (targetIsArray) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("array assignment to \"%s\" requires type %s" + " but expression is of type %s", + targetName, + format_type_be(targetTypeId), + format_type_be(exprType(rhs))), + errhint("You will need to rewrite or cast the expression."))); + else + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("subfield \"%s\" is of type %s" + " but expression is of type %s", + targetName, + format_type_be(targetTypeId), + format_type_be(exprType(rhs))), + errhint("You will need to rewrite or cast the expression."))); + } + + return result; +} + /* * checkInsertTargets - @@ -466,21 +676,42 @@ checkInsertTargets(ParseState *pstate, List *cols, List **attrnos) /* * Do initial validation of user-supplied INSERT column list. */ + List *wholecols = NIL; ListCell *tl; foreach(tl, cols) { - char *name = ((ResTarget *) lfirst(tl))->name; + ResTarget *col = (ResTarget *) lfirst(tl); + char *name = col->name; int attrno; /* Lookup column name, ereport on failure */ attrno = attnameAttNum(pstate->p_target_relation, name, false); - /* Check for duplicates */ - if (list_member_int(*attrnos, attrno)) - ereport(ERROR, - (errcode(ERRCODE_DUPLICATE_COLUMN), - errmsg("column \"%s\" specified more than once", - name))); + + /* + * Check for duplicates, but only of whole columns --- we + * allow INSERT INTO foo (col.subcol1, col.subcol2) + */ + if (col->indirection == NIL) + { + /* whole column; must not have any other assignment */ + if (list_member_int(*attrnos, attrno)) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_COLUMN), + errmsg("column \"%s\" specified more than once", + name))); + wholecols = lappend_int(wholecols, attrno); + } + else + { + /* partial column; must not have any whole assignment */ + if (list_member_int(wholecols, attrno)) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_COLUMN), + errmsg("column \"%s\" specified more than once", + name))); + } + *attrnos = lappend_int(*attrnos, attrno); } } @@ -572,30 +803,45 @@ FigureColnameInternal(Node *node, char **name) { case T_ColumnRef: { - char *cname = strVal(llast(((ColumnRef *) node)->fields)); + char *fname = NULL; + ListCell *l; - if (strcmp(cname, "*") != 0) + /* find last field name, if any, ignoring "*" */ + foreach(l, ((ColumnRef *) node)->fields) { - *name = cname; + Node *i = lfirst(l); + + if (strcmp(strVal(i), "*") != 0) + fname = strVal(i); + } + if (fname) + { + *name = fname; return 2; } } break; - case T_ExprFieldSelect: + case T_A_Indirection: { - ExprFieldSelect *efs = (ExprFieldSelect *) node; + A_Indirection *ind = (A_Indirection *) node; + char *fname = NULL; + ListCell *l; - if (efs->fields) + /* find last field name, if any, ignoring "*" */ + foreach(l, ind->indirection) { - char *fname = strVal(llast(efs->fields)); + Node *i = lfirst(l); - if (strcmp(fname, "*") != 0) - { - *name = fname; - return 2; - } + if (IsA(i, String) && + strcmp(strVal(i), "*") != 0) + fname = strVal(i); + } + if (fname) + { + *name = fname; + return 2; } - return FigureColnameInternal(efs->arg, name); + return FigureColnameInternal(ind->arg, name); } break; case T_FuncCall: |
