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

@ -1,4 +1,4 @@
<!-- $PostgreSQL: pgsql/doc/src/sgml/syntax.sgml,v 1.121 2008/01/23 19:51:29 tgl Exp $ -->
<!-- $PostgreSQL: pgsql/doc/src/sgml/syntax.sgml,v 1.122 2008/03/20 21:42:47 tgl Exp $ -->
<chapter id="sql-syntax">
<title>SQL Syntax</title>
@ -1305,7 +1305,7 @@ sqrt(2)
where <replaceable>aggregate_name</replaceable> is a previously
defined aggregate (possibly qualified with a schema name), and
<replaceable>expression</replaceable> is
<replaceable>expression</replaceable> is
any value expression that does not itself contain an aggregate
expression.
</para>
@ -1335,7 +1335,7 @@ sqrt(2)
<para>
The predefined aggregate functions are described in <xref
linkend="functions-aggregate">. Other aggregate functions can be added
by the user.
by the user.
</para>
<para>
@ -1495,9 +1495,9 @@ SELECT name, (SELECT max(pop) FROM cities WHERE cities.state = states.name)
<para>
An array constructor is an expression that builds an
array value from values for its member elements. A simple array
constructor
constructor
consists of the key word <literal>ARRAY</literal>, a left square bracket
<literal>[</>, one or more expressions (separated by commas) for the
<literal>[</>, a list of expressions (separated by commas) for the
array element values, and finally a right square bracket <literal>]</>.
For example:
<programlisting>
@ -1507,9 +1507,22 @@ SELECT ARRAY[1,2,3+4];
{1,2,7}
(1 row)
</programlisting>
The array element type is the common type of the member expressions,
By default,
the array element type is the common type of the member expressions,
determined using the same rules as for <literal>UNION</> or
<literal>CASE</> constructs (see <xref linkend="typeconv-union-case">).
<literal>CASE</> constructs (see <xref linkend="typeconv-union-case">).
You can override this by explicitly casting the array constructor to the
desired type, for example:
<programlisting>
SELECT ARRAY[1,2,22.7]::integer[];
array
----------
{1,2,23}
(1 row)
</programlisting>
This has the same effect as casting each expression to the array
element type individually.
For more on casting, see <xref linkend="sql-syntax-type-casts">.
</para>
<para>
@ -1534,6 +1547,8 @@ SELECT ARRAY[[1,2],[3,4]];
Since multidimensional arrays must be rectangular, inner constructors
at the same level must produce sub-arrays of identical dimensions.
Any cast applied to the outer <literal>ARRAY</> constructor propagates
automatically to all the inner constructors.
</para>
<para>
@ -1553,6 +1568,19 @@ SELECT ARRAY[f1, f2, '{{9,10},{11,12}}'::int[]] FROM arr;
</programlisting>
</para>
<para>
You can construct an empty array, but since it's impossible to have an
array with no type, you must explicitly cast your empty array to the
desired type. For example:
<programlisting>
SELECT ARRAY[]::integer[];
array
-------
{}
(1 row)
</programlisting>
</para>
<para>
It is also possible to construct an array from the results of a
subquery. In this form, the array constructor is written with the

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