From 7e64dbc6b5e516a2510ae41c8c7999d1d8d25872 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Wed, 9 Jun 2004 19:08:20 +0000 Subject: [PATCH] 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. --- doc/src/sgml/ref/insert.sgml | 13 +- doc/src/sgml/ref/update.sgml | 7 +- doc/src/sgml/rowtypes.sgml | 66 +++- src/backend/executor/execQual.c | 238 ++++++++++++--- src/backend/nodes/copyfuncs.c | 35 ++- src/backend/nodes/equalfuncs.c | 26 +- src/backend/nodes/outfuncs.c | 85 +++++- src/backend/nodes/readfuncs.c | 47 ++- src/backend/optimizer/util/clauses.c | 33 +- src/backend/parser/analyze.c | 3 +- src/backend/parser/gram.y | 275 ++++++++++------- src/backend/parser/parse_clause.c | 5 +- src/backend/parser/parse_expr.c | 105 ++++--- src/backend/parser/parse_node.c | 125 ++++---- src/backend/parser/parse_target.c | 400 ++++++++++++++++++++----- src/backend/rewrite/rewriteHandler.c | 130 ++++++-- src/backend/utils/adt/ruleutils.c | 236 ++++++++++----- src/include/nodes/execnodes.h | 14 +- src/include/nodes/nodes.h | 6 +- src/include/nodes/parsenodes.h | 62 ++-- src/include/nodes/primnodes.h | 35 ++- src/include/parser/parse_node.h | 7 +- src/pl/plpgsql/src/pl_exec.c | 14 +- src/test/regress/expected/arrays.out | 31 +- src/test/regress/expected/rowtypes.out | 22 +- src/test/regress/sql/arrays.sql | 9 +- src/test/regress/sql/rowtypes.sql | 9 +- 27 files changed, 1466 insertions(+), 572 deletions(-) diff --git a/doc/src/sgml/ref/insert.sgml b/doc/src/sgml/ref/insert.sgml index f73c7fafc5..a77428d33e 100644 --- a/doc/src/sgml/ref/insert.sgml +++ b/doc/src/sgml/ref/insert.sgml @@ -1,5 +1,5 @@ @@ -73,6 +73,9 @@ INSERT INTO table [ ( The name of a column in table. + The column name can be qualified with a subfield name or array + subscript, if needed. (Inserting into only some fields of a + composite column leaves the other fields null.) @@ -184,13 +187,11 @@ INSERT INTO films SELECT * FROM tmp; -- Create an empty 3x3 gameboard for noughts-and-crosses --- (all of these commands create the same board) +-- (these commands create the same board) INSERT INTO tictactoe (game, board[1:3][1:3]) - VALUES (1,'{{"","",""},{},{"",""}}'); -INSERT INTO tictactoe (game, board[3][3]) - VALUES (2,'{}'); + VALUES (1,'{{"","",""},{"","",""},{"","",""}}'); INSERT INTO tictactoe (game, board) - VALUES (3,'{{,,},{,,},{,,}}'); + VALUES (2,'{{,,},{,,},{,,}}'); diff --git a/doc/src/sgml/ref/update.sgml b/doc/src/sgml/ref/update.sgml index 6a6cf99137..5695df1584 100644 --- a/doc/src/sgml/ref/update.sgml +++ b/doc/src/sgml/ref/update.sgml @@ -1,5 +1,5 @@ @@ -77,7 +77,10 @@ UPDATE [ ONLY ] table SET column - The name of a column in table. + The name of a column in table. + The column name can be qualified with a subfield name or array + subscript, if needed. diff --git a/doc/src/sgml/rowtypes.sgml b/doc/src/sgml/rowtypes.sgml index 4a5d013a0a..264b4c59a0 100644 --- a/doc/src/sgml/rowtypes.sgml +++ b/doc/src/sgml/rowtypes.sgml @@ -1,4 +1,4 @@ - + Composite Types @@ -66,6 +66,27 @@ SELECT price_extension(item, 10) FROM on_hand; + + + Whenever you create a table, a composite type is also automatically + created, with the same name as the table, to represent the table's + row type. For example, had we said + +CREATE TABLE inventory_item ( + name text, + supplier_id integer REFERENCES suppliers, + price numeric CHECK (price > 0) +); + + then the same inventory_item composite type shown above would + come into being as a + byproduct, and could be used just as above. Note however an important + restriction of the current implementation: since no constraints are + associated with a composite type, the constraints shown in the table + definition do not apply to values of the composite type + outside the table. (A partial workaround is to use domain + types as members of composite types.) + @@ -178,6 +199,49 @@ SELECT (my_func(...)).field FROM ... + + Modifying Composite Types + + + Here are some examples of the proper syntax for inserting and updating + composite columns. + First, inserting or updating a whole column: + + +INSERT INTO mytab (complex_col) VALUES((1.1,2.2)); + +UPDATE mytab SET complex_col = ROW(1.1,2.2) WHERE ...; + + + The first example omits ROW, the second uses it; we + could have done it either way. + + + + We can update an individual subfield of a composite column: + + +UPDATE mytab SET complex_col.r = (complex_col).r + 1 WHERE ...; + + + Notice here that we don't need to (and indeed cannot) + put parentheses around the column name appearing just after + SET, but we do need parentheses when referencing the same + column in the expression to the right of the equal sign. + + + + And we can specify subfields as targets for INSERT, too: + + +INSERT INTO mytab (complex_col.r, complex_col.i) VALUES(1.1, 2.2); + + + Had we not supplied values for all the subfields of the column, the + remaining subfields would have been filled with NULLs. + + + Composite Type Input and Output Syntax diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index 8ada5e6faf..e4362c38f5 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.163 2004/06/05 19:48:08 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.164 2004/06/09 19:08:14 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -118,6 +118,9 @@ static Datum ExecEvalCoerceToDomainValue(ExprState *exprstate, static Datum ExecEvalFieldSelect(FieldSelectState *fstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalFieldStore(FieldStoreState *fstate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalRelabelType(GenericExprState *exprstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); @@ -217,6 +220,7 @@ ExecEvalArrayRef(ArrayRefExprState *astate, ArrayType *array_source; ArrayType *resultArray; bool isAssignment = (arrayRef->refassgnexpr != NULL); + bool eisnull; ListCell *l; int i = 0, j = 0; @@ -224,39 +228,23 @@ ExecEvalArrayRef(ArrayRefExprState *astate, lower; int *lIndex; - /* Set default values for result flags: non-null, not a set result */ - *isNull = false; - if (isDone) - *isDone = ExprSingleResult; + array_source = (ArrayType *) + DatumGetPointer(ExecEvalExpr(astate->refexpr, + econtext, + isNull, + isDone)); - if (arrayRef->refexpr != NULL) + /* + * If refexpr yields NULL, and it's a fetch, then result is NULL. + * In the assignment case, we'll cons up something below. + */ + if (*isNull) { - array_source = (ArrayType *) - DatumGetPointer(ExecEvalExpr(astate->refexpr, - econtext, - isNull, - isDone)); - - /* - * If refexpr yields NULL, result is always NULL, for now anyway. - * (This means you cannot assign to an element or slice of an - * array that's NULL; it'll just stay NULL.) - */ - if (*isNull) + if (isDone && *isDone == ExprEndResult) + return (Datum) NULL; /* end of set result */ + if (!isAssignment) return (Datum) NULL; } - else - { - /* - * Empty refexpr indicates we are doing an INSERT into an array - * column. For now, we just take the refassgnexpr (which the - * parser will have ensured is an array value) and return it - * as-is, ignoring any subscripts that may have been supplied in - * the INSERT column list. This is a kluge, but it's not real - * clear what the semantics ought to be... - */ - array_source = NULL; - } foreach(l, astate->refupperindexpr) { @@ -270,14 +258,16 @@ ExecEvalArrayRef(ArrayRefExprState *astate, upper.indx[i++] = DatumGetInt32(ExecEvalExpr(eltstate, econtext, - isNull, + &eisnull, NULL)); /* If any index expr yields NULL, result is NULL or source array */ - if (*isNull) + if (eisnull) { - if (!isAssignment || array_source == NULL) + if (!isAssignment) + { + *isNull = true; return (Datum) NULL; - *isNull = false; + } return PointerGetDatum(array_source); } } @@ -296,18 +286,20 @@ ExecEvalArrayRef(ArrayRefExprState *astate, lower.indx[j++] = DatumGetInt32(ExecEvalExpr(eltstate, econtext, - isNull, + &eisnull, NULL)); /* * If any index expr yields NULL, result is NULL or source * array */ - if (*isNull) + if (eisnull) { - if (!isAssignment || array_source == NULL) + if (!isAssignment) + { + *isNull = true; return (Datum) NULL; - *isNull = false; + } return PointerGetDatum(array_source); } } @@ -321,26 +313,50 @@ ExecEvalArrayRef(ArrayRefExprState *astate, if (isAssignment) { - Datum sourceData = ExecEvalExpr(astate->refassgnexpr, - econtext, - isNull, - NULL); + Datum sourceData; + + /* + * Evaluate the value to be assigned into the array. + * + * XXX At some point we'll need to look into making the old value of + * the array element available via CaseTestExpr, as is done by + * ExecEvalFieldStore. This is not needed now but will be needed + * to support arrays of composite types; in an assignment to a field + * of an array member, the parser would generate a FieldStore that + * expects to fetch its input tuple via CaseTestExpr. + */ + sourceData = ExecEvalExpr(astate->refassgnexpr, + econtext, + &eisnull, + NULL); /* * For now, can't cope with inserting NULL into an array, so make * it a no-op per discussion above... */ + if (eisnull) + return PointerGetDatum(array_source); + + /* + * For an assignment, if all the subscripts and the input expression + * are non-null but the original array is null, then substitute an + * empty (zero-dimensional) array and proceed with the assignment. + * This only works for varlena arrays, though; for fixed-length + * array types we punt and return the null input array. + */ if (*isNull) { - if (array_source == NULL) - return (Datum) NULL; + if (astate->refattrlength > 0) /* fixed-length array? */ + return PointerGetDatum(array_source); + + array_source = construct_md_array(NULL, 0, NULL, NULL, + arrayRef->refelemtype, + astate->refelemlength, + astate->refelembyval, + astate->refelemalign); *isNull = false; - return PointerGetDatum(array_source); } - if (array_source == NULL) - return sourceData; /* XXX do something else? */ - if (lIndex == NULL) resultArray = array_set(array_source, i, upper.indx, @@ -2538,6 +2554,120 @@ ExecEvalFieldSelect(FieldSelectState *fstate, return result; } +/* ---------------------------------------------------------------- + * ExecEvalFieldStore + * + * Evaluate a FieldStore node. + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalFieldStore(FieldStoreState *fstate, + ExprContext *econtext, + bool *isNull, + ExprDoneCond *isDone) +{ + FieldStore *fstore = (FieldStore *) fstate->xprstate.expr; + HeapTuple tuple; + Datum tupDatum; + TupleDesc tupDesc; + Datum *values; + char *nulls; + Datum save_datum; + bool save_isNull; + ListCell *l1, + *l2; + + tupDatum = ExecEvalExpr(fstate->arg, econtext, isNull, isDone); + + if (isDone && *isDone == ExprEndResult) + return tupDatum; + + /* Lookup tupdesc if first time through or if type changes */ + tupDesc = fstate->argdesc; + if (tupDesc == NULL || + fstore->resulttype != tupDesc->tdtypeid) + { + MemoryContext oldcontext; + + tupDesc = lookup_rowtype_tupdesc(fstore->resulttype, -1); + /* Copy the tupdesc into query storage for safety */ + oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); + tupDesc = CreateTupleDescCopy(tupDesc); + if (fstate->argdesc) + FreeTupleDesc(fstate->argdesc); + fstate->argdesc = tupDesc; + MemoryContextSwitchTo(oldcontext); + } + + /* Allocate workspace */ + values = (Datum *) palloc(tupDesc->natts * sizeof(Datum)); + nulls = (char *) palloc(tupDesc->natts * sizeof(char)); + + if (!*isNull) + { + /* + * heap_deformtuple needs a HeapTuple not a bare HeapTupleHeader. + * We set all the fields in the struct just in case. + */ + HeapTupleHeader tuphdr; + HeapTupleData tmptup; + + tuphdr = DatumGetHeapTupleHeader(tupDatum); + tmptup.t_len = HeapTupleHeaderGetDatumLength(tuphdr); + ItemPointerSetInvalid(&(tmptup.t_self)); + tmptup.t_tableOid = InvalidOid; + tmptup.t_data = tuphdr; + + heap_deformtuple(&tmptup, tupDesc, values, nulls); + } + else + { + /* Convert null input tuple into an all-nulls row */ + memset(nulls, 'n', tupDesc->natts * sizeof(char)); + } + + /* Result is never null */ + *isNull = false; + + save_datum = econtext->caseValue_datum; + save_isNull = econtext->caseValue_isNull; + + forboth(l1, fstate->newvals, l2, fstore->fieldnums) + { + ExprState *newval = (ExprState *) lfirst(l1); + AttrNumber fieldnum = lfirst_int(l2); + bool eisnull; + + Assert(fieldnum > 0 && fieldnum <= tupDesc->natts); + + /* + * Use the CaseTestExpr mechanism to pass down the old value of the + * field being replaced; this is useful in case we have a nested field + * update situation. It's safe to reuse the CASE mechanism because + * there cannot be a CASE between here and where the value would be + * needed. + */ + econtext->caseValue_datum = values[fieldnum - 1]; + econtext->caseValue_isNull = (nulls[fieldnum - 1] == 'n'); + + values[fieldnum - 1] = ExecEvalExpr(newval, + econtext, + &eisnull, + NULL); + nulls[fieldnum - 1] = eisnull ? 'n' : ' '; + } + + econtext->caseValue_datum = save_datum; + econtext->caseValue_isNull = save_isNull; + + tuple = heap_formtuple(tupDesc, values, nulls); + + pfree(values); + pfree(nulls); + + return HeapTupleGetDatum(tuple); +} + /* ---------------------------------------------------------------- * ExecEvalRelabelType * @@ -2810,6 +2940,18 @@ ExecInitExpr(Expr *node, PlanState *parent) state = (ExprState *) fstate; } break; + case T_FieldStore: + { + FieldStore *fstore = (FieldStore *) node; + FieldStoreState *fstate = makeNode(FieldStoreState); + + fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalFieldStore; + fstate->arg = ExecInitExpr(fstore->arg, parent); + fstate->newvals = (List *) ExecInitExpr((Expr *) fstore->newvals, parent); + fstate->argdesc = NULL; + state = (ExprState *) fstate; + } + break; case T_RelabelType: { RelabelType *relabel = (RelabelType *) node; diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index f3086c84b1..bfcb82447d 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -15,7 +15,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.284 2004/05/30 23:40:27 neilc Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.285 2004/06/09 19:08:15 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -844,6 +844,22 @@ _copyFieldSelect(FieldSelect *from) return newnode; } +/* + * _copyFieldStore + */ +static FieldStore * +_copyFieldStore(FieldStore *from) +{ + FieldStore *newnode = makeNode(FieldStore); + + COPY_NODE_FIELD(arg); + COPY_NODE_FIELD(newvals); + COPY_NODE_FIELD(fieldnums); + COPY_SCALAR_FIELD(resulttype); + + return newnode; +} + /* * _copyRelabelType */ @@ -1275,7 +1291,6 @@ _copyColumnRef(ColumnRef *from) ColumnRef *newnode = makeNode(ColumnRef); COPY_NODE_FIELD(fields); - COPY_NODE_FIELD(indirection); return newnode; } @@ -1286,8 +1301,6 @@ _copyParamRef(ParamRef *from) ParamRef *newnode = makeNode(ParamRef); COPY_SCALAR_FIELD(number); - COPY_NODE_FIELD(fields); - COPY_NODE_FIELD(indirection); return newnode; } @@ -1347,13 +1360,12 @@ _copyAIndices(A_Indices *from) return newnode; } -static ExprFieldSelect * -_copyExprFieldSelect(ExprFieldSelect *from) +static A_Indirection * +_copyA_Indirection(A_Indirection *from) { - ExprFieldSelect *newnode = makeNode(ExprFieldSelect); + A_Indirection *newnode = makeNode(A_Indirection); COPY_NODE_FIELD(arg); - COPY_NODE_FIELD(fields); COPY_NODE_FIELD(indirection); return newnode; @@ -2648,6 +2660,9 @@ copyObject(void *from) case T_FieldSelect: retval = _copyFieldSelect(from); break; + case T_FieldStore: + retval = _copyFieldStore(from); + break; case T_RelabelType: retval = _copyRelabelType(from); break; @@ -2984,8 +2999,8 @@ copyObject(void *from) case T_A_Indices: retval = _copyAIndices(from); break; - case T_ExprFieldSelect: - retval = _copyExprFieldSelect(from); + case T_A_Indirection: + retval = _copyA_Indirection(from); break; case T_ResTarget: retval = _copyResTarget(from); diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index ca2511ea09..fec550836b 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -18,7 +18,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.223 2004/05/30 23:40:27 neilc Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.224 2004/06/09 19:08:15 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -350,6 +350,17 @@ _equalFieldSelect(FieldSelect *a, FieldSelect *b) return true; } +static bool +_equalFieldStore(FieldStore *a, FieldStore *b) +{ + COMPARE_NODE_FIELD(arg); + COMPARE_NODE_FIELD(newvals); + COMPARE_NODE_FIELD(fieldnums); + COMPARE_SCALAR_FIELD(resulttype); + + return true; +} + static bool _equalRelabelType(RelabelType *a, RelabelType *b) { @@ -1428,7 +1439,6 @@ static bool _equalColumnRef(ColumnRef *a, ColumnRef *b) { COMPARE_NODE_FIELD(fields); - COMPARE_NODE_FIELD(indirection); return true; } @@ -1437,8 +1447,6 @@ static bool _equalParamRef(ParamRef *a, ParamRef *b) { COMPARE_SCALAR_FIELD(number); - COMPARE_NODE_FIELD(fields); - COMPARE_NODE_FIELD(indirection); return true; } @@ -1474,10 +1482,9 @@ _equalAIndices(A_Indices *a, A_Indices *b) } static bool -_equalExprFieldSelect(ExprFieldSelect *a, ExprFieldSelect *b) +_equalA_Indirection(A_Indirection *a, A_Indirection *b) { COMPARE_NODE_FIELD(arg); - COMPARE_NODE_FIELD(fields); COMPARE_NODE_FIELD(indirection); return true; @@ -1805,6 +1812,9 @@ equal(void *a, void *b) case T_FieldSelect: retval = _equalFieldSelect(a, b); break; + case T_FieldStore: + retval = _equalFieldStore(a, b); + break; case T_RelabelType: retval = _equalRelabelType(a, b); break; @@ -2127,8 +2137,8 @@ equal(void *a, void *b) case T_A_Indices: retval = _equalAIndices(a, b); break; - case T_ExprFieldSelect: - retval = _equalExprFieldSelect(a, b); + case T_A_Indirection: + retval = _equalA_Indirection(a, b); break; case T_ResTarget: retval = _equalResTarget(a, b); diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index cb6964c2d8..1984ced756 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.238 2004/05/30 23:40:27 neilc Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.239 2004/06/09 19:08:15 tgl Exp $ * * NOTES * Every node type that can appear in stored rules' parsetrees *must* @@ -745,6 +745,17 @@ _outFieldSelect(StringInfo str, FieldSelect *node) WRITE_INT_FIELD(resulttypmod); } +static void +_outFieldStore(StringInfo str, FieldStore *node) +{ + WRITE_NODE_TYPE("FIELDSTORE"); + + WRITE_NODE_FIELD(arg); + WRITE_NODE_FIELD(newvals); + WRITE_NODE_FIELD(fieldnums); + WRITE_OID_FIELD(resulttype); +} + static void _outRelabelType(StringInfo str, RelabelType *node) { @@ -1166,8 +1177,24 @@ _outSelectStmt(StringInfo str, SelectStmt *node) { WRITE_NODE_TYPE("SELECT"); - /* XXX this is pretty durn incomplete */ WRITE_NODE_FIELD(whereClause); + WRITE_NODE_FIELD(distinctClause); + WRITE_NODE_FIELD(into); + WRITE_NODE_FIELD(intoColNames); + WRITE_ENUM_FIELD(intoHasOids, ContainsOids); + WRITE_NODE_FIELD(targetList); + WRITE_NODE_FIELD(fromClause); + WRITE_NODE_FIELD(whereClause); + WRITE_NODE_FIELD(groupClause); + WRITE_NODE_FIELD(havingClause); + WRITE_NODE_FIELD(sortClause); + WRITE_NODE_FIELD(limitOffset); + WRITE_NODE_FIELD(limitCount); + WRITE_NODE_FIELD(forUpdate); + WRITE_ENUM_FIELD(op, SetOperation); + WRITE_BOOL_FIELD(all); + WRITE_NODE_FIELD(larg); + WRITE_NODE_FIELD(rarg); } static void @@ -1181,6 +1208,15 @@ _outFuncCall(StringInfo str, FuncCall *node) WRITE_BOOL_FIELD(agg_distinct); } +static void +_outDefElem(StringInfo str, DefElem *node) +{ + WRITE_NODE_TYPE("DEFELEM"); + + WRITE_STRING_FIELD(defname); + WRITE_NODE_FIELD(arg); +} + static void _outColumnDef(StringInfo str, ColumnDef *node) { @@ -1439,7 +1475,6 @@ _outColumnRef(StringInfo str, ColumnRef *node) WRITE_NODE_TYPE("COLUMNREF"); WRITE_NODE_FIELD(fields); - WRITE_NODE_FIELD(indirection); } static void @@ -1448,29 +1483,45 @@ _outParamRef(StringInfo str, ParamRef *node) WRITE_NODE_TYPE("PARAMREF"); WRITE_INT_FIELD(number); - WRITE_NODE_FIELD(fields); - WRITE_NODE_FIELD(indirection); } static void _outAConst(StringInfo str, A_Const *node) { - WRITE_NODE_TYPE("CONST "); + WRITE_NODE_TYPE("A_CONST"); _outValue(str, &(node->val)); WRITE_NODE_FIELD(typename); } static void -_outExprFieldSelect(StringInfo str, ExprFieldSelect *node) +_outA_Indices(StringInfo str, A_Indices *node) { - WRITE_NODE_TYPE("EXPRFIELDSELECT"); + WRITE_NODE_TYPE("A_INDICES"); + + WRITE_NODE_FIELD(lidx); + WRITE_NODE_FIELD(uidx); +} + +static void +_outA_Indirection(StringInfo str, A_Indirection *node) +{ + WRITE_NODE_TYPE("A_INDIRECTION"); WRITE_NODE_FIELD(arg); - WRITE_NODE_FIELD(fields); WRITE_NODE_FIELD(indirection); } +static void +_outResTarget(StringInfo str, ResTarget *node) +{ + WRITE_NODE_TYPE("RESTARGET"); + + WRITE_STRING_FIELD(name); + WRITE_NODE_FIELD(indirection); + WRITE_NODE_FIELD(val); +} + static void _outConstraint(StringInfo str, Constraint *node) { @@ -1666,6 +1717,9 @@ _outNode(StringInfo str, void *obj) case T_FieldSelect: _outFieldSelect(str, obj); break; + case T_FieldStore: + _outFieldStore(str, obj); + break; case T_RelabelType: _outRelabelType(str, obj); break; @@ -1815,8 +1869,14 @@ _outNode(StringInfo str, void *obj) case T_A_Const: _outAConst(str, obj); break; - case T_ExprFieldSelect: - _outExprFieldSelect(str, obj); + case T_A_Indices: + _outA_Indices(str, obj); + break; + case T_A_Indirection: + _outA_Indirection(str, obj); + break; + case T_ResTarget: + _outResTarget(str, obj); break; case T_Constraint: _outConstraint(str, obj); @@ -1827,6 +1887,9 @@ _outNode(StringInfo str, void *obj) case T_FuncCall: _outFuncCall(str, obj); break; + case T_DefElem: + _outDefElem(str, obj); + break; default: diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 225bf6241f..e3fa983cea 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.171 2004/05/30 23:40:27 neilc Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.172 2004/06/09 19:08:15 tgl Exp $ * * NOTES * Path and Plan nodes do not have any readfuncs support, because we @@ -543,6 +543,22 @@ _readFieldSelect(void) READ_DONE(); } +/* + * _readFieldStore + */ +static FieldStore * +_readFieldStore(void) +{ + READ_LOCALS(FieldStore); + + READ_NODE_FIELD(arg); + READ_NODE_FIELD(newvals); + READ_NODE_FIELD(fieldnums); + READ_OID_FIELD(resulttype); + + READ_DONE(); +} + /* * _readRelabelType */ @@ -814,17 +830,6 @@ _readFromExpr(void) * Stuff from parsenodes.h. */ -static ColumnRef * -_readColumnRef(void) -{ - READ_LOCALS(ColumnRef); - - READ_NODE_FIELD(fields); - READ_NODE_FIELD(indirection); - - READ_DONE(); -} - static ColumnDef * _readColumnDef(void) { @@ -859,18 +864,6 @@ _readTypeName(void) READ_DONE(); } -static ExprFieldSelect * -_readExprFieldSelect(void) -{ - READ_LOCALS(ExprFieldSelect); - - READ_NODE_FIELD(arg); - READ_NODE_FIELD(fields); - READ_NODE_FIELD(indirection); - - READ_DONE(); -} - /* * _readRangeTblEntry */ @@ -974,6 +967,8 @@ parseNodeString(void) return_value = _readSubLink(); else if (MATCH("FIELDSELECT", 11)) return_value = _readFieldSelect(); + else if (MATCH("FIELDSTORE", 10)) + return_value = _readFieldStore(); else if (MATCH("RELABELTYPE", 11)) return_value = _readRelabelType(); else if (MATCH("CASE", 4)) @@ -1008,14 +1003,10 @@ parseNodeString(void) return_value = _readJoinExpr(); else if (MATCH("FROMEXPR", 8)) return_value = _readFromExpr(); - else if (MATCH("COLUMNREF", 9)) - return_value = _readColumnRef(); else if (MATCH("COLUMNDEF", 9)) return_value = _readColumnDef(); else if (MATCH("TYPENAME", 8)) return_value = _readTypeName(); - else if (MATCH("EXPRFIELDSELECT", 15)) - return_value = _readExprFieldSelect(); else if (MATCH("RTE", 3)) return_value = _readRangeTblEntry(); else if (MATCH("NOTIFY", 6)) diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index b22360ac2b..a3a2ddfbd8 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.174 2004/06/05 19:48:08 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.175 2004/06/09 19:08:16 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -724,6 +724,13 @@ contain_nonstrict_functions_walker(Node *node, void *context) /* an aggregate could return non-null with null input */ return true; } + if (IsA(node, ArrayRef)) + { + /* array assignment is nonstrict */ + if (((ArrayRef *) node)->refassgnexpr != NULL) + return true; + /* else fall through to check args */ + } if (IsA(node, FuncExpr)) { FuncExpr *expr = (FuncExpr *) node; @@ -771,6 +778,8 @@ contain_nonstrict_functions_walker(Node *node, void *context) } if (IsA(node, SubPlan)) return true; + if (IsA(node, FieldStore)) + return true; if (IsA(node, CaseExpr)) return true; if (IsA(node, CaseWhen)) @@ -2450,6 +2459,16 @@ expression_tree_walker(Node *node, break; case T_FieldSelect: return walker(((FieldSelect *) node)->arg, context); + case T_FieldStore: + { + FieldStore *fstore = (FieldStore *) node; + + if (walker(fstore->arg, context)) + return true; + if (walker(fstore->newvals, context)) + return true; + } + break; case T_RelabelType: return walker(((RelabelType *) node)->arg, context); case T_CaseExpr: @@ -2840,6 +2859,18 @@ expression_tree_mutator(Node *node, return (Node *) newnode; } break; + case T_FieldStore: + { + FieldStore *fstore = (FieldStore *) node; + FieldStore *newnode; + + FLATCOPY(newnode, fstore, FieldStore); + MUTATE(newnode->arg, fstore->arg, Expr *); + MUTATE(newnode->newvals, fstore->newvals, List *); + newnode->fieldnums = list_copy(fstore->fieldnums); + return (Node *) newnode; + } + break; case T_RelabelType: { RelabelType *relabel = (RelabelType *) node; 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 TableElement ConstraintElem TableFuncElement %type columnDef %type def_elem -%type def_arg columnElem where_clause insert_column_item - a_expr b_expr c_expr AexprConst +%type def_arg columnElem where_clause + a_expr b_expr c_expr AexprConst indirection_el columnref in_expr having_clause func_table array_expr %type row type_list array_expr_list %type case_expr case_arg when_clause case_default @@ -284,14 +285,13 @@ static void doNegateFloat(Value *v); %type OptCreateAs CreateAsList %type CreateAsElement %type NumericOnly FloatOnly IntegerOnly -%type columnref %type alias_clause %type sortby %type index_elem %type table_ref %type joined_table %type relation_expr -%type target_el insert_target_el update_target_el +%type target_el insert_target_el update_target_el insert_column_item %type 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); @@ -7781,6 +7782,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 $ * *------------------------------------------------------------------------- */ @@ -67,6 +67,39 @@ make_var(ParseState *pstate, RangeTblEntry *rte, int attrno) return makeVar(vnum, attrno, vartypeid, type_mod, sublevels_up); } +/* + * 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 @@ -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: diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index cf6281d7cc..768f8788b7 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.138 2004/05/30 23:40:35 neilc Exp $ + * $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.139 2004/06/09 19:08:17 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -50,6 +50,7 @@ static void rewriteTargetList(Query *parsetree, Relation target_relation); static TargetEntry *process_matched_tle(TargetEntry *src_tle, TargetEntry *prior_tle, const char *attrName); +static Node *get_assignment_input(Node *node); static void markQueryForUpdate(Query *qry, bool skipOldNew); static List *matchLocks(CmdType event, RuleLock *rulelocks, int varno, Query *parsetree); @@ -273,8 +274,9 @@ adjustJoinTreeList(Query *parsetree, bool removert, int rt_index) * expressions. * * 2. Merge multiple entries for the same target attribute, or declare error - * if we can't. Presently, multiple entries are only allowed for UPDATE of - * an array field, for example "UPDATE table SET foo[2] = 42, foo[4] = 43". + * if we can't. Multiple entries are only allowed for INSERT/UPDATE of + * portions of an array or record field, for example + * UPDATE table SET foo[2] = 42, foo[4] = 43; * We can merge such 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) @@ -431,8 +433,12 @@ process_matched_tle(TargetEntry *src_tle, const char *attrName) { Resdom *resdom = src_tle->resdom; + Node *src_expr; + Node *prior_expr; + Node *src_input; + Node *prior_input; Node *priorbottom; - ArrayRef *newexpr; + Node *newexpr; if (prior_tle == NULL) { @@ -443,30 +449,55 @@ process_matched_tle(TargetEntry *src_tle, return src_tle; } - /* + /*---------- * Multiple assignments to same attribute. Allow only if all are - * array-assign operators with same bottom array object. + * FieldStore or ArrayRef assignment operations. This is a bit + * tricky because what we may actually be looking at is a nest of + * such nodes; consider + * UPDATE tab SET col.fld1.subfld1 = x, col.fld2.subfld2 = y + * The two expressions produced by the parser will look like + * FieldStore(col, fld1, FieldStore(placeholder, subfld1, x)) + * FieldStore(col, fld2, FieldStore(placeholder, subfld2, x)) + * However, we can ignore the substructure and just consider the top + * FieldStore or ArrayRef from each assignment, because it works to + * combine these as + * FieldStore(FieldStore(col, fld1, + * FieldStore(placeholder, subfld1, x)), + * fld2, FieldStore(placeholder, subfld2, x)) + * Note the leftmost expression goes on the inside so that the + * assignments appear to occur left-to-right. + * + * For FieldStore, instead of nesting we can generate a single + * FieldStore with multiple target fields. We must nest when + * ArrayRefs are involved though. + *---------- */ - 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)->refrestype != - ((ArrayRef *) prior_tle->expr)->refrestype) + src_expr = (Node *) src_tle->expr; + prior_expr = (Node *) prior_tle->expr; + src_input = get_assignment_input(src_expr); + prior_input = get_assignment_input(prior_expr); + if (src_input == NULL || + prior_input == NULL || + exprType(src_expr) != exprType(prior_expr)) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("multiple assignments to same column \"%s\"", attrName))); /* - * Prior TLE could be a nest of ArrayRefs if we do this more than + * Prior TLE could be a nest of assignments if we do this more than * once. */ - priorbottom = (Node *) ((ArrayRef *) prior_tle->expr)->refexpr; - while (priorbottom != NULL && IsA(priorbottom, ArrayRef) && - ((ArrayRef *) priorbottom)->refassgnexpr != NULL) - priorbottom = (Node *) ((ArrayRef *) priorbottom)->refexpr; - if (!equal(priorbottom, ((ArrayRef *) src_tle->expr)->refexpr)) + priorbottom = prior_input; + for (;;) + { + Node *newbottom = get_assignment_input(priorbottom); + + if (newbottom == NULL) + break; /* found the original Var reference */ + priorbottom = newbottom; + } + if (!equal(priorbottom, src_input)) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("multiple assignments to same column \"%s\"", @@ -475,13 +506,70 @@ process_matched_tle(TargetEntry *src_tle, /* * Looks OK to nest 'em. */ - newexpr = makeNode(ArrayRef); - memcpy(newexpr, src_tle->expr, sizeof(ArrayRef)); - newexpr->refexpr = prior_tle->expr; + if (IsA(src_expr, FieldStore)) + { + FieldStore *fstore = makeNode(FieldStore); + + if (IsA(prior_expr, FieldStore)) + { + /* combine the two */ + memcpy(fstore, prior_expr, sizeof(FieldStore)); + fstore->newvals = + list_concat(list_copy(((FieldStore *) prior_expr)->newvals), + list_copy(((FieldStore *) src_expr)->newvals)); + fstore->fieldnums = + list_concat(list_copy(((FieldStore *) prior_expr)->fieldnums), + list_copy(((FieldStore *) src_expr)->fieldnums)); + } + else + { + /* general case, just nest 'em */ + memcpy(fstore, src_expr, sizeof(FieldStore)); + fstore->arg = (Expr *) prior_expr; + } + newexpr = (Node *) fstore; + } + else if (IsA(src_expr, ArrayRef)) + { + ArrayRef *aref = makeNode(ArrayRef); + + memcpy(aref, src_expr, sizeof(ArrayRef)); + aref->refexpr = (Expr *) prior_expr; + newexpr = (Node *) aref; + } + else + { + elog(ERROR, "can't happen"); + newexpr = NULL; + } return makeTargetEntry(resdom, (Expr *) newexpr); } +/* + * If node is an assignment node, return its input; else return NULL + */ +static Node * +get_assignment_input(Node *node) +{ + if (node == NULL) + return NULL; + if (IsA(node, FieldStore)) + { + FieldStore *fstore = (FieldStore *) node; + + return (Node *) fstore->arg; + } + else if (IsA(node, ArrayRef)) + { + ArrayRef *aref = (ArrayRef *) node; + + if (aref->refassgnexpr == NULL) + return NULL; + return (Node *) aref->refexpr; + } + return NULL; +} /* * Make an expression tree for the default value for a column. diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index ef58e99dac..589db025d2 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -3,7 +3,7 @@ * back to source text * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.170 2004/06/06 00:41:27 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.171 2004/06/09 19:08:18 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -204,7 +204,8 @@ static void get_from_clause_coldeflist(List *coldeflist, deparse_context *context); static void get_opclass_name(Oid opclass, Oid actual_datatype, StringInfo buf); -static bool tleIsArrayAssign(TargetEntry *tle); +static Node *processIndirection(Node *node, deparse_context *context); +static void printSubscripts(ArrayRef *aref, deparse_context *context); static char *generate_relation_name(Oid relid); static char *generate_function_name(Oid funcid, int nargs, Oid *argtypes); static char *generate_operator_name(Oid operid, Oid arg1, Oid arg2); @@ -2061,6 +2062,7 @@ get_insert_query_def(Query *query, deparse_context *context) RangeTblEntry *rte; char *sep; ListCell *l; + List *strippedexprs; /* * If it's an INSERT ... SELECT there will be a single subquery RTE @@ -2087,11 +2089,15 @@ get_insert_query_def(Query *query, deparse_context *context) context->indentLevel += PRETTYINDENT_STD; appendStringInfoChar(buf, ' '); } - appendStringInfo(buf, "INSERT INTO %s", + appendStringInfo(buf, "INSERT INTO %s (", generate_relation_name(rte->relid)); - /* Add the insert-column-names list */ - sep = " ("; + /* + * Add the insert-column-names list, and make a list of the actual + * assignment source expressions. + */ + strippedexprs = NIL; + sep = ""; foreach(l, query->targetList) { TargetEntry *tle = (TargetEntry *) lfirst(l); @@ -2101,9 +2107,22 @@ get_insert_query_def(Query *query, deparse_context *context) appendStringInfo(buf, sep); sep = ", "; + + /* + * Put out name of target column; look in the catalogs, not at + * tle->resname, since resname will fail to track RENAME. + */ appendStringInfoString(buf, quote_identifier(get_relid_attribute_name(rte->relid, tle->resdom->resno))); + + /* + * Print any indirection needed (subfields or subscripts), and strip + * off the top-level nodes representing the indirection assignments. + */ + strippedexprs = lappend(strippedexprs, + processIndirection((Node *) tle->expr, + context)); } appendStringInfo(buf, ") "); @@ -2113,16 +2132,13 @@ get_insert_query_def(Query *query, deparse_context *context) appendContextKeyword(context, "VALUES (", -PRETTYINDENT_STD, PRETTYINDENT_STD, 2); sep = ""; - foreach(l, query->targetList) + foreach(l, strippedexprs) { - TargetEntry *tle = (TargetEntry *) lfirst(l); - - if (tle->resdom->resjunk) - continue; /* ignore junk entries */ + Node *expr = lfirst(l); appendStringInfo(buf, sep); sep = ", "; - get_rule_expr((Node *) tle->expr, context, false); + get_rule_expr(expr, context, false); } appendStringInfoChar(buf, ')'); } @@ -2163,6 +2179,7 @@ get_update_query_def(Query *query, deparse_context *context) foreach(l, query->targetList) { TargetEntry *tle = (TargetEntry *) lfirst(l); + Node *expr; if (tle->resdom->resjunk) continue; /* ignore junk entries */ @@ -2171,15 +2188,22 @@ get_update_query_def(Query *query, deparse_context *context) sep = ", "; /* - * If the update expression is an array assignment, we mustn't put - * out "attname =" here; it will come out of the display of the - * ArrayRef node instead. + * Put out name of target column; look in the catalogs, not at + * tle->resname, since resname will fail to track RENAME. */ - if (!tleIsArrayAssign(tle)) - appendStringInfo(buf, "%s = ", + appendStringInfoString(buf, quote_identifier(get_relid_attribute_name(rte->relid, tle->resdom->resno))); - get_rule_expr((Node *) tle->expr, context, false); + + /* + * Print any indirection needed (subfields or subscripts), and strip + * off the top-level nodes representing the indirection assignments. + */ + expr = processIndirection((Node *) tle->expr, context); + + appendStringInfo(buf, " = "); + + get_rule_expr(expr, context, false); } /* Add the FROM clause if needed */ @@ -2452,6 +2476,13 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags) */ return (IsA(parentNode, FieldSelect) ? false : true); + case T_FieldStore: + + /* + * treat like FieldSelect (probably doesn't matter) + */ + return (IsA(parentNode, FieldStore) ? false : true); + case T_CoerceToDomain: /* maybe simple, check args */ return isSimpleNode((Node *) ((CoerceToDomain *) node)->arg, @@ -2757,53 +2788,27 @@ get_rule_expr(Node *node, deparse_context *context, case T_ArrayRef: { ArrayRef *aref = (ArrayRef *) node; - bool savevarprefix = context->varprefix; bool need_parens; - ListCell *lowlist_item; - ListCell *uplist_item; /* - * If we are doing UPDATE array[n] = expr, we need to - * suppress any prefix on the array name. Currently, that - * is the only context in which we will see a non-null - * refassgnexpr --- but someday a smarter test may be - * needed. + * Parenthesize the argument unless it's a simple Var or + * a FieldSelect. (In particular, if it's another ArrayRef, + * we *must* parenthesize to avoid confusion.) */ - if (aref->refassgnexpr) - context->varprefix = false; - - /* - * Parenthesize the argument unless it's a simple Var. - */ - need_parens = (aref->refassgnexpr == NULL) && - !IsA(aref->refexpr, Var); + need_parens = !IsA(aref->refexpr, Var) && + !IsA(aref->refexpr, FieldSelect); if (need_parens) appendStringInfoChar(buf, '('); get_rule_expr((Node *) aref->refexpr, context, showimplicit); if (need_parens) appendStringInfoChar(buf, ')'); - context->varprefix = savevarprefix; - lowlist_item = list_head(aref->reflowerindexpr); - foreach(uplist_item, aref->refupperindexpr) - { - appendStringInfo(buf, "["); - if (lowlist_item) - { - get_rule_expr((Node *) lfirst(lowlist_item), context, - false); - appendStringInfo(buf, ":"); - lowlist_item = lnext(lowlist_item); - } - get_rule_expr((Node *) lfirst(uplist_item), - context, false); - appendStringInfo(buf, "]"); - } + printSubscripts(aref, context); + /* + * Array assignment nodes should have been handled in + * processIndirection(). + */ if (aref->refassgnexpr) - { - appendStringInfo(buf, " = "); - get_rule_expr((Node *) aref->refassgnexpr, context, - showimplicit); - } + elog(ERROR, "unexpected refassgnexpr"); } break; @@ -2935,6 +2940,7 @@ get_rule_expr(Node *node, deparse_context *context, Oid argType = exprType((Node *) fselect->arg); Oid typrelid; char *fieldname; + bool need_parens; /* lookup arg type and get the field name */ typrelid = get_typ_typrelid(argType); @@ -2943,19 +2949,31 @@ get_rule_expr(Node *node, deparse_context *context, format_type_be(argType)); fieldname = get_relid_attribute_name(typrelid, fselect->fieldnum); - /* - * If the argument is simple enough, we could emit - * arg.fieldname, but most cases where FieldSelect is used - * are *not* simple. So, always use parenthesized syntax. + * Parenthesize the argument unless it's an ArrayRef or + * another FieldSelect. Note in particular that it would be + * WRONG to not parenthesize a Var argument; simplicity is not + * the issue here, having the right number of names is. */ - appendStringInfoChar(buf, '('); - get_rule_expr_paren((Node *) fselect->arg, context, true, node); - appendStringInfoChar(buf, ')'); + need_parens = !IsA(fselect->arg, ArrayRef) && + !IsA(fselect->arg, FieldSelect); + if (need_parens) + appendStringInfoChar(buf, '('); + get_rule_expr((Node *) fselect->arg, context, true); + if (need_parens) + appendStringInfoChar(buf, ')'); appendStringInfo(buf, ".%s", quote_identifier(fieldname)); } break; + case T_FieldStore: + /* + * We shouldn't see FieldStore here; it should have been + * stripped off by processIndirection(). + */ + elog(ERROR, "unexpected FieldStore"); + break; + case T_RelabelType: { RelabelType *relabel = (RelabelType *) node; @@ -4043,29 +4061,89 @@ get_opclass_name(Oid opclass, Oid actual_datatype, } /* - * tleIsArrayAssign - check for array assignment + * processIndirection - take care of array and subfield assignment + * + * We strip any top-level FieldStore or assignment ArrayRef nodes that + * appear in the input, printing out the appropriate decoration for the + * base column name (that the caller just printed). We return the + * subexpression that's to be assigned. */ -static bool -tleIsArrayAssign(TargetEntry *tle) +static Node * +processIndirection(Node *node, deparse_context *context) { - ArrayRef *aref; + StringInfo buf = context->buf; - if (tle->expr == NULL || !IsA(tle->expr, ArrayRef)) - return false; - aref = (ArrayRef *) tle->expr; - if (aref->refassgnexpr == NULL) - return false; + for (;;) + { + if (node == NULL) + break; + if (IsA(node, FieldStore)) + { + FieldStore *fstore = (FieldStore *) node; + Oid typrelid; + char *fieldname; + + /* lookup tuple type */ + typrelid = get_typ_typrelid(fstore->resulttype); + if (!OidIsValid(typrelid)) + elog(ERROR, "argument type %s of FieldStore is not a tuple type", + format_type_be(fstore->resulttype)); + /* + * Get the field name. Note we assume here that there's only + * one field being assigned to. This is okay in stored rules + * but could be wrong in executable target lists. Presently no + * problem since explain.c doesn't print plan targetlists, but + * someday may have to think of something ... + */ + fieldname = get_relid_attribute_name(typrelid, + linitial_int(fstore->fieldnums)); + appendStringInfo(buf, ".%s", quote_identifier(fieldname)); + /* + * We ignore arg since it should be an uninteresting reference + * to the target column or subcolumn. + */ + node = (Node *) linitial(fstore->newvals); + } + else if (IsA(node, ArrayRef)) + { + ArrayRef *aref = (ArrayRef *) node; - /* - * Currently, it should only be possible to see non-null refassgnexpr - * if we are indeed looking at an "UPDATE array[n] = expr" situation. - * So aref->refexpr ought to match the tle's target. - */ - if (aref->refexpr == NULL || !IsA(aref->refexpr, Var) || - ((Var *) aref->refexpr)->varattno != tle->resdom->resno) - elog(ERROR, "unrecognized situation in array assignment"); + if (aref->refassgnexpr == NULL) + break; + printSubscripts(aref, context); + /* + * We ignore refexpr since it should be an uninteresting reference + * to the target column or subcolumn. + */ + node = (Node *) aref->refassgnexpr; + } + else + break; + } - return true; + return node; +} + +static void +printSubscripts(ArrayRef *aref, deparse_context *context) +{ + StringInfo buf = context->buf; + ListCell *lowlist_item; + ListCell *uplist_item; + + lowlist_item = list_head(aref->reflowerindexpr); /* could be NULL */ + foreach(uplist_item, aref->refupperindexpr) + { + appendStringInfoChar(buf, '['); + if (lowlist_item) + { + get_rule_expr((Node *) lfirst(lowlist_item), context, false); + appendStringInfoChar(buf, ':'); + lowlist_item = lnext(lowlist_item); + } + get_rule_expr((Node *) lfirst(uplist_item), context, false); + appendStringInfoChar(buf, ']'); + } } /* diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 325bf87680..a32378c7f8 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.116 2004/05/10 22:44:49 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.117 2004/06/09 19:08:18 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -574,6 +574,18 @@ typedef struct FieldSelectState TupleDesc argdesc; /* tupdesc for most recent input */ } FieldSelectState; +/* ---------------- + * FieldStoreState node + * ---------------- + */ +typedef struct FieldStoreState +{ + ExprState xprstate; + ExprState *arg; /* input tuple value */ + List *newvals; /* new value(s) for field(s) */ + TupleDesc argdesc; /* tupdesc for most recent input */ +} FieldStoreState; + /* ---------------- * CaseExprState node * ---------------- diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 6feedf6762..b719747e76 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.156 2004/05/26 13:57:02 momjian Exp $ + * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.157 2004/06/09 19:08:18 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -110,6 +110,7 @@ typedef enum NodeTag T_SubLink, T_SubPlan, T_FieldSelect, + T_FieldStore, T_RelabelType, T_CaseExpr, T_CaseWhen, @@ -143,6 +144,7 @@ typedef enum NodeTag T_BoolExprState, T_SubPlanState, T_FieldSelectState, + T_FieldStoreState, T_CaseExprState, T_CaseWhenState, T_ArrayExprState, @@ -274,7 +276,7 @@ typedef enum NodeTag T_A_Const, T_FuncCall, T_A_Indices, - T_ExprFieldSelect, + T_A_Indirection, T_ResTarget, T_TypeCast, T_SortBy, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 8f6cc25e0a..0da9b37907 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.257 2004/06/02 21:01:09 momjian Exp $ + * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.258 2004/06/09 19:08:18 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -166,26 +166,26 @@ typedef struct TypeName * ColumnRef - specifies a reference to a column, or possibly a whole tuple * * The "fields" list must be nonempty; its last component may be "*" - * instead of a field name. Subscripts are optional. + * instead of a regular field name. + * + * Note: any array subscripting or selection of fields from composite columns + * is represented by an A_Indirection node above the ColumnRef. However, + * for simplicity in the normal case, initial field selection from a table + * name is represented within ColumnRef and not by adding A_Indirection. */ typedef struct ColumnRef { NodeTag type; List *fields; /* field names (list of Value strings) */ - List *indirection; /* subscripts (list of A_Indices) */ } ColumnRef; /* - * ParamRef - specifies a parameter reference - * - * The parameter could be qualified with field names and/or subscripts + * ParamRef - specifies a $n parameter reference */ typedef struct ParamRef { NodeTag type; int number; /* the number of the parameter */ - List *fields; /* field names (list of Value strings) */ - List *indirection; /* subscripts (list of A_Indices) */ } ParamRef; /* @@ -267,40 +267,50 @@ typedef struct A_Indices } A_Indices; /* - * ExprFieldSelect - select a field and/or array element from an expression + * A_Indirection - select a field and/or array element from an expression * - * This is used in the raw parsetree to represent selection from an - * arbitrary expression (not a column or param reference). Either - * fields or indirection may be NIL if not used. + * The indirection list can contain both A_Indices nodes (representing + * subscripting) and string Value nodes (representing field selection + * --- the string value is the name of the field to select). For example, + * a complex selection operation like + * (foo).field1[42][7].field2 + * would be represented with a single A_Indirection node having a 4-element + * indirection list. + * + * Note: as of Postgres 7.5, we don't support arrays of composite values, + * so cases in which a field select follows a subscript aren't actually + * semantically legal. However the parser is prepared to handle such. */ -typedef struct ExprFieldSelect +typedef struct A_Indirection { NodeTag type; Node *arg; /* the thing being selected from */ - List *fields; /* field names (list of Value strings) */ - List *indirection; /* subscripts (list of A_Indices) */ -} ExprFieldSelect; + List *indirection; /* subscripts and/or field names */ +} A_Indirection; /* * ResTarget - - * result target (used in target list of pre-transformed Parse trees) + * result target (used in target list of pre-transformed parse trees) * - * In a SELECT or INSERT target list, 'name' is either NULL or - * the column name assigned to the value. (If there is an 'AS ColumnLabel' - * clause, the grammar sets 'name' from it; otherwise 'name' is initially NULL - * and is filled in during the parse analysis phase.) - * The 'indirection' field is not used at all. + * In a SELECT or INSERT target list, 'name' is the column label from an + * 'AS ColumnLabel' clause, or NULL if there was none, and 'val' is the + * value expression itself. The 'indirection' field is not used. * - * In an UPDATE target list, 'name' is the name of the destination column, + * INSERT has a second ResTarget list which is the target-column-names list. + * Here, 'val' is not used, 'name' is the name of the destination column, * and 'indirection' stores any subscripts attached to the destination. - * That is, our representation is UPDATE table SET name [indirection] = val. + * + * 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. + * + * See A_Indirection for more info about what can appear in 'indirection'. */ typedef struct ResTarget { NodeTag type; char *name; /* column name or NULL */ - List *indirection; /* subscripts for destination column, or - * NIL */ + List *indirection; /* subscripts and field names, or NIL */ Node *val; /* the value expression to compute or * assign */ } ResTarget; diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 5db00199ce..3ed4d74ee3 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -10,7 +10,7 @@ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.99 2004/05/30 23:40:39 neilc Exp $ + * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.100 2004/06/09 19:08:18 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -36,14 +36,15 @@ * ordinal position (counting from 1). However, in an INSERT or UPDATE * targetlist, resno represents the attribute number of the destination * column for the item; so there may be missing or out-of-order resnos. - * In an UPDATE, it is even legal to have duplicated resnos; consider + * It is even legal to have duplicated resnos; consider * UPDATE table SET arraycol[1] = ..., arraycol[2] = ..., ... * The two meanings come together in the executor, because the planner * transforms INSERT/UPDATE tlists into a normalized form with exactly * one entry for each column of the destination table. Before that's * happened, however, it is risky to assume that resno == position. * Generally get_tle_by_resno() should be used rather than list_nth() - * to fetch tlist entries by resno. + * to fetch tlist entries by resno, and only in SELECT should you assume + * that resno is a unique identifier. * * resname is required to represent the correct column name in non-resjunk * entries of top-level SELECT targetlists, since it will be used as the @@ -540,6 +541,31 @@ typedef struct FieldSelect int32 resulttypmod; /* output typmod (usually -1) */ } FieldSelect; +/* ---------------- + * FieldStore + * + * FieldStore represents the operation of modifying one field in a tuple + * value, yielding a new tuple value (the input is not touched!). Like + * the assign case of ArrayRef, this is used to implement UPDATE of a + * portion of a column. + * + * A single FieldStore can actually represent updates of several different + * fields. The parser only generates FieldStores with single-element lists, + * but the planner will collapse multiple updates of the same base column + * into one FieldStore. + * ---------------- + */ + +typedef struct FieldStore +{ + Expr xpr; + Expr *arg; /* input tuple value */ + List *newvals; /* new value(s) for field(s) */ + List *fieldnums; /* integer list of field attnums */ + Oid resulttype; /* type of result (same as type of arg) */ + /* Like RowExpr, we deliberately omit a typmod here */ +} FieldStore; + /* ---------------- * RelabelType * @@ -607,6 +633,9 @@ typedef struct CaseWhen * Placeholder node for the test value to be processed by a CASE expression. * This is effectively like a Param, but can be implemented more simply * since we need only one replacement value at a time. + * + * We also use this in nested UPDATE expressions. + * See transformAssignmentIndirection(). */ typedef struct CaseTestExpr { diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h index 8bb595c709..de7b476655 100644 --- a/src/include/parser/parse_node.h +++ b/src/include/parser/parse_node.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/parser/parse_node.h,v 1.38 2003/11/29 22:41:09 pgsql Exp $ + * $PostgreSQL: pgsql/src/include/parser/parse_node.h,v 1.39 2004/06/09 19:08:19 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -67,12 +67,13 @@ typedef struct ParseState extern ParseState *make_parsestate(ParseState *parentParseState); extern Var *make_var(ParseState *pstate, RangeTblEntry *rte, int attrno); +extern Oid transformArrayType(Oid arrayType); extern ArrayRef *transformArraySubscripts(ParseState *pstate, Node *arrayBase, Oid arrayType, - int32 arrayTypMod, + Oid elementType, + int32 elementTypMod, List *indirection, - bool forceSlice, Node *assignFrom); extern Const *make_const(Value *value); diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index a8b531874d..0c9d865d17 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -3,7 +3,7 @@ * procedural language * * IDENTIFICATION - * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.106 2004/06/06 00:41:28 tgl Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.107 2004/06/09 19:08:19 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -3852,6 +3852,18 @@ exec_simple_check_node(Node *node) case T_FieldSelect: return exec_simple_check_node((Node *) ((FieldSelect *) node)->arg); + case T_FieldStore: + { + FieldStore *expr = (FieldStore *) node; + + if (!exec_simple_check_node((Node *) expr->arg)) + return FALSE; + if (!exec_simple_check_node((Node *) expr->newvals)) + return FALSE; + + return TRUE; + } + case T_RelabelType: return exec_simple_check_node((Node *) ((RelabelType *) node)->arg); diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 6e82a7d0a6..8647eccf14 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -11,26 +11,25 @@ CREATE TABLE arrtest ( g varchar(5)[] ); -- --- only this array as a 0-based 'e', the others are 1-based. --- 'e' is also a large object. +-- only the 'e' array is 0-based, the others are 1-based. -- -INSERT INTO arrtest (a[5], b[2][1][2], c, d, f, g) +INSERT INTO arrtest (a[1:5], b[1:1][1:2][1:2], c, d, f, g) VALUES ('{1,2,3,4,5}', '{{{0,0},{1,2}}}', '{}', '{}', '{}', '{}'); UPDATE arrtest SET e[0] = '1.1'; UPDATE arrtest SET e[1] = '2.2'; INSERT INTO arrtest (f) VALUES ('{"too long"}'); ERROR: value too long for type character(5) -INSERT INTO arrtest (a, b[2][2][1], c, d, e, f, g) +INSERT INTO arrtest (a, b[1:2][1:2], c, d, e, f, g) VALUES ('{11,12,23}', '{{3,4},{4,5}}', '{"foobar"}', '{{"elt1", "elt2"}}', '{"3.4", "6.7"}', '{"abc","abcde"}', '{"abc","abcde"}'); -INSERT INTO arrtest (a, b[1][2][2], c, d[2][1]) +INSERT INTO arrtest (a, b[1:2], c, d[1:2]) VALUES ('{}', '{3,4}', '{foo,bar}', '{bar,foo}'); SELECT * FROM arrtest; a | b | c | d | e | f | g -------------+-----------------+-----------+---------------+-----------+-----------------+------------- - {1,2,3,4,5} | {{{0,0},{1,2}}} | {} | {} | | {} | {} + {1,2,3,4,5} | {{{0,0},{1,2}}} | {} | {} | {1.1,2.2} | {} | {} {11,12,23} | {{3,4},{4,5}} | {foobar} | {{elt1,elt2}} | {3.4,6.7} | {"abc ",abcde} | {abc,abcde} {} | {3,4} | {foo,bar} | {bar,foo} | | | (3 rows) @@ -41,20 +40,20 @@ SELECT arrtest.a[1], arrtest.d[1][1], arrtest.e[0] FROM arrtest; - a | b | c | d | e -----+---+--------+------+--- - 1 | 0 | | | - 11 | | foobar | elt1 | - | | foo | | + a | b | c | d | e +----+---+--------+------+----- + 1 | 0 | | | 1.1 + 11 | | foobar | elt1 | + | | foo | | (3 rows) SELECT a[1], b[1][1][1], c[1], d[1][1], e[0] FROM arrtest; - a | b | c | d | e -----+---+--------+------+--- - 1 | 0 | | | - 11 | | foobar | elt1 | - | | foo | | + a | b | c | d | e +----+---+--------+------+----- + 1 | 0 | | | 1.1 + 11 | | foobar | elt1 | + | | foo | | (3 rows) SELECT a[1:3], diff --git a/src/test/regress/expected/rowtypes.out b/src/test/regress/expected/rowtypes.out index 91eeaf0ab8..384e5ad6d5 100644 --- a/src/test/regress/expected/rowtypes.out +++ b/src/test/regress/expected/rowtypes.out @@ -87,19 +87,23 @@ select * from people; (Joe,Blow,) | 01-10-1984 (1 row) --- This fails at the moment, would like it to work though: +-- test insertion/updating of subfields update people set fn.suffix = 'Jr'; -ERROR: syntax error at or near "." at character 21 -LINE 1: update people set fn.suffix = 'Jr'; - ^ --- ugly workaround: -update people set fn = ((fn).first, (fn).last, 'III'); select * from people; - fn | bd -----------------+------------ - (Joe,Blow,III) | 01-10-1984 + fn | bd +---------------+------------ + (Joe,Blow,Jr) | 01-10-1984 (1 row) +insert into quadtable (f1, q.c1.r, q.c2.i) values(44,55,66); +select * from quadtable; + f1 | q +----+--------------------------- + 1 | ("(3.3,4.4)","(5.5,6.6)") + 2 | ("(,4.4)","(5.5,6.6)") + 44 | ("(55,)","(,66)") +(3 rows) + -- The object here is to ensure that toasted references inside -- composite values don't cause problems. The large f1 value will -- be toasted inside pp, it must still work after being copied to people. diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index 629ca15fb1..97cb5bbc07 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -13,11 +13,10 @@ CREATE TABLE arrtest ( ); -- --- only this array as a 0-based 'e', the others are 1-based. --- 'e' is also a large object. +-- only the 'e' array is 0-based, the others are 1-based. -- -INSERT INTO arrtest (a[5], b[2][1][2], c, d, f, g) +INSERT INTO arrtest (a[1:5], b[1:1][1:2][1:2], c, d, f, g) VALUES ('{1,2,3,4,5}', '{{{0,0},{1,2}}}', '{}', '{}', '{}', '{}'); UPDATE arrtest SET e[0] = '1.1'; @@ -27,12 +26,12 @@ UPDATE arrtest SET e[1] = '2.2'; INSERT INTO arrtest (f) VALUES ('{"too long"}'); -INSERT INTO arrtest (a, b[2][2][1], c, d, e, f, g) +INSERT INTO arrtest (a, b[1:2][1:2], c, d, e, f, g) VALUES ('{11,12,23}', '{{3,4},{4,5}}', '{"foobar"}', '{{"elt1", "elt2"}}', '{"3.4", "6.7"}', '{"abc","abcde"}', '{"abc","abcde"}'); -INSERT INTO arrtest (a, b[1][2][2], c, d[2][1]) +INSERT INTO arrtest (a, b[1:2], c, d[1:2]) VALUES ('{}', '{3,4}', '{foo,bar}', '{bar,foo}'); diff --git a/src/test/regress/sql/rowtypes.sql b/src/test/regress/sql/rowtypes.sql index 809cad8f32..e53197c05b 100644 --- a/src/test/regress/sql/rowtypes.sql +++ b/src/test/regress/sql/rowtypes.sql @@ -53,14 +53,15 @@ alter table fullname add column suffix text default null; select * from people; --- This fails at the moment, would like it to work though: +-- test insertion/updating of subfields update people set fn.suffix = 'Jr'; --- ugly workaround: -update people set fn = ((fn).first, (fn).last, 'III'); - select * from people; +insert into quadtable (f1, q.c1.r, q.c2.i) values(44,55,66); + +select * from quadtable; + -- The object here is to ensure that toasted references inside -- composite values don't cause problems. The large f1 value will -- be toasted inside pp, it must still work after being copied to people. -- 2.40.0