Commit 7e64dbc6 authored by Tom Lane's avatar Tom Lane

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.
parent 3a0df651
<!--
$PostgreSQL: pgsql/doc/src/sgml/ref/insert.sgml,v 1.26 2003/11/29 19:51:39 pgsql Exp $
$PostgreSQL: pgsql/doc/src/sgml/ref/insert.sgml,v 1.27 2004/06/09 19:08:13 tgl Exp $
PostgreSQL documentation
-->
......@@ -73,6 +73,9 @@ INSERT INTO <replaceable class="PARAMETER">table</replaceable> [ ( <replaceable
<listitem>
<para>
The name of a column in <replaceable class="PARAMETER">table</replaceable>.
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.)
</para>
</listitem>
</varlistentry>
......@@ -184,13 +187,11 @@ INSERT INTO films SELECT * FROM tmp;
<programlisting>
-- 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,'{{,,},{,,},{,,}}');
</programlisting>
</para>
</refsect1>
......
<!--
$PostgreSQL: pgsql/doc/src/sgml/ref/update.sgml,v 1.28 2004/03/03 22:22:24 neilc Exp $
$PostgreSQL: pgsql/doc/src/sgml/ref/update.sgml,v 1.29 2004/06/09 19:08:13 tgl Exp $
PostgreSQL documentation
-->
......@@ -77,7 +77,10 @@ UPDATE [ ONLY ] <replaceable class="PARAMETER">table</replaceable> SET <replacea
<term><replaceable class="PARAMETER">column</replaceable></term>
<listitem>
<para>
The name of a column in <replaceable class="PARAMETER">table</replaceable>.
The name of a column in <replaceable
class="PARAMETER">table</replaceable>.
The column name can be qualified with a subfield name or array
subscript, if needed.
</para>
</listitem>
</varlistentry>
......
<!-- $PostgreSQL: pgsql/doc/src/sgml/rowtypes.sgml,v 2.1 2004/06/07 04:04:47 tgl Exp $ -->
<!-- $PostgreSQL: pgsql/doc/src/sgml/rowtypes.sgml,v 2.2 2004/06/09 19:08:14 tgl Exp $ -->
<sect1 id="rowtypes">
<title>Composite Types</title>
......@@ -66,6 +66,27 @@ SELECT price_extension(item, 10) FROM on_hand;
</programlisting>
</para>
<para>
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
<programlisting>
CREATE TABLE inventory_item (
name text,
supplier_id integer REFERENCES suppliers,
price numeric CHECK (price > 0)
);
</programlisting>
then the same <literal>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 <emphasis>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.)
</para>
</sect2>
<sect2>
......@@ -178,6 +199,49 @@ SELECT (my_func(...)).field FROM ...
</para>
</sect2>
<sect2>
<title>Modifying Composite Types</title>
<para>
Here are some examples of the proper syntax for inserting and updating
composite columns.
First, inserting or updating a whole column:
<programlisting>
INSERT INTO mytab (complex_col) VALUES((1.1,2.2));
UPDATE mytab SET complex_col = ROW(1.1,2.2) WHERE ...;
</programlisting>
The first example omits <literal>ROW</>, the second uses it; we
could have done it either way.
</para>
<para>
We can update an individual subfield of a composite column:
<programlisting>
UPDATE mytab SET complex_col.r = (complex_col).r + 1 WHERE ...;
</programlisting>
Notice here that we don't need to (and indeed cannot)
put parentheses around the column name appearing just after
<literal>SET</>, but we do need parentheses when referencing the same
column in the expression to the right of the equal sign.
</para>
<para>
And we can specify subfields as targets for <command>INSERT</>, too:
<programlisting>
INSERT INTO mytab (complex_col.r, complex_col.i) VALUES(1.1, 2.2);
</programlisting>
Had we not supplied values for all the subfields of the column, the
remaining subfields would have been filled with NULLs.
</para>
</sect2>
<sect2>
<title>Composite Type Input and Output Syntax</title>
......
......@@ -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;
......
......@@ -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);
......
......@@ -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);
......
......@@ -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:
......
......@@ -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))
......
......@@ -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;
......
......@@ -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,
......
This diff is collapsed.
......@@ -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));
......
......@@ -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;
......
......@@ -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;
}
......
This diff is collapsed.
......@@ -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.
......
This diff is collapsed.
......@@ -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
* ----------------
......
......@@ -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,
......
......@@ -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;
......
......@@ -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
{
......
......@@ -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);
......
......@@ -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);
......
......@@ -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],
......
......@@ -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.
......
......@@ -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}');
......
......@@ -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.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment