1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-20 05:03:10 +03:00

Arrange for an explicit cast applied to an ARRAY[] constructor to be applied

directly to all the member expressions, instead of the previous implementation
where the ARRAY[] constructor would infer a common element type and then we'd
coerce the finished array after the fact.  This has a number of benefits,
one being that we can allow an empty ARRAY[] construct so long as its
element type is specified by such a cast.

Brendan Jurd, minor fixes by me.
This commit is contained in:
Tom Lane
2008-03-20 21:42:48 +00:00
parent 8759b79d0f
commit 6b0706ac33
11 changed files with 312 additions and 100 deletions

View File

@ -15,7 +15,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.388 2008/02/07 20:19:47 tgl Exp $
* $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.389 2008/03/20 21:42:47 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -1684,6 +1684,16 @@ _copyA_Indirection(A_Indirection *from)
return newnode;
}
static A_ArrayExpr *
_copyA_ArrayExpr(A_ArrayExpr *from)
{
A_ArrayExpr *newnode = makeNode(A_ArrayExpr);
COPY_NODE_FIELD(elements);
return newnode;
}
static ResTarget *
_copyResTarget(ResTarget *from)
{
@ -3543,6 +3553,9 @@ copyObject(void *from)
case T_A_Indirection:
retval = _copyA_Indirection(from);
break;
case T_A_ArrayExpr:
retval = _copyA_ArrayExpr(from);
break;
case T_ResTarget:
retval = _copyResTarget(from);
break;

View File

@ -18,7 +18,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.318 2008/02/07 20:19:47 tgl Exp $
* $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.319 2008/03/20 21:42:48 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -1729,6 +1729,14 @@ _equalA_Indirection(A_Indirection *a, A_Indirection *b)
return true;
}
static bool
_equalA_ArrayExpr(A_ArrayExpr *a, A_ArrayExpr *b)
{
COMPARE_NODE_FIELD(elements);
return true;
}
static bool
_equalResTarget(ResTarget *a, ResTarget *b)
{
@ -2469,6 +2477,9 @@ equal(void *a, void *b)
case T_A_Indirection:
retval = _equalA_Indirection(a, b);
break;
case T_A_ArrayExpr:
retval = _equalA_ArrayExpr(a, b);
break;
case T_ResTarget:
retval = _equalResTarget(a, b);
break;

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.322 2008/01/09 08:46:44 neilc Exp $
* $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.323 2008/03/20 21:42:48 tgl Exp $
*
* NOTES
* Every node type that can appear in stored rules' parsetrees *must*
@ -1971,6 +1971,14 @@ _outA_Indirection(StringInfo str, A_Indirection *node)
WRITE_NODE_FIELD(indirection);
}
static void
_outA_ArrayExpr(StringInfo str, A_ArrayExpr *node)
{
WRITE_NODE_TYPE("A_ARRAYEXPR");
WRITE_NODE_FIELD(elements);
}
static void
_outResTarget(StringInfo str, ResTarget *node)
{
@ -2417,6 +2425,9 @@ _outNode(StringInfo str, void *obj)
case T_A_Indirection:
_outA_Indirection(str, obj);
break;
case T_A_ArrayExpr:
_outA_ArrayExpr(str, obj);
break;
case T_ResTarget:
_outResTarget(str, obj);
break;

View File

@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.608 2008/03/19 18:38:30 tgl Exp $
* $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.609 2008/03/20 21:42:48 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
@ -107,6 +107,7 @@ static void insertSelectOptions(SelectStmt *stmt,
static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg);
static Node *doNegate(Node *n, int location);
static void doNegateFloat(Value *v);
static Node *makeAArrayExpr(List *elements);
static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *args);
%}
@ -8429,6 +8430,29 @@ expr_list: a_expr
}
;
type_list: Typename { $$ = list_make1($1); }
| type_list ',' Typename { $$ = lappend($1, $3); }
;
array_expr: '[' expr_list ']'
{
$$ = makeAArrayExpr($2);
}
| '[' array_expr_list ']'
{
$$ = makeAArrayExpr($2);
}
| '[' ']'
{
$$ = makeAArrayExpr(NIL);
}
;
array_expr_list: array_expr { $$ = list_make1($1); }
| array_expr_list ',' array_expr { $$ = lappend($1, $3); }
;
extract_list:
extract_arg FROM a_expr
{
@ -8440,34 +8464,9 @@ extract_list:
| /*EMPTY*/ { $$ = NIL; }
;
type_list: Typename { $$ = list_make1($1); }
| type_list ',' Typename { $$ = lappend($1, $3); }
;
array_expr_list: array_expr
{ $$ = list_make1($1); }
| array_expr_list ',' array_expr
{ $$ = lappend($1, $3); }
;
array_expr: '[' expr_list ']'
{
ArrayExpr *n = makeNode(ArrayExpr);
n->elements = $2;
$$ = (Node *)n;
}
| '[' array_expr_list ']'
{
ArrayExpr *n = makeNode(ArrayExpr);
n->elements = $2;
$$ = (Node *)n;
}
;
/* Allow delimited string SCONST in extract_arg as an SQL extension.
* - thomas 2001-04-12
*/
extract_arg:
IDENT { $$ = $1; }
| YEAR_P { $$ = "year"; }
@ -9502,13 +9501,6 @@ makeColumnRef(char *relname, List *indirection, int location)
static Node *
makeTypeCast(Node *arg, TypeName *typename)
{
/*
* Simply generate a TypeCast node.
*
* Earlier we would determine whether an A_Const would
* be acceptable, however Domains require coerce_type()
* to process them -- applying constraints as required.
*/
TypeCast *n = makeNode(TypeCast);
n->arg = arg;
n->typename = typename;
@ -9582,7 +9574,7 @@ makeBoolAConst(bool state)
{
A_Const *n = makeNode(A_Const);
n->val.type = T_String;
n->val.val.str = (state? "t": "f");
n->val.val.str = (state ? "t" : "f");
n->typename = SystemTypeName("bool");
return n;
}
@ -9763,15 +9755,6 @@ SystemTypeName(char *name)
makeString(name)));
}
/* parser_init()
* Initialize to parse one query string
*/
void
parser_init(void)
{
QueryIsRule = FALSE;
}
/* doNegate()
* Handle negation of a numeric constant.
*
@ -9827,6 +9810,15 @@ doNegateFloat(Value *v)
}
}
static Node *
makeAArrayExpr(List *elements)
{
A_ArrayExpr *n = makeNode(A_ArrayExpr);
n->elements = elements;
return (Node *) n;
}
static Node *
makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *args)
{
@ -9844,6 +9836,15 @@ makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *args)
return (Node *) x;
}
/* parser_init()
* Initialize to parse one query string
*/
void
parser_init(void)
{
QueryIsRule = FALSE;
}
/*
* Must undefine base_yylex before including scan.c, since we want it
* to create the function base_yylex not filtered_base_yylex.

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.226 2008/01/01 19:45:50 momjian Exp $
* $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.227 2008/03/20 21:42:48 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -52,7 +52,8 @@ static Node *transformAExprIn(ParseState *pstate, A_Expr *a);
static Node *transformFuncCall(ParseState *pstate, FuncCall *fn);
static Node *transformCaseExpr(ParseState *pstate, CaseExpr *c);
static Node *transformSubLink(ParseState *pstate, SubLink *sublink);
static Node *transformArrayExpr(ParseState *pstate, ArrayExpr *a);
static Node *transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
Oid array_type, Oid element_type, int32 typmod);
static Node *transformRowExpr(ParseState *pstate, RowExpr *r);
static Node *transformCoalesceExpr(ParseState *pstate, CoalesceExpr *c);
static Node *transformMinMaxExpr(ParseState *pstate, MinMaxExpr *m);
@ -142,11 +143,49 @@ transformExpr(ParseState *pstate, Node *expr)
break;
}
case T_A_ArrayExpr:
result = transformArrayExpr(pstate, (A_ArrayExpr *) expr,
InvalidOid, InvalidOid, -1);
break;
case T_TypeCast:
{
TypeCast *tc = (TypeCast *) expr;
Node *arg = transformExpr(pstate, tc->arg);
Node *arg;
/*
* If the subject of the typecast is an ARRAY[] construct
* and the target type is an array type, we invoke
* transformArrayExpr() directly so that we can pass down
* the type information. This avoids some cases where
* transformArrayExpr() might not infer the correct type.
*/
if (IsA(tc->arg, A_ArrayExpr))
{
Oid targetType;
Oid elementType;
int32 targetTypmod;
targetType = typenameTypeId(pstate, tc->typename,
&targetTypmod);
elementType = get_element_type(targetType);
if (OidIsValid(elementType))
{
result = transformArrayExpr(pstate,
(A_ArrayExpr *) tc->arg,
targetType,
elementType,
targetTypmod);
break;
}
/*
* Corner case: ARRAY[] cast to a non-array type.
* Fall through to do it the standard way.
*/
}
arg = transformExpr(pstate, tc->arg);
result = typecast_expression(pstate, arg, tc->typename);
break;
}
@ -205,10 +244,6 @@ transformExpr(ParseState *pstate, Node *expr)
result = transformCaseExpr(pstate, (CaseExpr *) expr);
break;
case T_ArrayExpr:
result = transformArrayExpr(pstate, (ArrayExpr *) expr);
break;
case T_RowExpr:
result = transformRowExpr(pstate, (RowExpr *) expr);
break;
@ -1255,64 +1290,156 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
return result;
}
/*
* transformArrayExpr
*
* If the caller specifies the target type, the resulting array will
* be of exactly that type. Otherwise we try to infer a common type
* for the elements using select_common_type().
*/
static Node *
transformArrayExpr(ParseState *pstate, ArrayExpr *a)
transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
Oid array_type, Oid element_type, int32 typmod)
{
ArrayExpr *newa = makeNode(ArrayExpr);
List *newelems = NIL;
List *newcoercedelems = NIL;
List *typeids = NIL;
ListCell *element;
Oid array_type;
Oid element_type;
Oid coerce_type;
bool coerce_hard;
/* Transform the element expressions */
/*
* Transform the element expressions
*
* Assume that the array is one-dimensional unless we find an
* array-type element expression.
*/
newa->multidims = false;
foreach(element, a->elements)
{
Node *e = (Node *) lfirst(element);
Node *newe;
Oid newe_type;
/*
* If an element is itself an A_ArrayExpr, recurse directly so that
* we can pass down any target type we were given.
*/
if (IsA(e, A_ArrayExpr))
{
newe = transformArrayExpr(pstate,
(A_ArrayExpr *) e,
array_type,
element_type,
typmod);
newe_type = exprType(newe);
/* we certainly have an array here */
Assert(array_type == InvalidOid || array_type == newe_type);
newa->multidims = true;
}
else
{
newe = transformExpr(pstate, e);
newe_type = exprType(newe);
/*
* Check for sub-array expressions, if we haven't already
* found one.
*/
if (!newa->multidims && type_is_array(newe_type))
newa->multidims = true;
}
newe = transformExpr(pstate, e);
newelems = lappend(newelems, newe);
typeids = lappend_oid(typeids, exprType(newe));
typeids = lappend_oid(typeids, newe_type);
}
/* Select a common type for the elements */
element_type = select_common_type(typeids, "ARRAY");
/*
* Select a target type for the elements.
*
* If we haven't been given a target array type, we must try to deduce a
* common type based on the types of the individual elements present.
*/
if (OidIsValid(array_type))
{
/* Caller must ensure array_type matches element_type */
Assert(OidIsValid(element_type));
coerce_type = (newa->multidims ? array_type : element_type);
coerce_hard = true;
}
else
{
/* Can't handle an empty array without a target type */
if (typeids == NIL)
ereport(ERROR,
(errcode(ERRCODE_INDETERMINATE_DATATYPE),
errmsg("cannot determine type of empty array"),
errhint("Explicitly cast to the desired type, "
"for example ARRAY[]::integer[].")));
/* Coerce arguments to common type if necessary */
/* Select a common type for the elements */
coerce_type = select_common_type(typeids, "ARRAY");
if (newa->multidims)
{
array_type = coerce_type;
element_type = get_element_type(array_type);
if (!OidIsValid(element_type))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("could not find element type for data type %s",
format_type_be(array_type))));
}
else
{
element_type = coerce_type;
array_type = get_array_type(element_type);
if (!OidIsValid(array_type))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("could not find array type for data type %s",
format_type_be(element_type))));
}
coerce_hard = false;
}
/*
* Coerce elements to target type
*
* If the array has been explicitly cast, then the elements are in turn
* explicitly coerced.
*
* If the array's type was merely derived from the common type of its
* elements, then the elements are implicitly coerced to the common type.
* This is consistent with other uses of select_common_type().
*/
foreach(element, newelems)
{
Node *e = (Node *) lfirst(element);
Node *newe;
newe = coerce_to_common_type(pstate, e,
element_type,
"ARRAY");
if (coerce_hard)
{
newe = coerce_to_target_type(pstate, e,
exprType(e),
coerce_type,
typmod,
COERCION_EXPLICIT,
COERCE_EXPLICIT_CAST);
if (newe == NULL)
ereport(ERROR,
(errcode(ERRCODE_CANNOT_COERCE),
errmsg("cannot cast type %s to %s",
format_type_be(exprType(e)),
format_type_be(coerce_type))));
}
else
newe = coerce_to_common_type(pstate, e,
coerce_type,
"ARRAY");
newcoercedelems = lappend(newcoercedelems, newe);
}
/* Do we have an array type to use? */
array_type = get_array_type(element_type);
if (array_type != InvalidOid)
{
/* Elements are presumably of scalar type */
newa->multidims = false;
}
else
{
/* Must be nested array expressions */
newa->multidims = true;
array_type = element_type;
element_type = get_element_type(array_type);
if (!OidIsValid(element_type))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("could not find array type for data type %s",
format_type_be(array_type))));
}
newa->array_typeid = array_type;
newa->element_typeid = element_type;
newa->elements = newcoercedelems;

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/parser/parse_target.c,v 1.158 2008/01/01 19:45:51 momjian Exp $
* $PostgreSQL: pgsql/src/backend/parser/parse_target.c,v 1.159 2008/03/20 21:42:48 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -1294,7 +1294,7 @@ FigureColnameInternal(Node *node, char **name)
return 1;
}
break;
case T_ArrayExpr:
case T_A_ArrayExpr:
/* make ARRAY[] act like a function */
*name = "array";
return 2;

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.205 2008/01/01 19:45:58 momjian Exp $
* $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.206 2008/03/20 21:42:48 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -324,6 +324,7 @@ typedef enum NodeTag
T_FuncCall,
T_A_Indices,
T_A_Indirection,
T_A_ArrayExpr,
T_ResTarget,
T_TypeCast,
T_SortBy,

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.359 2008/02/07 17:09:51 tgl Exp $
* $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.360 2008/03/20 21:42:48 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -242,9 +242,9 @@ typedef struct A_Const
* TypeCast - a CAST expression
*
* NOTE: for mostly historical reasons, A_Const parsenodes contain
* room for a TypeName; we only generate a separate TypeCast node if the
* argument to be casted is not a constant. In theory either representation
* would work, but the combined representation saves a bit of code in many
* room for a TypeName, allowing a constant to be marked as being of a given
* type without a separate TypeCast node. Either representation will work,
* but the combined representation saves a bit of code in many
* productions in gram.y.
*/
typedef struct TypeCast
@ -304,6 +304,15 @@ typedef struct A_Indirection
List *indirection; /* subscripts and/or field names */
} A_Indirection;
/*
* A_ArrayExpr - an ARRAY[] construct
*/
typedef struct A_ArrayExpr
{
NodeTag type;
List *elements; /* array element expressions */
} A_ArrayExpr;
/*
* ResTarget -
* result target (used in target list of pre-transformed parse trees)

View File

@ -785,6 +785,9 @@ select '{}}'::text[];
ERROR: malformed array literal: "{}}"
select '{ }}'::text[];
ERROR: malformed array literal: "{ }}"
select array[];
ERROR: cannot determine type of empty array
HINT: Explicitly cast to the desired type, for example ARRAY[]::integer[].
-- none of the above should be accepted
-- all of the following should be accepted
select '{}'::text[];
@ -826,6 +829,12 @@ select '{
{"@ 0","@ 1 hour 42 mins 20 secs"}
(1 row)
select array[]::text[];
array
-------
{}
(1 row)
-- all of the above should be accepted
-- tests for array aggregates
CREATE TEMP TABLE arraggtest ( f1 INT[], f2 TEXT[][], f3 FLOAT[]);

View File

@ -280,6 +280,7 @@ select E'{{1,2},\\{2,3}}'::text[];
select '{{"1 2" x},{3}}'::text[];
select '{}}'::text[];
select '{ }}'::text[];
select array[];
-- none of the above should be accepted
-- all of the following should be accepted
@ -292,6 +293,7 @@ select '{
0 second,
@ 1 hour @ 42 minutes @ 20 seconds
}'::interval[];
select array[]::text[];
-- all of the above should be accepted
-- tests for array aggregates