<!--
-$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
-->
<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>
<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
-->
<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>
</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>
</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>
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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);
ArrayType *array_source;
ArrayType *resultArray;
bool isAssignment = (arrayRef->refassgnexpr != NULL);
+ bool eisnull;
ListCell *l;
int i = 0,
j = 0;
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)
{
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);
}
}
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);
}
}
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,
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
*
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;
* 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 $
*
*-------------------------------------------------------------------------
*/
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
*/
ColumnRef *newnode = makeNode(ColumnRef);
COPY_NODE_FIELD(fields);
- COPY_NODE_FIELD(indirection);
return newnode;
}
ParamRef *newnode = makeNode(ParamRef);
COPY_SCALAR_FIELD(number);
- COPY_NODE_FIELD(fields);
- COPY_NODE_FIELD(indirection);
return newnode;
}
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;
case T_FieldSelect:
retval = _copyFieldSelect(from);
break;
+ case T_FieldStore:
+ retval = _copyFieldStore(from);
+ break;
case T_RelabelType:
retval = _copyRelabelType(from);
break;
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);
* 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 $
*
*-------------------------------------------------------------------------
*/
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)
{
_equalColumnRef(ColumnRef *a, ColumnRef *b)
{
COMPARE_NODE_FIELD(fields);
- COMPARE_NODE_FIELD(indirection);
return true;
}
_equalParamRef(ParamRef *a, ParamRef *b)
{
COMPARE_SCALAR_FIELD(number);
- COMPARE_NODE_FIELD(fields);
- COMPARE_NODE_FIELD(indirection);
return true;
}
}
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;
case T_FieldSelect:
retval = _equalFieldSelect(a, b);
break;
+ case T_FieldStore:
+ retval = _equalFieldStore(a, b);
+ break;
case T_RelabelType:
retval = _equalRelabelType(a, b);
break;
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);
*
*
* 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*
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)
{
{
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
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)
{
WRITE_NODE_TYPE("COLUMNREF");
WRITE_NODE_FIELD(fields);
- WRITE_NODE_FIELD(indirection);
}
static void
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)
{
case T_FieldSelect:
_outFieldSelect(str, obj);
break;
+ case T_FieldStore:
+ _outFieldStore(str, obj);
+ break;
case T_RelabelType:
_outRelabelType(str, obj);
break;
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);
case T_FuncCall:
_outFuncCall(str, obj);
break;
+ case T_DefElem:
+ _outDefElem(str, obj);
+ break;
default:
*
*
* 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
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
*/
* 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)
{
READ_DONE();
}
-static ExprFieldSelect *
-_readExprFieldSelect(void)
-{
- READ_LOCALS(ExprFieldSelect);
-
- READ_NODE_FIELD(arg);
- READ_NODE_FIELD(fields);
- READ_NODE_FIELD(indirection);
-
- READ_DONE();
-}
-
/*
* _readRangeTblEntry
*/
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))
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))
*
*
* 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
/* 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;
}
if (IsA(node, SubPlan))
return true;
+ if (IsA(node, FieldStore))
+ return true;
if (IsA(node, CaseExpr))
return true;
if (IsA(node, CaseWhen))
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:
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;
* 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 $
*
*-------------------------------------------------------------------------
*/
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,
*
*
* 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
*/
/*#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);
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,
List *list;
Node *node;
Value *value;
- ColumnRef *columnref;
ObjectType objtype;
TypeName *typnam;
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
%type <node> TableElement ConstraintElem TableFuncElement
%type <node> columnDef
%type <defelt> def_elem
-%type <node> def_arg columnElem where_clause insert_column_item
- a_expr b_expr c_expr AexprConst
+%type <node> def_arg columnElem where_clause
+ a_expr b_expr c_expr AexprConst indirection_el columnref
in_expr having_clause func_table array_expr
%type <list> row type_list array_expr_list
%type <node> case_expr case_arg when_clause case_default
%type <list> OptCreateAs CreateAsList
%type <node> CreateAsElement
%type <value> NumericOnly FloatOnly IntegerOnly
-%type <columnref> columnref
%type <alias> alias_clause
%type <sortby> sortby
%type <ielem> index_elem
%type <node> table_ref
%type <jexpr> joined_table
%type <range> relation_expr
-%type <target> target_el insert_target_el update_target_el
+%type <target> target_el insert_target_el update_target_el insert_column_item
%type <typnam> Typename SimpleTypename ConstTypename
GenericType Numeric opt_float
/* 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:
;
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:
;
insert_column_list:
- insert_column_item { $$ = list_make1($1); }
+ insert_column_item
+ { $$ = list_make1($1); }
| insert_column_list ',' insert_column_item
{ $$ = lappend($1, $3); }
;
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;
}
;
* 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;
}
*/
;
-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);
* 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); }
{
ColumnRef *n = makeNode(ColumnRef);
n->fields = list_make1(makeString("*"));
- n->indirection = NIL;
+
$$ = makeNode(ResTarget);
$$->name = NULL;
$$->indirection = NIL;
}
;
-/* 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); }
;
insert_target_el:
- target_el { $$ = $1; }
+ a_expr
+ {
+ $$ = makeNode(ResTarget);
+ $$->name = NULL;
+ $$->indirection = NIL;
+ $$->val = (Node *)$1;
+ }
| DEFAULT
{
$$ = makeNode(ResTarget);
$$->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;
}
}
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)); }
;
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);
%%
+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)
{
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
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
*----------
*/
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));
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
int paramno = pref->number;
ParseState *toppstate;
Param *param;
- ListCell *fields;
/*
* Find topmost ParseState, which is where paramtype info
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:
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:
case T_NullIfExpr:
case T_BoolExpr:
case T_FieldSelect:
+ case T_FieldStore:
case T_RelabelType:
case T_CaseTestExpr:
case T_CoerceToDomain:
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 *
}
/*
- * 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
break;
}
- return transformIndirection(pstate, node, cref->indirection);
+ return node;
}
/*
case T_FieldSelect:
type = ((FieldSelect *) expr)->resulttype;
break;
+ case T_FieldStore:
+ type = ((FieldStore *) expr)->resulttype;
+ break;
case T_RelabelType:
type = ((RelabelType *) expr)->resulttype;
break;
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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
*
* 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;
}
}
A_Indices *ai = (A_Indices *) lfirst(idx);
Node *subexpr;
+ Assert(IsA(ai, A_Indices));
if (isSlice)
{
if (ai->lidx)
/*
* 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.")));
}
/*
aref->refexpr = (Expr *) arrayBase;
aref->refassgnexpr = (Expr *) assignFrom;
- ReleaseSysCache(type_tuple_array);
-
return aref;
}
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
#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);
* 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)
* 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,
* 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))
{
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.")));
}
/*
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 -
/*
* 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);
}
}
{
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:
* 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 $
*
*-------------------------------------------------------------------------
*/
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);
* 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)
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)
{
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\"",
/*
* 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.
* 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.
*
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);
RangeTblEntry *rte;
char *sep;
ListCell *l;
+ List *strippedexprs;
/*
* If it's an INSERT ... SELECT there will be a single subquery RTE
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);
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, ") ");
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, ')');
}
foreach(l, query->targetList)
{
TargetEntry *tle = (TargetEntry *) lfirst(l);
+ Node *expr;
if (tle->resdom->resjunk)
continue; /* ignore junk entries */
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 */
*/
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,
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;
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);
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;
}
/*
- * 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, ']');
+ }
}
/*
* 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 $
*
*-------------------------------------------------------------------------
*/
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
* ----------------
* 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 $
*
*-------------------------------------------------------------------------
*/
T_SubLink,
T_SubPlan,
T_FieldSelect,
+ T_FieldStore,
T_RelabelType,
T_CaseExpr,
T_CaseWhen,
T_BoolExprState,
T_SubPlanState,
T_FieldSelectState,
+ T_FieldStoreState,
T_CaseExprState,
T_CaseWhenState,
T_ArrayExprState,
T_A_Const,
T_FuncCall,
T_A_Indices,
- T_ExprFieldSelect,
+ T_A_Indirection,
T_ResTarget,
T_TypeCast,
T_SortBy,
* 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 $
*
*-------------------------------------------------------------------------
*/
* 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;
/*
} 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;
* 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 $
*
*-------------------------------------------------------------------------
*/
* 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
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
*
* 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
{
* 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 $
*
*-------------------------------------------------------------------------
*/
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);
* 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.
*
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);
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)
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],
(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.
);
--
--- 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';
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}');
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.