As discussed on -hackers, this patch allows the construction of an
empty array if an explicit cast to an array type is given (as in,
ARRAY[]::int[]).

postgres=# select array[]::int[];
 array
-------
 {}

postgres=# select array[];
ERROR:  no target type for empty array
HINT:  Empty arrays must be explictly cast to the desired array type,
e.g. ARRAY[]::int[]

A few notes on the implementation:

 * The syntax now allows an ARRAY constructor with an empty expression
list (array_expr_list may be empty).

 * I've added a new parsenode for arrays, A_ArrayExpr (previously the
parser would create ArrayExpr primnodes).

 * transformArrayExpr() now takes two extra arguments, a type oid and
a typmod.  When transforming a typecast which casts an A_ArrayExpr to
an array type, transformExpr passes these type details down to
transformArrayExpr, and skips the typecast.

 * transformArrayExpr() behaves slightly differently when passed type
information.  The overall type of the array is set to the given type,
and all elements are explictly coerced to the equivalent element type.
 If it was not passed a type, then the behaviour is as previous; the
function looks for a common type among the elements, and coerces them
to that type.  The overall type of the array is derived from the
common element type.

The patch is very invasive (at least compared to any of my previous
patches), but so far I haven't managed to find any broken behaviour.
All regression tests pass, and the regression tests for arrays seem to
be quite comprehensive.  I did add a couple of new tests for the empty
array behaviours, but the rest I've left alone.

I look forward to your comments -- although given the length of the
8.4 patch review queue, that will probably be an exercise in extreme
patience!

Major thanks go out to Tom for all his guidance on -hackers while I
developed the patch.

Regards,
BJ
*** ./doc/src/sgml/syntax.sgml.orig     Fri Nov 30 19:31:29 2007
--- ./doc/src/sgml/syntax.sgml  Fri Nov 30 19:32:11 2007
***************
*** 1497,1503 ****
      array value from values for its member elements.  A simple array
      constructor 
      consists of the key word <literal>ARRAY</literal>, a left square bracket
!     <literal>[</>, one or more expressions (separated by commas) for the
      array element values, and finally a right square bracket <literal>]</>.
      For example:
  <programlisting>
--- 1497,1503 ----
      array value from values for its member elements.  A simple array
      constructor 
      consists of the key word <literal>ARRAY</literal>, a left square bracket
!     <literal>[</>, a list of expressions (separated by commas) for the
      array element values, and finally a right square bracket <literal>]</>.
      For example:
  <programlisting>
***************
*** 1507,1515 ****
   {1,2,7}
  (1 row)
  </programlisting>
!     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">). 
     </para>
  
     <para>
--- 1507,1516 ----
   {1,2,7}
  (1 row)
  </programlisting>
!       If the array is not explictly cast to a particular type, 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">). 
     </para>
  
     <para>
***************
*** 1554,1559 ****
--- 1555,1573 ----
    </para>
  
    <para>
+    You can construct an empty array, but since it's impossible to have an 
array
+    with no type, you must explictly cast your empty array to the desired 
type.  For example:
+ <programlisting>
+ SELECT ARRAY[]::int[];
+  int4
+ ------
+  {}
+ (1 row)
+ </programlisting>
+    For more on casting, see <xref linkend="sql-syntax-type-casts">.
+   </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
     key word <literal>ARRAY</literal> followed by a parenthesized (not
*** ./src/backend/nodes/copyfuncs.c.orig        Fri Nov 30 19:29:16 2007
--- ./src/backend/nodes/copyfuncs.c     Fri Nov 30 19:32:11 2007
***************
*** 1704,1709 ****
--- 1704,1719 ----
        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)
  {
***************
*** 3538,3543 ****
--- 3548,3556 ----
                case T_A_ArrayExpr:
                        retval = _copyA_ArrayExpr(from);
                        break;
+               case T_A_ArrayExpr:
+                       retval = _copyA_ArrayExpr(from);
+                       break;
                case T_ResTarget:
                        retval = _copyResTarget(from);
                        break;
*** ./src/backend/nodes/outfuncs.c.orig Fri Nov 30 19:29:16 2007
--- ./src/backend/nodes/outfuncs.c      Fri Nov 30 19:32:11 2007
***************
*** 1978,1983 ****
--- 1978,1991 ----
  }
  
  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)
  {
        WRITE_NODE_TYPE("RESTARGET");
*** ./src/backend/parser/gram.y.orig    Fri Nov 30 19:31:29 2007
--- ./src/backend/parser/gram.y Fri Nov 30 19:32:11 2007
***************
*** 108,113 ****
--- 108,114 ----
  static Node *doNegate(Node *n, int location);
  static void doNegateFloat(Value *v);
  static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args, List 
*args);
+ static Node *makeAArrayExpr(List *elements);
  
  %}
  
***************
*** 8439,8457 ****
                                {       $$ = 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;
                                }
                ;
  
--- 8440,8456 ----
                                {       $$ = list_make1($1);            }
                        | array_expr_list ',' array_expr
                                {       $$ = lappend($1, $3);   }
+                       | /* EMPTY */
+                               {       $$ = NIL;       }
                ;
  
  array_expr: '[' expr_list ']'
                                {
!                                       $$ = makeAArrayExpr($2);
                                }
                        | '[' array_expr_list ']'
                                {
!                                       $$ = makeAArrayExpr($2);
                                }
                ;
  
***************
*** 9820,9825 ****
--- 9819,9833 ----
        return (Node *) x;
  }
  
+ static Node *
+ makeAArrayExpr(List *elements)
+ {
+       A_ArrayExpr *n = makeNode(A_ArrayExpr);
+ 
+       n->elements = elements;
+       return (Node *) n;
+ }
+ 
  /*
   * Must undefine base_yylex before including scan.c, since we want it
   * to create the function base_yylex not filtered_base_yylex.
*** ./src/backend/parser/parse_expr.c.orig      Fri Nov 30 19:31:29 2007
--- ./src/backend/parser/parse_expr.c   Fri Nov 30 19:32:11 2007
***************
*** 52,58 ****
  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 *transformRowExpr(ParseState *pstate, RowExpr *r);
  static Node *transformCoalesceExpr(ParseState *pstate, CoalesceExpr *c);
  static Node *transformMinMaxExpr(ParseState *pstate, MinMaxExpr *m);
--- 52,58 ----
  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, A_ArrayExpr *a, Oid 
typeid, int32 typmod);
  static Node *transformRowExpr(ParseState *pstate, RowExpr *r);
  static Node *transformCoalesceExpr(ParseState *pstate, CoalesceExpr *c);
  static Node *transformMinMaxExpr(ParseState *pstate, MinMaxExpr *m);
***************
*** 145,153 ****
                case T_TypeCast:
                        {
                                TypeCast   *tc = (TypeCast *) expr;
!                               Node       *arg = transformExpr(pstate, 
tc->arg);
  
!                               result = typecast_expression(pstate, arg, 
tc->typename);
                                break;
                        }
  
--- 145,174 ----
                case T_TypeCast:
                        {
                                TypeCast   *tc = (TypeCast *) expr;
!                               int32           typmod;
!                               Oid                     typeid = 
typenameTypeId(pstate, tc->typename, &typmod);
  
!                               /*
!                                * If the subject of the typecast is an array 
expression, we
!                                * check whether the target type is an array 
type.  If so, we
!                                * skip the creation of a typecast expression 
and instead pass
!                                * the type down to transformArrayExpr.  This 
allows us to know
!                                * the type of the array without having to 
examine the types of
!                                * all its elements.  This in turn allows us to 
construct
!                                * viable empty arrays.
!                                */
!                               if (nodeTag(tc->arg) == T_A_ArrayExpr && 
type_is_array(typeid))
!                               {
!                                       result = transformArrayExpr(pstate, 
!                                                                               
                (A_ArrayExpr *) tc->arg, 
!                                                                               
                typeid, 
!                                                                               
                typmod);
!                               }
!                               else
!                               {
!                                       Node       *arg = transformExpr(pstate, 
tc->arg);
!                                       result = typecast_expression(pstate, 
arg, tc->typename);
!                               }
                                break;
                        }
  
***************
*** 205,212 ****
                        result = transformCaseExpr(pstate, (CaseExpr *) expr);
                        break;
  
!               case T_ArrayExpr:
!                       result = transformArrayExpr(pstate, (ArrayExpr *) expr);
                        break;
  
                case T_RowExpr:
--- 226,233 ----
                        result = transformCaseExpr(pstate, (CaseExpr *) expr);
                        break;
  
!               case T_A_ArrayExpr:
!                       result = transformArrayExpr(pstate, (A_ArrayExpr *) 
expr, InvalidOid, -1);
                        break;
  
                case T_RowExpr:
***************
*** 1255,1262 ****
        return result;
  }
  
  static Node *
! transformArrayExpr(ParseState *pstate, ArrayExpr *a)
  {
        ArrayExpr  *newa = makeNode(ArrayExpr);
        List       *newelems = NIL;
--- 1276,1292 ----
        return result;
  }
  
+ /*
+  * transformArrayExpr
+  *
+  * If the array is the subject of an explicit cast to an array type, the 
target
+  * type should be passed in via the typeid argument.
+  *
+  * When typeid is present, rather than examining the elements to determine the
+  * overall array type, the elements will be coerced to the appropriate type.
+  */
  static Node *
! transformArrayExpr(ParseState *pstate, A_ArrayExpr *a, Oid typeid, int32 
typmod)
  {
        ArrayExpr  *newa = makeNode(ArrayExpr);
        List       *newelems = NIL;
***************
*** 1265,1318 ****
        ListCell   *element;
        Oid                     array_type;
        Oid                     element_type;
  
!       /* Transform the element expressions */
        foreach(element, a->elements)
        {
                Node       *e = (Node *) lfirst(element);
                Node       *newe;
  
                newe = transformExpr(pstate, e);
                newelems = lappend(newelems, newe);
-               typeids = lappend_oid(typeids, exprType(newe));
-       }
- 
-       /* Select a common type for the elements */
-       element_type = select_common_type(typeids, "ARRAY");
  
!       /* Coerce arguments to common type if necessary */
!       foreach(element, newelems)
!       {
!               Node       *e = (Node *) lfirst(element);
!               Node       *newe;
  
!               newe = coerce_to_common_type(pstate, e,
!                                                                        
element_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;
--- 1295,1400 ----
        ListCell   *element;
        Oid                     array_type;
        Oid                     element_type;
+       Oid                     coerce_type;
  
!       /* 
!        * Transform the element expressions 
!        *
!        * Assume that the array is one-dimensional until we discover otherwise.
!        */ 
!       newa->multidims = false;
        foreach(element, a->elements)
        {
                Node       *e = (Node *) lfirst(element);
                Node       *newe;
+               Oid                     newe_type;
  
                newe = transformExpr(pstate, e);
                newelems = lappend(newelems, newe);
  
!               newe_type = exprType(newe);
!               if (!newa->multidims && OidIsValid(get_element_type(newe_type)))
!                       /* 
!                        * This element appears to be an array, so presume that 
we have a
!                        * multi-dimensional array.
!                        */
!                       newa->multidims = true;
  
!               typeids = lappend_oid(typeids, newe_type);
        }
  
!       /* 
!        * Select a target type for the elements.
!        *
!        * If we haven't been given an array type via the typeid argument, we 
must
!        * try to deduce a common type based on the types of the individual
!        * elements present.
!        */ 
!       if (OidIsValid(typeid))
        {
!               array_type = typeid;
!               element_type = get_element_type(array_type);
!               coerce_type = (newa->multidims ? array_type : element_type);
! 
!               if (!OidIsValid(element_type))
!                       /* The caller screwed up by passing us a non-array type 
*/
!                       ereport(ERROR,
!                                       (errcode(ERRCODE_INTERNAL_ERROR),
!                                        errmsg("invalid target type for array 
expression"),
!                                        errdetail("%s is not an array type.", 
!                                                          
format_type_be(typeid))));
        }
        else
        {
!               if (newelems == NIL)
!                       /* Can't have an empty array without a target type */
!                       ereport(ERROR,
!                                       
(errcode(ERRCODE_INDETERMINATE_DATATYPE),
!                                        errmsg("no target type for empty 
array"),
!                                        errhint("Empty arrays must be 
explictly cast to the "
!                                                        "desired array type, 
e.g. ARRAY[]::int[]")));
! 
!               coerce_type = select_common_type(typeids, "ARRAY");
!               array_type = (newa->multidims ? 
!                                         coerce_type : 
!                                         get_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))));
        }
  
+       /*
+        * Coerce elements to target type 
+        *
+        * If the array has been explictly type cast, then the elements are in 
turn
+        * explictly coerced.
+        *
+        * If the array's type was merely derived from the common type of its
+        * elements, then the elements are implictly coerced to the common type.
+        */ 
+       foreach(element, newelems)
+       {
+               Node       *e = (Node *) lfirst(element);
+               Node       *newe;
+ 
+               if (OidIsValid(typeid))
+                       newe = coerce_to_target_type(pstate, e, 
+                                                                               
 exprType(e),
+                                                                               
 coerce_type, 
+                                                                               
 typmod,
+                                                                               
 COERCION_EXPLICIT,
+                                                                               
 COERCE_EXPLICIT_CAST);
+               else
+                       newe = coerce_to_common_type(pstate, e,
+                                                                               
 coerce_type,
+                                                                               
 "ARRAY");
+               newcoercedelems = lappend(newcoercedelems, newe);
+       }
+ 
        newa->array_typeid = array_type;
        newa->element_typeid = element_type;
        newa->elements = newcoercedelems;
*** ./src/backend/parser/parse_target.c.orig    Fri Nov 30 19:31:30 2007
--- ./src/backend/parser/parse_target.c Fri Nov 30 19:32:11 2007
***************
*** 1294,1299 ****
--- 1294,1300 ----
                                return 1;
                        }
                        break;
+               case T_A_ArrayExpr:
                case T_ArrayExpr:
                        /* make ARRAY[] act like a function */
                        *name = "array";
*** ./src/include/nodes/nodes.h.orig    Fri Nov 30 19:31:30 2007
--- ./src/include/nodes/nodes.h Fri Nov 30 19:32:11 2007
***************
*** 347,352 ****
--- 347,353 ----
        T_LockingClause,
        T_RowMarkClause,
        T_XmlSerialize,
+       T_A_ArrayExpr,
  
        /*
         * TAGS FOR RANDOM OTHER STUFF
*** ./src/include/nodes/parsenodes.h.orig       Fri Nov 30 19:31:30 2007
--- ./src/include/nodes/parsenodes.h    Fri Nov 30 19:32:11 2007
***************
*** 305,310 ****
--- 305,319 ----
  } A_Indirection;
  
  /*
+  * A_ArrayExpr - an ARRAY[] construction
+  */
+ typedef struct A_ArrayExpr
+ {
+       NodeTag         type;
+       List       *elements;   /* array elements, or NIL if none */
+ } A_ArrayExpr;
+ 
+ /*
   * ResTarget -
   *      result target (used in target list of pre-transformed parse trees)
   *
*** ./src/test/regress/expected/arrays.out.orig Fri Nov 30 19:31:30 2007
--- ./src/test/regress/expected/arrays.out      Fri Nov 30 19:32:11 2007
***************
*** 785,790 ****
--- 785,793 ----
  ERROR:  malformed array literal: "{}}"
  select '{ }}'::text[];
  ERROR:  malformed array literal: "{ }}"
+ select array[];
+ ERROR:  no target type for empty array
+ HINT:  Empty arrays must be explictly cast to the desired array type, e.g. 
ARRAY[]::int[]
  -- none of the above should be accepted
  -- all of the following should be accepted
  select '{}'::text[];
***************
*** 826,831 ****
--- 829,840 ----
   {"@ 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[]);
*** ./src/test/regress/sql/arrays.sql.orig      Fri Nov 30 19:31:30 2007
--- ./src/test/regress/sql/arrays.sql   Fri Nov 30 19:32:11 2007
***************
*** 280,285 ****
--- 280,286 ----
  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,297 ****
--- 293,299 ----
             0 second,
             @ 1 hour @ 42 minutes @ 20 seconds
           }'::interval[];
+ select array[]::text[];
  -- all of the above should be accepted
  
  -- tests for array aggregates
---------------------------(end of broadcast)---------------------------
TIP 7: You can help support the PostgreSQL project by donating at

                http://www.postgresql.org/about/donate

Reply via email to