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