Allow omitting one or both boundaries in an array slice specifier.

Omitted boundaries represent the upper or lower limit of the corresponding
array subscript.  This allows simpler specification of many common
use-cases.

(Revised version of commit 9246af6799)

YUriy Zhuravlev
This commit is contained in:
Tom Lane 2015-12-22 21:05:16 -05:00
parent 0ba3f3bc65
commit 6efbded6e4
15 changed files with 302 additions and 33 deletions

View File

@ -276,6 +276,29 @@ SELECT schedule[1:2][2] FROM sal_emp WHERE name = 'Bill';
for all dimensions, e.g., <literal>[1:2][1:1]</>, not <literal>[2][1:1]</>.
</para>
<para>
It is possible to omit the <replaceable>lower-bound</replaceable> and/or
<replaceable>upper-bound</replaceable> of a slice specifier; the missing
bound is replaced by the lower or upper limit of the array's subscripts.
For example:
<programlisting>
SELECT schedule[:2][2:] FROM sal_emp WHERE name = 'Bill';
schedule
------------------------
{{lunch},{presentation}}
(1 row)
SELECT schedule[:][1:1] FROM sal_emp WHERE name = 'Bill';
schedule
------------------------
{{meeting},{training}}
(1 row)
</programlisting>
</para>
<para>
An array subscript expression will return null if either the array itself or
any of the subscript expressions are null. Also, null is returned if a
@ -391,6 +414,10 @@ UPDATE sal_emp SET pay_by_quarter[1:2] = '{27000,27000}'
WHERE name = 'Carol';
</programlisting>
The slice syntaxes with omitted <replaceable>lower-bound</replaceable> and/or
<replaceable>upper-bound</replaceable> can be used too, but only when
updating an array value that is not NULL or zero-dimensional (otherwise,
there is no existing subscript limit to substitute).
</para>
<para>

View File

@ -271,6 +271,8 @@ ExecEvalArrayRef(ArrayRefExprState *astate,
j = 0;
IntArray upper,
lower;
bool upperProvided[MAXDIM],
lowerProvided[MAXDIM];
int *lIndex;
array_source = ExecEvalExpr(astate->refexpr,
@ -300,6 +302,15 @@ ExecEvalArrayRef(ArrayRefExprState *astate,
errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
i + 1, MAXDIM)));
if (eltstate == NULL)
{
/* Slice bound is omitted, so use array's upper bound */
Assert(astate->reflowerindexpr != NIL);
upperProvided[i++] = false;
continue;
}
upperProvided[i] = true;
upper.indx[i++] = DatumGetInt32(ExecEvalExpr(eltstate,
econtext,
&eisnull,
@ -328,6 +339,14 @@ ExecEvalArrayRef(ArrayRefExprState *astate,
errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
j + 1, MAXDIM)));
if (eltstate == NULL)
{
/* Slice bound is omitted, so use array's lower bound */
lowerProvided[j++] = false;
continue;
}
lowerProvided[j] = true;
lower.indx[j++] = DatumGetInt32(ExecEvalExpr(eltstate,
econtext,
&eisnull,
@ -398,6 +417,7 @@ ExecEvalArrayRef(ArrayRefExprState *astate,
econtext->caseValue_datum =
array_get_slice(array_source, i,
upper.indx, lower.indx,
upperProvided, lowerProvided,
astate->refattrlength,
astate->refelemlength,
astate->refelembyval,
@ -456,6 +476,7 @@ ExecEvalArrayRef(ArrayRefExprState *astate,
else
return array_set_slice(array_source, i,
upper.indx, lower.indx,
upperProvided, lowerProvided,
sourceData,
eisnull,
astate->refattrlength,
@ -475,6 +496,7 @@ ExecEvalArrayRef(ArrayRefExprState *astate,
else
return array_get_slice(array_source, i,
upper.indx, lower.indx,
upperProvided, lowerProvided,
astate->refattrlength,
astate->refelemlength,
astate->refelembyval,

View File

@ -2401,6 +2401,7 @@ _copyAIndices(const A_Indices *from)
{
A_Indices *newnode = makeNode(A_Indices);
COPY_SCALAR_FIELD(is_slice);
COPY_NODE_FIELD(lidx);
COPY_NODE_FIELD(uidx);

View File

@ -2151,6 +2151,7 @@ _equalAStar(const A_Star *a, const A_Star *b)
static bool
_equalAIndices(const A_Indices *a, const A_Indices *b)
{
COMPARE_SCALAR_FIELD(is_slice);
COMPARE_NODE_FIELD(lidx);
COMPARE_NODE_FIELD(uidx);

View File

@ -2763,6 +2763,7 @@ _outA_Indices(StringInfo str, const A_Indices *node)
{
WRITE_NODE_TYPE("A_INDICES");
WRITE_BOOL_FIELD(is_slice);
WRITE_NODE_FIELD(lidx);
WRITE_NODE_FIELD(uidx);
}

View File

@ -434,7 +434,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> columnDef columnOptions
%type <defelt> def_elem reloption_elem old_aggr_elem operator_def_elem
%type <node> def_arg columnElem where_clause where_or_current_clause
a_expr b_expr c_expr AexprConst indirection_el
a_expr b_expr c_expr AexprConst indirection_el opt_slice_bound
columnref in_expr having_clause func_table array_expr
ExclusionWhereClause
%type <list> rowsfrom_item rowsfrom_list opt_col_def_list
@ -13191,19 +13191,26 @@ indirection_el:
| '[' a_expr ']'
{
A_Indices *ai = makeNode(A_Indices);
ai->is_slice = false;
ai->lidx = NULL;
ai->uidx = $2;
$$ = (Node *) ai;
}
| '[' a_expr ':' a_expr ']'
| '[' opt_slice_bound ':' opt_slice_bound ']'
{
A_Indices *ai = makeNode(A_Indices);
ai->is_slice = true;
ai->lidx = $2;
ai->uidx = $4;
$$ = (Node *) ai;
}
;
opt_slice_bound:
a_expr { $$ = $1; }
| /*EMPTY*/ { $$ = NULL; }
;
indirection:
indirection_el { $$ = list_make1($1); }
| indirection indirection_el { $$ = lappend($1, $2); }

View File

@ -311,18 +311,18 @@ transformArraySubscripts(ParseState *pstate,
elementType = transformArrayType(&arrayType, &arrayTypMod);
/*
* 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. We have to prescan the indirection list to see if
* there are any double subscripts.
* A list containing only simple subscripts refers to a single array
* element. If any of the items are slice specifiers (lower:upper), then
* the subscript expression means an array slice operation. In this case,
* we convert any non-slice items to slices by treating the single
* subscript as the upper bound and supplying an assumed lower bound of 1.
* We have to prescan the list to see if there are any slice items.
*/
foreach(idx, indirection)
{
A_Indices *ai = (A_Indices *) lfirst(idx);
if (ai->lidx != NULL)
if (ai->is_slice)
{
isSlice = true;
break;
@ -356,7 +356,7 @@ transformArraySubscripts(ParseState *pstate,
errmsg("array subscript must have type integer"),
parser_errposition(pstate, exprLocation(ai->lidx))));
}
else
else if (!ai->is_slice)
{
/* Make a constant 1 */
subexpr = (Node *) makeConst(INT4OID,
@ -367,21 +367,38 @@ transformArraySubscripts(ParseState *pstate,
false,
true); /* pass by value */
}
else
{
/* Slice with omitted lower bound, put NULL into the list */
subexpr = NULL;
}
lowerIndexpr = lappend(lowerIndexpr, subexpr);
}
subexpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
/* If it's not int4 already, try to coerce */
subexpr = coerce_to_target_type(pstate,
subexpr, exprType(subexpr),
INT4OID, -1,
COERCION_ASSIGNMENT,
COERCE_IMPLICIT_CAST,
-1);
if (subexpr == NULL)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("array subscript must have type integer"),
parser_errposition(pstate, exprLocation(ai->uidx))));
else
Assert(ai->lidx == NULL && !ai->is_slice);
if (ai->uidx)
{
subexpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
/* If it's not int4 already, try to coerce */
subexpr = coerce_to_target_type(pstate,
subexpr, exprType(subexpr),
INT4OID, -1,
COERCION_ASSIGNMENT,
COERCE_IMPLICIT_CAST,
-1);
if (subexpr == NULL)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("array subscript must have type integer"),
parser_errposition(pstate, exprLocation(ai->uidx))));
}
else
{
/* Slice with omitted upper bound, put NULL into the list */
Assert(isSlice && ai->is_slice);
subexpr = NULL;
}
upperIndexpr = lappend(upperIndexpr, subexpr);
}

View File

@ -650,7 +650,7 @@ transformAssignmentIndirection(ParseState *pstate,
if (IsA(n, A_Indices))
{
subscripts = lappend(subscripts, n);
if (((A_Indices *) n)->lidx != NULL)
if (((A_Indices *) n)->is_slice)
isSlice = true;
}
else if (IsA(n, A_Star))

View File

@ -1995,6 +1995,8 @@ array_get_element_expanded(Datum arraydatum,
* nSubscripts: number of subscripts supplied (must be same for upper/lower)
* upperIndx[]: the upper subscript values
* lowerIndx[]: the lower subscript values
* upperProvided[]: true for provided upper subscript values
* lowerProvided[]: true for provided lower subscript values
* arraytyplen: pg_type.typlen for the array type
* elmlen: pg_type.typlen for the array's element type
* elmbyval: pg_type.typbyval for the array's element type
@ -2003,6 +2005,9 @@ array_get_element_expanded(Datum arraydatum,
* Outputs:
* The return value is the new array Datum (it's never NULL)
*
* Omitted upper and lower subscript values are replaced by the corresponding
* array bound.
*
* NOTE: we assume it is OK to scribble on the provided subscript arrays
* lowerIndx[] and upperIndx[]. These are generally just temporaries.
*/
@ -2011,6 +2016,8 @@ array_get_slice(Datum arraydatum,
int nSubscripts,
int *upperIndx,
int *lowerIndx,
bool *upperProvided,
bool *lowerProvided,
int arraytyplen,
int elmlen,
bool elmbyval,
@ -2081,9 +2088,9 @@ array_get_slice(Datum arraydatum,
for (i = 0; i < nSubscripts; i++)
{
if (lowerIndx[i] < lb[i])
if (!lowerProvided[i] || lowerIndx[i] < lb[i])
lowerIndx[i] = lb[i];
if (upperIndx[i] >= (dim[i] + lb[i]))
if (!upperProvided[i] || upperIndx[i] >= (dim[i] + lb[i]))
upperIndx[i] = dim[i] + lb[i] - 1;
if (lowerIndx[i] > upperIndx[i])
return PointerGetDatum(construct_empty_array(elemtype));
@ -2708,6 +2715,8 @@ array_set_element_expanded(Datum arraydatum,
* nSubscripts: number of subscripts supplied (must be same for upper/lower)
* upperIndx[]: the upper subscript values
* lowerIndx[]: the lower subscript values
* upperProvided[]: true for provided upper subscript values
* lowerProvided[]: true for provided lower subscript values
* srcArrayDatum: the source for the inserted values
* isNull: indicates whether srcArrayDatum is NULL
* arraytyplen: pg_type.typlen for the array type
@ -2719,6 +2728,9 @@ array_set_element_expanded(Datum arraydatum,
* A new array is returned, just like the old except for the
* modified range. The original array object is not changed.
*
* Omitted upper and lower subscript values are replaced by the corresponding
* array bound.
*
* For one-dimensional arrays only, we allow the array to be extended
* by assigning to positions outside the existing subscript range; any
* positions between the existing elements and the new ones are set to NULLs.
@ -2735,6 +2747,8 @@ array_set_slice(Datum arraydatum,
int nSubscripts,
int *upperIndx,
int *lowerIndx,
bool *upperProvided,
bool *lowerProvided,
Datum srcArrayDatum,
bool isNull,
int arraytyplen,
@ -2806,6 +2820,13 @@ array_set_slice(Datum arraydatum,
for (i = 0; i < nSubscripts; i++)
{
if (!upperProvided[i] || !lowerProvided[i])
ereport(ERROR,
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
errmsg("array slice subscript must provide both boundaries"),
errdetail("When assigning to a slice of an empty array value,"
" slice boundaries must be fully specified.")));
dim[i] = 1 + upperIndx[i] - lowerIndx[i];
lb[i] = lowerIndx[i];
}
@ -2839,6 +2860,10 @@ array_set_slice(Datum arraydatum,
if (ndim == 1)
{
Assert(nSubscripts == 1);
if (!lowerProvided[0])
lowerIndx[0] = lb[0];
if (!upperProvided[0])
upperIndx[0] = dim[0] + lb[0] - 1;
if (lowerIndx[0] > upperIndx[0])
ereport(ERROR,
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
@ -2867,6 +2892,10 @@ array_set_slice(Datum arraydatum,
*/
for (i = 0; i < nSubscripts; i++)
{
if (!lowerProvided[i])
lowerIndx[i] = lb[i];
if (!upperProvided[i])
upperIndx[i] = dim[i] + lb[i] - 1;
if (lowerIndx[i] > upperIndx[i])
ereport(ERROR,
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),

View File

@ -9372,10 +9372,12 @@ printSubscripts(ArrayRef *aref, deparse_context *context)
appendStringInfoChar(buf, '[');
if (lowlist_item)
{
/* If subexpression is NULL, get_rule_expr prints nothing */
get_rule_expr((Node *) lfirst(lowlist_item), context, false);
appendStringInfoChar(buf, ':');
lowlist_item = lnext(lowlist_item);
}
/* If subexpression is NULL, get_rule_expr prints nothing */
get_rule_expr((Node *) lfirst(uplist_item), context, false);
appendStringInfoChar(buf, ']');
}

View File

@ -157,9 +157,10 @@ typedef struct Query
List *constraintDeps; /* a list of pg_constraint OIDs that the query
* depends on to be semantically valid */
List *withCheckOptions; /* a list of WithCheckOption's, which are
* only added during rewrite and therefore
* are not written out as part of Query. */
List *withCheckOptions; /* a list of WithCheckOption's, which
* are only added during rewrite and
* therefore are not written out as
* part of Query. */
} Query;
@ -351,13 +352,17 @@ typedef struct A_Star
} A_Star;
/*
* A_Indices - array subscript or slice bounds ([lidx:uidx] or [uidx])
* A_Indices - array subscript or slice bounds ([idx] or [lidx:uidx])
*
* In slice case, either or both of lidx and uidx can be NULL (omitted).
* In non-slice case, uidx holds the single subscript and lidx is always NULL.
*/
typedef struct A_Indices
{
NodeTag type;
Node *lidx; /* NULL if it's a single subscript */
Node *uidx;
bool is_slice; /* true if slice (i.e., colon present) */
Node *lidx; /* slice lower bound, if any */
Node *uidx; /* subscript, or slice upper bound if any */
} A_Indices;
/*

View File

@ -341,6 +341,9 @@ typedef struct WindowFunc
* reflowerindexpr must be the same length as refupperindexpr when it
* is not NIL.
*
* In the slice case, individual expressions in the subscript lists can be
* NULL, meaning "substitute the array's current lower or upper bound".
*
* Note: the result datatype is the element type when fetching a single
* element; but it is the array type when doing subarray fetch or either
* type of store.
@ -360,7 +363,7 @@ typedef struct ArrayRef
List *refupperindexpr;/* expressions that evaluate to upper array
* indexes */
List *reflowerindexpr;/* expressions that evaluate to lower array
* indexes */
* indexes, or NIL for single array element */
Expr *refexpr; /* the expression that evaluates to an array
* value */
Expr *refassgnexpr; /* expression for the source value, or NULL if

View File

@ -377,9 +377,11 @@ extern Datum array_set_element(Datum arraydatum, int nSubscripts, int *indx,
int arraytyplen, int elmlen, bool elmbyval, char elmalign);
extern Datum array_get_slice(Datum arraydatum, int nSubscripts,
int *upperIndx, int *lowerIndx,
bool *upperProvided, bool *lowerProvided,
int arraytyplen, int elmlen, bool elmbyval, char elmalign);
extern Datum array_set_slice(Datum arraydatum, int nSubscripts,
int *upperIndx, int *lowerIndx,
bool *upperProvided, bool *lowerProvided,
Datum srcArrayDatum, bool isNull,
int arraytyplen, int elmlen, bool elmbyval, char elmalign);

View File

@ -125,6 +125,16 @@ SELECT a[1:3],
{16,25,23} | {} | {foobar,new_word} | {{elt2}}
(3 rows)
SELECT b[1:1][2][2],
d[1:1][2]
FROM arrtest;
b | d
-----------------------+---------------
{{{113,142},{1,147}}} | {}
{} | {}
{} | {{elt1,elt2}}
(3 rows)
INSERT INTO arrtest(a) VALUES('{1,null,3}');
SELECT a FROM arrtest;
a
@ -152,6 +162,107 @@ SELECT a,b,c FROM arrtest;
[4:4]={NULL} | {3,4} | {foo,new_word}
(3 rows)
-- test mixed slice/scalar subscripting
select '{{1,2,3},{4,5,6},{7,8,9}}'::int[];
int4
---------------------------
{{1,2,3},{4,5,6},{7,8,9}}
(1 row)
select ('{{1,2,3},{4,5,6},{7,8,9}}'::int[])[1:2][2];
int4
---------------
{{1,2},{4,5}}
(1 row)
select '[0:2][0:2]={{1,2,3},{4,5,6},{7,8,9}}'::int[];
int4
--------------------------------------
[0:2][0:2]={{1,2,3},{4,5,6},{7,8,9}}
(1 row)
select ('[0:2][0:2]={{1,2,3},{4,5,6},{7,8,9}}'::int[])[1:2][2];
int4
---------------
{{5,6},{8,9}}
(1 row)
-- test slices with empty lower and/or upper index
CREATE TEMP TABLE arrtest_s (
a int2[],
b int2[][]
);
INSERT INTO arrtest_s VALUES ('{1,2,3,4,5}', '{{1,2,3}, {4,5,6}, {7,8,9}}');
INSERT INTO arrtest_s VALUES ('[0:4]={1,2,3,4,5}', '[0:2][0:2]={{1,2,3}, {4,5,6}, {7,8,9}}');
SELECT * FROM arrtest_s;
a | b
-------------------+--------------------------------------
{1,2,3,4,5} | {{1,2,3},{4,5,6},{7,8,9}}
[0:4]={1,2,3,4,5} | [0:2][0:2]={{1,2,3},{4,5,6},{7,8,9}}
(2 rows)
SELECT a[:3], b[:2][:2] FROM arrtest_s;
a | b
-----------+---------------------------
{1,2,3} | {{1,2},{4,5}}
{1,2,3,4} | {{1,2,3},{4,5,6},{7,8,9}}
(2 rows)
SELECT a[2:], b[2:][2:] FROM arrtest_s;
a | b
-----------+---------------
{2,3,4,5} | {{5,6},{8,9}}
{3,4,5} | {{9}}
(2 rows)
SELECT a[:], b[:] FROM arrtest_s;
a | b
-------------+---------------------------
{1,2,3,4,5} | {{1,2,3},{4,5,6},{7,8,9}}
{1,2,3,4,5} | {{1,2,3},{4,5,6},{7,8,9}}
(2 rows)
-- updates
UPDATE arrtest_s SET a[:3] = '{11, 12, 13}', b[:2][:2] = '{{11,12}, {14,15}}'
WHERE array_lower(a,1) = 1;
SELECT * FROM arrtest_s;
a | b
-------------------+--------------------------------------
[0:4]={1,2,3,4,5} | [0:2][0:2]={{1,2,3},{4,5,6},{7,8,9}}
{11,12,13,4,5} | {{11,12,3},{14,15,6},{7,8,9}}
(2 rows)
UPDATE arrtest_s SET a[3:] = '{23, 24, 25}', b[2:][2:] = '{{25,26}, {28,29}}';
SELECT * FROM arrtest_s;
a | b
---------------------+---------------------------------------
[0:4]={1,2,3,23,24} | [0:2][0:2]={{1,2,3},{4,5,6},{7,8,25}}
{11,12,23,24,25} | {{11,12,3},{14,25,26},{7,28,29}}
(2 rows)
UPDATE arrtest_s SET a[:] = '{11, 12, 13, 14, 15}';
SELECT * FROM arrtest_s;
a | b
------------------------+---------------------------------------
[0:4]={11,12,13,14,15} | [0:2][0:2]={{1,2,3},{4,5,6},{7,8,25}}
{11,12,13,14,15} | {{11,12,3},{14,25,26},{7,28,29}}
(2 rows)
UPDATE arrtest_s SET a[:] = '{23, 24, 25}'; -- fail, too small
ERROR: source array too small
INSERT INTO arrtest_s VALUES(NULL, NULL);
UPDATE arrtest_s SET a[:] = '{11, 12, 13, 14, 15}'; -- fail, no good with null
ERROR: array slice subscript must provide both boundaries
DETAIL: When assigning to a slice of an empty array value, slice boundaries must be fully specified.
-- check with fixed-length-array type, such as point
SELECT f1[0:1] FROM POINT_TBL;
ERROR: slices of fixed-length arrays not implemented
SELECT f1[0:] FROM POINT_TBL;
ERROR: slices of fixed-length arrays not implemented
SELECT f1[:1] FROM POINT_TBL;
ERROR: slices of fixed-length arrays not implemented
SELECT f1[:] FROM POINT_TBL;
ERROR: slices of fixed-length arrays not implemented
--
-- test array extension
--

View File

@ -86,6 +86,10 @@ SELECT a[1:3],
d[1:1][2:2]
FROM arrtest;
SELECT b[1:1][2][2],
d[1:1][2]
FROM arrtest;
INSERT INTO arrtest(a) VALUES('{1,null,3}');
SELECT a FROM arrtest;
UPDATE arrtest SET a[4] = NULL WHERE a[2] IS NULL;
@ -93,6 +97,43 @@ SELECT a FROM arrtest WHERE a[2] IS NULL;
DELETE FROM arrtest WHERE a[2] IS NULL AND b IS NULL;
SELECT a,b,c FROM arrtest;
-- test mixed slice/scalar subscripting
select '{{1,2,3},{4,5,6},{7,8,9}}'::int[];
select ('{{1,2,3},{4,5,6},{7,8,9}}'::int[])[1:2][2];
select '[0:2][0:2]={{1,2,3},{4,5,6},{7,8,9}}'::int[];
select ('[0:2][0:2]={{1,2,3},{4,5,6},{7,8,9}}'::int[])[1:2][2];
-- test slices with empty lower and/or upper index
CREATE TEMP TABLE arrtest_s (
a int2[],
b int2[][]
);
INSERT INTO arrtest_s VALUES ('{1,2,3,4,5}', '{{1,2,3}, {4,5,6}, {7,8,9}}');
INSERT INTO arrtest_s VALUES ('[0:4]={1,2,3,4,5}', '[0:2][0:2]={{1,2,3}, {4,5,6}, {7,8,9}}');
SELECT * FROM arrtest_s;
SELECT a[:3], b[:2][:2] FROM arrtest_s;
SELECT a[2:], b[2:][2:] FROM arrtest_s;
SELECT a[:], b[:] FROM arrtest_s;
-- updates
UPDATE arrtest_s SET a[:3] = '{11, 12, 13}', b[:2][:2] = '{{11,12}, {14,15}}'
WHERE array_lower(a,1) = 1;
SELECT * FROM arrtest_s;
UPDATE arrtest_s SET a[3:] = '{23, 24, 25}', b[2:][2:] = '{{25,26}, {28,29}}';
SELECT * FROM arrtest_s;
UPDATE arrtest_s SET a[:] = '{11, 12, 13, 14, 15}';
SELECT * FROM arrtest_s;
UPDATE arrtest_s SET a[:] = '{23, 24, 25}'; -- fail, too small
INSERT INTO arrtest_s VALUES(NULL, NULL);
UPDATE arrtest_s SET a[:] = '{11, 12, 13, 14, 15}'; -- fail, no good with null
-- check with fixed-length-array type, such as point
SELECT f1[0:1] FROM POINT_TBL;
SELECT f1[0:] FROM POINT_TBL;
SELECT f1[:1] FROM POINT_TBL;
SELECT f1[:] FROM POINT_TBL;
--
-- test array extension
--