aboutsummaryrefslogtreecommitdiff
path: root/src/backend/parser/parse_target.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2004-06-09 19:08:20 +0000
committerTom Lane <tgl@sss.pgh.pa.us>2004-06-09 19:08:20 +0000
commit7e64dbc6b5e516a2510ae41c8c7999d1d8d25872 (patch)
treec819b78903b490e720b4c20969ed6cf8816889d1 /src/backend/parser/parse_target.c
parent3a0df651da253879bf133a8556853acfb1f664fd (diff)
downloadpostgresql-7e64dbc6b5e516a2510ae41c8c7999d1d8d25872.tar.gz
postgresql-7e64dbc6b5e516a2510ae41c8c7999d1d8d25872.zip
Support assignment to subfields of composite columns in UPDATE and INSERT.
As a side effect, cause subscripts in INSERT targetlists to do something more or less sensible; previously we evaluated such subscripts and then effectively ignored them. Another side effect is that UPDATE-ing an element or slice of an array value that is NULL now produces a non-null result, namely an array containing just the assigned-to positions.
Diffstat (limited to 'src/backend/parser/parse_target.c')
-rw-r--r--src/backend/parser/parse_target.c400
1 files changed, 323 insertions, 77 deletions
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 3856005fab6..e0f0e6c930e 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/parser/parse_target.c,v 1.120 2004/06/01 03:28:48 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/parse_target.c,v 1.121 2004/06/09 19:08:17 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -25,9 +25,18 @@
#include "parser/parse_target.h"
#include "parser/parse_type.h"
#include "utils/builtins.h"
+#include "utils/lsyscache.h"
static void markTargetListOrigin(ParseState *pstate, Resdom *res, Var *var);
+static Node *transformAssignmentIndirection(ParseState *pstate,
+ Node *basenode,
+ const char *targetName,
+ bool targetIsArray,
+ Oid targetTypeId,
+ int32 targetTypMod,
+ ListCell *indirection,
+ Node *rhs);
static List *ExpandAllTables(ParseState *pstate);
static char *FigureColname(Node *node);
static int FigureColnameInternal(Node *node, char **name);
@@ -87,7 +96,7 @@ transformTargetEntry(ParseState *pstate,
* Turns a list of ResTarget's into a list of TargetEntry's.
*
* At this point, we don't care whether we are doing SELECT, INSERT,
- * or UPDATE; we just transform the given expressions.
+ * or UPDATE; we just transform the given expressions (the "val" fields).
*/
List *
transformTargetList(ParseState *pstate, List *targetlist)
@@ -284,14 +293,14 @@ markTargetListOrigin(ParseState *pstate, Resdom *res, Var *var)
* This is used in INSERT and UPDATE statements only. It prepares a
* TargetEntry for assignment to a column of the target table.
* This includes coercing the given value to the target column's type
- * (if necessary), and dealing with any subscripts attached to the target
- * column itself.
+ * (if necessary), and dealing with any subfield names or subscripts
+ * attached to the target column itself.
*
* pstate parse state
* tle target list entry to be modified
* colname target column name (ie, name of attribute to be assigned to)
* attrno target attribute number
- * indirection subscripts for target column, if any
+ * indirection subscripts/field names for target column, if any
*/
void
updateTargetListEntry(ParseState *pstate,
@@ -320,8 +329,8 @@ updateTargetListEntry(ParseState *pstate,
* type/typmod into it so that exprType will report the right things.
* (We expect that the eventually substituted default expression will
* in fact have this type and typmod.) Also, reject trying to update
- * an array element with DEFAULT, since there can't be any default for
- * individual elements of a column.
+ * a subfield or array element with DEFAULT, since there can't be any
+ * default for portions of a column.
*/
if (tle->expr && IsA(tle->expr, SetToDefault))
{
@@ -330,82 +339,81 @@ updateTargetListEntry(ParseState *pstate,
def->typeId = attrtype;
def->typeMod = attrtypmod;
if (indirection)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot set an array element to DEFAULT")));
+ {
+ if (IsA(linitial(indirection), A_Indices))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot set an array element to DEFAULT")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot set a subfield to DEFAULT")));
+ }
}
/* Now we can use exprType() safely. */
type_id = exprType((Node *) tle->expr);
/*
- * If there are subscripts on the target column, prepare an array
- * assignment expression. This will generate an array value that the
- * source value has been inserted into, which can then be placed in
- * the new tuple constructed by INSERT or UPDATE. Note that
- * transformArraySubscripts takes care of type coercion.
+ * If there is indirection on the target column, prepare an array or
+ * subfield assignment expression. This will generate a new column value
+ * that the source value has been inserted into, which can then be placed
+ * in the new tuple constructed by INSERT or UPDATE.
*/
if (indirection)
{
- Node *arrayBase;
- ArrayRef *aref;
+ Node *colVar;
if (pstate->p_is_insert)
{
/*
- * The command is INSERT INTO table (arraycol[subscripts]) ...
- * so there is not really a source array value to work with.
- * Let the executor do something reasonable, if it can. Notice
- * that we force transformArraySubscripts to treat the
- * subscripting op as an array-slice op below, so the source
- * data will have been coerced to the array type.
+ * The command is INSERT INTO table (col.something) ...
+ * so there is not really a source value to work with.
+ * Insert a NULL constant as the source value.
*/
- arrayBase = NULL; /* signal there is no source array */
+ colVar = (Node *) makeNullConst(attrtype);
}
else
{
/*
- * Build a Var for the array to be updated.
+ * Build a Var for the column to be updated.
*/
- arrayBase = (Node *) make_var(pstate,
- pstate->p_target_rangetblentry,
- attrno);
+ colVar = (Node *) make_var(pstate,
+ pstate->p_target_rangetblentry,
+ attrno);
}
- aref = transformArraySubscripts(pstate,
- arrayBase,
- attrtype,
- attrtypmod,
- indirection,
- pstate->p_is_insert,
- (Node *) tle->expr);
- tle->expr = (Expr *) aref;
+ tle->expr = (Expr *)
+ transformAssignmentIndirection(pstate,
+ colVar,
+ colname,
+ false,
+ attrtype,
+ attrtypmod,
+ list_head(indirection),
+ (Node *) tle->expr);
}
else
{
/*
- * For normal non-subscripted target column, do type checking and
- * coercion. But accept InvalidOid, which indicates the source is
- * a NULL constant. (XXX is that still true?)
+ * For normal non-qualified target column, do type checking and
+ * coercion.
*/
- if (type_id != InvalidOid)
- {
- tle->expr = (Expr *)
- coerce_to_target_type(pstate,
- (Node *) tle->expr, type_id,
- attrtype, attrtypmod,
- COERCION_ASSIGNMENT,
- COERCE_IMPLICIT_CAST);
- if (tle->expr == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("column \"%s\" is of type %s"
- " but expression is of type %s",
- colname,
- format_type_be(attrtype),
- format_type_be(type_id)),
- errhint("You will need to rewrite or cast the expression.")));
- }
+ tle->expr = (Expr *)
+ coerce_to_target_type(pstate,
+ (Node *) tle->expr, type_id,
+ attrtype, attrtypmod,
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST);
+ if (tle->expr == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("column \"%s\" is of type %s"
+ " but expression is of type %s",
+ colname,
+ format_type_be(attrtype),
+ format_type_be(type_id)),
+ errhint("You will need to rewrite or cast the expression.")));
}
/*
@@ -425,6 +433,208 @@ updateTargetListEntry(ParseState *pstate,
resnode->resname = colname;
}
+/*
+ * Process indirection (field selection or subscripting) of the target
+ * column in INSERT/UPDATE. This routine recurses for multiple levels
+ * of indirection --- but note that several adjacent A_Indices nodes in
+ * the indirection list are treated as a single multidimensional subscript
+ * operation.
+ *
+ * In the initial call, basenode is a Var for the target column in UPDATE,
+ * or a null Const of the target's type in INSERT. In recursive calls,
+ * basenode is NULL, indicating that a substitute node should be consed up if
+ * needed.
+ *
+ * targetName is the name of the field or subfield we're assigning to, and
+ * targetIsArray is true if we're subscripting it. These are just for
+ * error reporting.
+ *
+ * targetTypeId and targetTypMod indicate the datatype of the object to
+ * be assigned to (initially the target column, later some subobject).
+ *
+ * indirection is the sublist remaining to process. When it's NULL, we're
+ * done recursing and can just coerce and return the RHS.
+ *
+ * rhs is the already-transformed value to be assigned; note it has not been
+ * coerced to any particular type.
+ */
+static Node *
+transformAssignmentIndirection(ParseState *pstate,
+ Node *basenode,
+ const char *targetName,
+ bool targetIsArray,
+ Oid targetTypeId,
+ int32 targetTypMod,
+ ListCell *indirection,
+ Node *rhs)
+{
+ Node *result;
+ List *subscripts = NIL;
+ bool isSlice = false;
+ ListCell *i;
+
+ if (indirection && !basenode)
+ {
+ /* Set up a substitution. We reuse CaseTestExpr for this. */
+ CaseTestExpr *ctest = makeNode(CaseTestExpr);
+
+ ctest->typeId = targetTypeId;
+ ctest->typeMod = targetTypMod;
+ basenode = (Node *) ctest;
+ }
+
+ /*
+ * We have to split any field-selection operations apart from
+ * subscripting. Adjacent A_Indices nodes have to be treated
+ * as a single multidimensional subscript operation.
+ */
+ for_each_cell(i, indirection)
+ {
+ Node *n = lfirst(i);
+
+ if (IsA(n, A_Indices))
+ {
+ subscripts = lappend(subscripts, n);
+ if (((A_Indices *) n)->lidx != NULL)
+ isSlice = true;
+ }
+ else
+ {
+ FieldStore *fstore;
+ Oid typrelid;
+ AttrNumber attnum;
+ Oid fieldTypeId;
+ int32 fieldTypMod;
+
+ Assert(IsA(n, String));
+
+ /* process subscripts before this field selection */
+ if (subscripts)
+ {
+ Oid elementTypeId = transformArrayType(targetTypeId);
+ Oid typeNeeded = isSlice ? targetTypeId : elementTypeId;
+
+ /* recurse to create appropriate RHS for array assign */
+ rhs = transformAssignmentIndirection(pstate,
+ NULL,
+ targetName,
+ true,
+ typeNeeded,
+ targetTypMod,
+ i,
+ rhs);
+ /* process subscripts */
+ return (Node *) transformArraySubscripts(pstate,
+ basenode,
+ targetTypeId,
+ elementTypeId,
+ targetTypMod,
+ subscripts,
+ rhs);
+ }
+
+ /* No subscripts, so can process field selection here */
+
+ typrelid = typeidTypeRelid(targetTypeId);
+ if (!typrelid)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot assign to a column of type %s because it is not a composite type",
+ format_type_be(targetTypeId))));
+
+ attnum = get_attnum(typrelid, strVal(n));
+ if (attnum == InvalidAttrNumber)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" not found in data type %s",
+ strVal(n), format_type_be(targetTypeId))));
+ if (attnum < 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("cannot assign to system column \"%s\"",
+ strVal(n))));
+
+ get_atttypetypmod(typrelid, attnum,
+ &fieldTypeId, &fieldTypMod);
+
+ /* recurse to create appropriate RHS for field assign */
+ rhs = transformAssignmentIndirection(pstate,
+ NULL,
+ strVal(n),
+ false,
+ fieldTypeId,
+ fieldTypMod,
+ lnext(i),
+ rhs);
+
+ /* and build a FieldStore node */
+ fstore = makeNode(FieldStore);
+ fstore->arg = (Expr *) basenode;
+ fstore->newvals = list_make1(rhs);
+ fstore->fieldnums = list_make1_int(attnum);
+ fstore->resulttype = targetTypeId;
+
+ return (Node *) fstore;
+ }
+ }
+
+ /* process trailing subscripts, if any */
+ if (subscripts)
+ {
+ Oid elementTypeId = transformArrayType(targetTypeId);
+ Oid typeNeeded = isSlice ? targetTypeId : elementTypeId;
+
+ /* recurse to create appropriate RHS for array assign */
+ rhs = transformAssignmentIndirection(pstate,
+ NULL,
+ targetName,
+ true,
+ typeNeeded,
+ targetTypMod,
+ NULL,
+ rhs);
+ /* process subscripts */
+ return (Node *) transformArraySubscripts(pstate,
+ basenode,
+ targetTypeId,
+ elementTypeId,
+ targetTypMod,
+ subscripts,
+ rhs);
+ }
+
+ /* base case: just coerce RHS to match target type ID */
+
+ result = coerce_to_target_type(pstate,
+ rhs, exprType(rhs),
+ targetTypeId, targetTypMod,
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST);
+ if (result == NULL)
+ {
+ if (targetIsArray)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("array assignment to \"%s\" requires type %s"
+ " but expression is of type %s",
+ targetName,
+ format_type_be(targetTypeId),
+ format_type_be(exprType(rhs))),
+ errhint("You will need to rewrite or cast the expression.")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("subfield \"%s\" is of type %s"
+ " but expression is of type %s",
+ targetName,
+ format_type_be(targetTypeId),
+ format_type_be(exprType(rhs))),
+ errhint("You will need to rewrite or cast the expression.")));
+ }
+
+ return result;
+}
+
/*
* checkInsertTargets -
@@ -466,21 +676,42 @@ checkInsertTargets(ParseState *pstate, List *cols, List **attrnos)
/*
* Do initial validation of user-supplied INSERT column list.
*/
+ List *wholecols = NIL;
ListCell *tl;
foreach(tl, cols)
{
- char *name = ((ResTarget *) lfirst(tl))->name;
+ ResTarget *col = (ResTarget *) lfirst(tl);
+ char *name = col->name;
int attrno;
/* Lookup column name, ereport on failure */
attrno = attnameAttNum(pstate->p_target_relation, name, false);
- /* Check for duplicates */
- if (list_member_int(*attrnos, attrno))
- ereport(ERROR,
- (errcode(ERRCODE_DUPLICATE_COLUMN),
- errmsg("column \"%s\" specified more than once",
- name)));
+
+ /*
+ * Check for duplicates, but only of whole columns --- we
+ * allow INSERT INTO foo (col.subcol1, col.subcol2)
+ */
+ if (col->indirection == NIL)
+ {
+ /* whole column; must not have any other assignment */
+ if (list_member_int(*attrnos, attrno))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column \"%s\" specified more than once",
+ name)));
+ wholecols = lappend_int(wholecols, attrno);
+ }
+ else
+ {
+ /* partial column; must not have any whole assignment */
+ if (list_member_int(wholecols, attrno))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column \"%s\" specified more than once",
+ name)));
+ }
+
*attrnos = lappend_int(*attrnos, attrno);
}
}
@@ -572,30 +803,45 @@ FigureColnameInternal(Node *node, char **name)
{
case T_ColumnRef:
{
- char *cname = strVal(llast(((ColumnRef *) node)->fields));
+ char *fname = NULL;
+ ListCell *l;
- if (strcmp(cname, "*") != 0)
+ /* find last field name, if any, ignoring "*" */
+ foreach(l, ((ColumnRef *) node)->fields)
{
- *name = cname;
+ Node *i = lfirst(l);
+
+ if (strcmp(strVal(i), "*") != 0)
+ fname = strVal(i);
+ }
+ if (fname)
+ {
+ *name = fname;
return 2;
}
}
break;
- case T_ExprFieldSelect:
+ case T_A_Indirection:
{
- ExprFieldSelect *efs = (ExprFieldSelect *) node;
+ A_Indirection *ind = (A_Indirection *) node;
+ char *fname = NULL;
+ ListCell *l;
- if (efs->fields)
+ /* find last field name, if any, ignoring "*" */
+ foreach(l, ind->indirection)
{
- char *fname = strVal(llast(efs->fields));
+ Node *i = lfirst(l);
- if (strcmp(fname, "*") != 0)
- {
- *name = fname;
- return 2;
- }
+ if (IsA(i, String) &&
+ strcmp(strVal(i), "*") != 0)
+ fname = strVal(i);
+ }
+ if (fname)
+ {
+ *name = fname;
+ return 2;
}
- return FigureColnameInternal(efs->arg, name);
+ return FigureColnameInternal(ind->arg, name);
}
break;
case T_FuncCall: