Improve error handling of column references in expression transformation

Column references are not allowed in default expressions and partition
bound expressions, and are restricted as such once the transformation of
their expressions is done.  However, trying to use more complex column
references can lead to confusing error messages.  For example, trying to
use a two-field column reference name for default expressions and
partition bounds leads to "missing FROM-clause entry for table", which
makes no sense in their respective context.

In order to make the errors generated more useful, this commit adds more
verbose messages when transforming column references depending on the
context.  This has a little consequence though: for example an
expression using an aggregate with a column reference as argument would
cause an error to be generated for the column reference, while the
aggregate was the problem reported before this commit because column
references get transformed first.

The confusion exists for default expressions for a long time, and the
problem is new as of v12 for partition bounds.  Still per the lack of
complaints on the matter no backpatch is done.

The patch has been written by Amit Langote and me, and Tom Lane has
provided the improvement of the documentation for default expressions on
the CREATE TABLE page.

Author: Amit Langote, Michael Paquier
Reviewed-by: Tom Lane
Discussion: https://postgr.es/m/20190326020853.GM2558@paquier.xyz
This commit is contained in:
Michael Paquier 2019-03-27 21:04:25 +09:00
parent d2fd7f74ee
commit ecfed4a122
6 changed files with 147 additions and 34 deletions

View File

@ -783,10 +783,10 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
<para>
The <literal>DEFAULT</literal> clause assigns a default data value for
the column whose column definition it appears within. The value
is any variable-free expression (subqueries and cross-references
to other columns in the current table are not allowed). The
data type of the default expression must match the data type of the
column.
is any variable-free expression (in particular, cross-references
to other columns in the current table are not allowed). Subqueries
are not allowed either. The data type of the default expression must
match the data type of the column.
</para>
<para>

View File

@ -2939,19 +2939,11 @@ cookDefault(ParseState *pstate,
expr = transformExpr(pstate, raw_default, EXPR_KIND_COLUMN_DEFAULT);
/*
* Make sure default expr does not refer to any vars (we need this check
* since the pstate includes the target table).
*/
if (contain_var_clause(expr))
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
errmsg("cannot use column references in default expression")));
/*
* transformExpr() should have already rejected subqueries, aggregates,
* window functions, and SRFs, based on the EXPR_KIND_ for a default
* expression.
* transformExpr() should have already rejected column references,
* subqueries, aggregates, window functions, and SRFs, based on the
* EXPR_KIND_ for a default expression.
*/
Assert(!contain_var_clause(expr));
/*
* Coerce the expression to the correct type and typmod, if given. This

View File

@ -520,6 +520,79 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
CRERR_WRONG_DB,
CRERR_TOO_MANY
} crerr = CRERR_NO_COLUMN;
const char *err;
/*
* Check to see if the column reference is in an invalid place within the
* query. We allow column references in most places, except in default
* expressions and partition bound expressions.
*/
err = NULL;
switch (pstate->p_expr_kind)
{
case EXPR_KIND_NONE:
Assert(false); /* can't happen */
break;
case EXPR_KIND_OTHER:
case EXPR_KIND_JOIN_ON:
case EXPR_KIND_JOIN_USING:
case EXPR_KIND_FROM_SUBSELECT:
case EXPR_KIND_FROM_FUNCTION:
case EXPR_KIND_WHERE:
case EXPR_KIND_POLICY:
case EXPR_KIND_HAVING:
case EXPR_KIND_FILTER:
case EXPR_KIND_WINDOW_PARTITION:
case EXPR_KIND_WINDOW_ORDER:
case EXPR_KIND_WINDOW_FRAME_RANGE:
case EXPR_KIND_WINDOW_FRAME_ROWS:
case EXPR_KIND_WINDOW_FRAME_GROUPS:
case EXPR_KIND_SELECT_TARGET:
case EXPR_KIND_INSERT_TARGET:
case EXPR_KIND_UPDATE_SOURCE:
case EXPR_KIND_UPDATE_TARGET:
case EXPR_KIND_GROUP_BY:
case EXPR_KIND_ORDER_BY:
case EXPR_KIND_DISTINCT_ON:
case EXPR_KIND_LIMIT:
case EXPR_KIND_OFFSET:
case EXPR_KIND_RETURNING:
case EXPR_KIND_VALUES:
case EXPR_KIND_VALUES_SINGLE:
case EXPR_KIND_CHECK_CONSTRAINT:
case EXPR_KIND_DOMAIN_CHECK:
case EXPR_KIND_FUNCTION_DEFAULT:
case EXPR_KIND_INDEX_EXPRESSION:
case EXPR_KIND_INDEX_PREDICATE:
case EXPR_KIND_ALTER_COL_TRANSFORM:
case EXPR_KIND_EXECUTE_PARAMETER:
case EXPR_KIND_TRIGGER_WHEN:
case EXPR_KIND_PARTITION_EXPRESSION:
case EXPR_KIND_CALL_ARGUMENT:
case EXPR_KIND_COPY_WHERE:
/* okay */
break;
case EXPR_KIND_COLUMN_DEFAULT:
err = _("cannot use column reference in DEFAULT expression");
break;
case EXPR_KIND_PARTITION_BOUND:
err = _("cannot use column reference in partition bound expression");
break;
/*
* There is intentionally no default: case here, so that the
* compiler will warn if we add a new ParseExprKind without
* extending this switch. If we do see an unrecognized value at
* runtime, the behavior will be the same as for EXPR_KIND_OTHER,
* which is sane anyway.
*/
}
if (err)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg_internal("%s", err),
parser_errposition(pstate, cref->location)));
/*
* Give the PreParseColumnRefHook, if any, first shot. If it returns

View File

@ -3926,12 +3926,12 @@ transformPartitionBoundValue(ParseState *pstate, Node *val,
if (!IsA(value, Const))
value = (Node *) expression_planner((Expr *) value);
/* Make sure the expression does not refer to any vars. */
if (contain_var_clause(value))
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
errmsg("cannot use column references in partition bound expression"),
parser_errposition(pstate, exprLocation(value))));
/*
* transformExpr() should have already rejected column references,
* subqueries, aggregates, window functions, and SRFs, based on the
* EXPR_KIND_ for a default expression.
*/
Assert(!contain_var_clause(value));
/*
* Evaluate the expression, assigning the partition key's collation to the

View File

@ -297,6 +297,40 @@ ERROR: tables declared WITH OIDS are not supported
-- but explicitly not adding oids is still supported
CREATE TEMP TABLE withoutoid() WITHOUT OIDS; DROP TABLE withoutoid;
CREATE TEMP TABLE withoutoid() WITH (oids = false); DROP TABLE withoutoid;
-- check restriction with default expressions
-- invalid use of column reference in default expressions
CREATE TABLE default_expr_column (id int DEFAULT (id));
ERROR: cannot use column reference in DEFAULT expression
LINE 1: CREATE TABLE default_expr_column (id int DEFAULT (id));
^
CREATE TABLE default_expr_column (id int DEFAULT (bar.id));
ERROR: cannot use column reference in DEFAULT expression
LINE 1: CREATE TABLE default_expr_column (id int DEFAULT (bar.id));
^
CREATE TABLE default_expr_agg_column (id int DEFAULT (avg(id)));
ERROR: cannot use column reference in DEFAULT expression
LINE 1: ...TE TABLE default_expr_agg_column (id int DEFAULT (avg(id)));
^
-- invalid column definition
CREATE TABLE default_expr_non_column (a int DEFAULT (avg(non_existent)));
ERROR: cannot use column reference in DEFAULT expression
LINE 1: ...TABLE default_expr_non_column (a int DEFAULT (avg(non_existe...
^
-- invalid use of aggregate
CREATE TABLE default_expr_agg (a int DEFAULT (avg(1)));
ERROR: aggregate functions are not allowed in DEFAULT expressions
LINE 1: CREATE TABLE default_expr_agg (a int DEFAULT (avg(1)));
^
-- invalid use of subquery
CREATE TABLE default_expr_agg (a int DEFAULT (select 1));
ERROR: cannot use subquery in DEFAULT expression
LINE 1: CREATE TABLE default_expr_agg (a int DEFAULT (select 1));
^
-- invalid use of set-returning function
CREATE TABLE default_expr_agg (a int DEFAULT (generate_series(1,3)));
ERROR: set-returning functions are not allowed in DEFAULT expressions
LINE 1: CREATE TABLE default_expr_agg (a int DEFAULT (generate_serie...
^
--
-- Partitioned tables
--
@ -491,23 +525,23 @@ Partitions: part_null FOR VALUES IN (NULL),
-- forbidden expressions for partition bound with list partitioned table
CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN (somename);
ERROR: column "somename" does not exist
ERROR: cannot use column reference in partition bound expression
LINE 1: ...expr_fail PARTITION OF list_parted FOR VALUES IN (somename);
^
CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN (somename.somename);
ERROR: missing FROM-clause entry for table "somename"
ERROR: cannot use column reference in partition bound expression
LINE 1: ...expr_fail PARTITION OF list_parted FOR VALUES IN (somename.s...
^
CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN (a);
ERROR: cannot use column references in partition bound expression
ERROR: cannot use column reference in partition bound expression
LINE 1: ..._bogus_expr_fail PARTITION OF list_parted FOR VALUES IN (a);
^
CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN (sum(a));
ERROR: aggregate functions are not allowed in partition bound
ERROR: cannot use column reference in partition bound expression
LINE 1: ...s_expr_fail PARTITION OF list_parted FOR VALUES IN (sum(a));
^
^
CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN (sum(somename));
ERROR: column "somename" does not exist
ERROR: cannot use column reference in partition bound expression
LINE 1: ..._fail PARTITION OF list_parted FOR VALUES IN (sum(somename))...
^
CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN (sum(1));
@ -573,27 +607,27 @@ CREATE TABLE range_parted (
-- forbidden expressions for partition bounds with range partitioned table
CREATE TABLE part_bogus_expr_fail PARTITION OF range_parted
FOR VALUES FROM (somename) TO ('2019-01-01');
ERROR: column "somename" does not exist
ERROR: cannot use column reference in partition bound expression
LINE 2: FOR VALUES FROM (somename) TO ('2019-01-01');
^
CREATE TABLE part_bogus_expr_fail PARTITION OF range_parted
FOR VALUES FROM (somename.somename) TO ('2019-01-01');
ERROR: missing FROM-clause entry for table "somename"
ERROR: cannot use column reference in partition bound expression
LINE 2: FOR VALUES FROM (somename.somename) TO ('2019-01-01');
^
CREATE TABLE part_bogus_expr_fail PARTITION OF range_parted
FOR VALUES FROM (a) TO ('2019-01-01');
ERROR: cannot use column references in partition bound expression
ERROR: cannot use column reference in partition bound expression
LINE 2: FOR VALUES FROM (a) TO ('2019-01-01');
^
CREATE TABLE part_bogus_expr_fail PARTITION OF range_parted
FOR VALUES FROM (max(a)) TO ('2019-01-01');
ERROR: aggregate functions are not allowed in partition bound
ERROR: cannot use column reference in partition bound expression
LINE 2: FOR VALUES FROM (max(a)) TO ('2019-01-01');
^
^
CREATE TABLE part_bogus_expr_fail PARTITION OF range_parted
FOR VALUES FROM (max(somename)) TO ('2019-01-01');
ERROR: column "somename" does not exist
ERROR: cannot use column reference in partition bound expression
LINE 2: FOR VALUES FROM (max(somename)) TO ('2019-01-01');
^
CREATE TABLE part_bogus_expr_fail PARTITION OF range_parted

View File

@ -304,6 +304,20 @@ CREATE TABLE withoid() WITH (oids = true);
CREATE TEMP TABLE withoutoid() WITHOUT OIDS; DROP TABLE withoutoid;
CREATE TEMP TABLE withoutoid() WITH (oids = false); DROP TABLE withoutoid;
-- check restriction with default expressions
-- invalid use of column reference in default expressions
CREATE TABLE default_expr_column (id int DEFAULT (id));
CREATE TABLE default_expr_column (id int DEFAULT (bar.id));
CREATE TABLE default_expr_agg_column (id int DEFAULT (avg(id)));
-- invalid column definition
CREATE TABLE default_expr_non_column (a int DEFAULT (avg(non_existent)));
-- invalid use of aggregate
CREATE TABLE default_expr_agg (a int DEFAULT (avg(1)));
-- invalid use of subquery
CREATE TABLE default_expr_agg (a int DEFAULT (select 1));
-- invalid use of set-returning function
CREATE TABLE default_expr_agg (a int DEFAULT (generate_series(1,3)));
--
-- Partitioned tables
--