Thanks for the review and sorry it took me a while to reply.

On 2019/01/02 22:58, Peter Eisentraut wrote:
> On 26/11/2018 05:58, Amit Langote wrote:
>> On 2018/11/09 14:38, Amit Langote wrote:
>>> Rebased due to change in addRangeTableEntryForRelation's API.
>>
>> Rebased again due to changes in psql's describe output for partitioned 
>> tables.
> 
> Review:
> 
> Is "partition bound" the right term?  For list partitioning, it's not
> really a bound.  Maybe it's OK.

Hmm, maybe "partition bound value"?  Just want to stress that the
expression specifies "bounding" value of a partition.

> Keep the ordering of EXPR_KIND_PARTITION_BOUND in the various switch
> statements consistent.

OK, fixed.

> I don't see any treatment of non-immutable expressions.  There is a test
> case with a non-immutable cast that was removed, but that's all.  There
> was considerable discussion earlier in the thread on whether
> non-immutable expressions should be allowed.  I'm not sure what the
> resolution was, but in any case there should be documentation and tests
> of the outcome.

The partition bound expression is evaluated only once, that is, when the
partition creation command is executed, and what gets stored in the
catalog is a Const expression that contains the value that was computed,
not the original non-immutable expression.  So, we don't need to do
anything special in terms of code to handle users possibly specifying a
non-immutable expression as partition bound.  Tom said something in that
thread which seems to support such a position:

https://www.postgresql.org/message-id/22534.1523374457%40sss.pgh.pa.us

Part of the test case that was removed is the error that used to be emitted:

 CREATE TABLE moneyp (
     a money
 ) PARTITION BY LIST (a);
 CREATE TABLE moneyp_10 PARTITION OF moneyp FOR VALUES IN (10);
-ERROR:  specified value cannot be cast to type money for column "a"
-LINE 1: ...EATE TABLE moneyp_10 PARTITION OF moneyp FOR VALUES IN (10);
-                                                                   ^
-DETAIL:  The cast requires a non-immutable conversion.
-HINT:  Try putting the literal value in single quotes.
-CREATE TABLE moneyp_10 PARTITION OF moneyp FOR VALUES IN ('10');

which is no longer emitted, because the patched
transformPartitionBoundValue evaluates the expression, instead of emitting
error because the expression hasn't become a Const even after applying
eval_const_expressions().

Do we need to mention any aspect of how this now works in the user-facing
documentation?

> The collation handling might need another look.  The following is
> allowed without any diagnostic:
> 
> CREATE TABLE t2 (
>     a text COLLATE "POSIX"
> ) PARTITION BY RANGE (a);
> CREATE TABLE t2a PARTITION OF t2 FOR VALUES FROM ('foo' COLLATE "C") TO
> ('xyz');
> 
> I think the correct behavior would be that an explicit collation in the
> partition bound expression is an error.

I tend to agree with that.  What happens is the partition bound expression
is assigned the collation of the parent's partition key:

+        partcollation = get_partition_col_collation(key, 0);

+            value = transformPartitionBoundValue(pstate, expr,
+                                                 colname, coltype, coltypmod,
+                                                 partcollation);

But that's essentially ignoring the collation specified by the user for
the partition bound value without providing any information about that to
the user.  I've fixed that by explicitly checking if the collate clause in
the partition bound value expression contains the same collation as the
parent partition key collation and give an error otherwise.

Updated patch attached.

Thanks,
Amit
>From 4c14559e2d8412c9ec4990e078cdc017a78ca69f Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyot...@lab.ntt.co.jp>
Date: Fri, 6 Jul 2018 14:05:22 +0900
Subject: [PATCH v8] Allow generalized expression syntax for partition bounds

Authors: Kyotaro Horiguchi, Tom Lane, Amit Langote
---
 doc/src/sgml/ref/alter_table.sgml          |   6 +-
 doc/src/sgml/ref/create_table.sgml         |  16 +--
 src/backend/commands/tablecmds.c           |   9 ++
 src/backend/optimizer/util/clauses.c       |   4 +-
 src/backend/parser/gram.y                  |  60 +-------
 src/backend/parser/parse_agg.c             |  10 ++
 src/backend/parser/parse_expr.c            |   5 +
 src/backend/parser/parse_func.c            |   3 +
 src/backend/parser/parse_utilcmd.c         | 211 +++++++++++++++++++----------
 src/include/optimizer/clauses.h            |   2 +
 src/include/parser/parse_node.h            |   1 +
 src/include/utils/partcache.h              |   6 +
 src/test/regress/expected/create_table.out |  69 +++++++---
 src/test/regress/sql/create_table.sql      |  31 ++++-
 14 files changed, 265 insertions(+), 168 deletions(-)

diff --git a/doc/src/sgml/ref/alter_table.sgml 
b/doc/src/sgml/ref/alter_table.sgml
index 0d1feaf828..bd037f2e39 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -86,9 +86,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable 
class="parameter">name</replaceable>
 
 <phrase>and <replaceable class="parameter">partition_bound_spec</replaceable> 
is:</phrase>
 
-IN ( { <replaceable class="parameter">numeric_literal</replaceable> | 
<replaceable class="parameter">string_literal</replaceable> | TRUE | FALSE | 
NULL } [, ...] ) |
-FROM ( { <replaceable class="parameter">numeric_literal</replaceable> | 
<replaceable class="parameter">string_literal</replaceable> | TRUE | FALSE | 
MINVALUE | MAXVALUE } [, ...] )
-  TO ( { <replaceable class="parameter">numeric_literal</replaceable> | 
<replaceable class="parameter">string_literal</replaceable> | TRUE | FALSE | 
MINVALUE | MAXVALUE } [, ...] ) |
+IN ( <replaceable class="parameter">partition_bound_value</replaceable> [, 
...] ) |
+FROM ( { <replaceable class="parameter">partition_bound_value</replaceable> | 
MINVALUE | MAXVALUE } [, ...] )
+  TO ( { <replaceable class="parameter">partition_bound_value</replaceable> | 
MINVALUE | MAXVALUE } [, ...] ) |
 WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, 
REMAINDER <replaceable class="parameter">numeric_literal</replaceable> )
 
 <phrase>and <replaceable class="parameter">column_constraint</replaceable> 
is:</phrase>
diff --git a/doc/src/sgml/ref/create_table.sgml 
b/doc/src/sgml/ref/create_table.sgml
index 857515ec8f..e8c0601602 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -87,9 +87,9 @@ class="parameter">referential_action</replaceable> ] [ ON 
UPDATE <replaceable cl
 
 <phrase>and <replaceable class="parameter">partition_bound_spec</replaceable> 
is:</phrase>
 
-IN ( { <replaceable class="parameter">numeric_literal</replaceable> | 
<replaceable class="parameter">string_literal</replaceable> | TRUE | FALSE | 
NULL } [, ...] ) |
-FROM ( { <replaceable class="parameter">numeric_literal</replaceable> | 
<replaceable class="parameter">string_literal</replaceable> | TRUE | FALSE | 
MINVALUE | MAXVALUE } [, ...] )
-  TO ( { <replaceable class="parameter">numeric_literal</replaceable> | 
<replaceable class="parameter">string_literal</replaceable> | TRUE | FALSE | 
MINVALUE | MAXVALUE } [, ...] ) |
+IN ( <replaceable class="parameter">partition_bound_value</replaceable> [, 
...] ) |
+FROM ( { <replaceable class="parameter">partition_bound_value</replaceable> | 
MINVALUE | MAXVALUE } [, ...] )
+  TO ( { <replaceable class="parameter">partition_bound_value</replaceable> | 
MINVALUE | MAXVALUE } [, ...] ) |
 WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, 
REMAINDER <replaceable class="parameter">numeric_literal</replaceable> )
 
 <phrase><replaceable class="parameter">index_parameters</replaceable> in 
<literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and 
<literal>EXCLUDE</literal> constraints are:</phrase>
@@ -413,12 +413,10 @@ WITH ( MODULUS <replaceable 
class="parameter">numeric_literal</replaceable>, REM
      </para>
 
      <para>
-      Each of the values specified in
-      the <replaceable class="parameter">partition_bound_spec</replaceable> is
-      a literal, <literal>NULL</literal>, <literal>MINVALUE</literal>, or
-      <literal>MAXVALUE</literal>.  Each literal value must be either a
-      numeric constant that is coercible to the corresponding partition key
-      column's type, or a string literal that is valid input for that type.
+      <replaceable class="parameter">partition_bound_value</replaceable> is
+      any variable-free expression (subqueries, window functions, aggregate
+      functions, and set-returning functions are not allowed).  Its data type
+      must match the data type of the corresponding partition key column.
      </para>
 
      <para>
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d2781cbf19..a1acdd14c2 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -827,6 +827,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
                                        defaultPartOid;
                Relation        parent,
                                        defaultRel = NULL;
+               RangeTblEntry *rte;
 
                /* Already have strong enough lock on the parent */
                parent = heap_open(parentId, NoLock);
@@ -869,6 +870,14 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
                pstate = make_parsestate(NULL);
                pstate->p_sourcetext = queryString;
 
+               /*
+                * Add an RTE containing this relation, so that transformExpr 
called
+                * on partition bound expressions is able to report errors 
using a
+                * proper context.
+                */
+               rte = addRangeTableEntryForRelation(pstate, rel, 
AccessShareLock,
+                                                                               
        NULL, false, false);
+               addRTEtoQuery(pstate, rte, false, true, true);
                bound = transformPartitionBound(pstate, parent, 
stmt->partbound);
 
                /*
diff --git a/src/backend/optimizer/util/clauses.c 
b/src/backend/optimizer/util/clauses.c
index f0ef1029d1..94b8fa0c3d 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -150,8 +150,6 @@ static Node *substitute_actual_parameters(Node *expr, int 
nargs, List *args,
 static Node *substitute_actual_parameters_mutator(Node *node,
                                                                         
substitute_actual_parameters_context *context);
 static void sql_inline_error_callback(void *arg);
-static Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
-                         Oid result_collation);
 static Query *substitute_actual_srf_parameters(Query *expr,
                                                                 int nargs, 
List *args);
 static Node *substitute_actual_srf_parameters_mutator(Node *node,
@@ -5045,7 +5043,7 @@ sql_inline_error_callback(void *arg)
  * We use the executor's routine ExecEvalExpr() to avoid duplication of
  * code and ensure we get the same result as the executor would get.
  */
-static Expr *
+Expr *
 evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
                          Oid result_collation)
 {
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c086235b25..497e29047e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -581,8 +581,7 @@ static Node *makeRecursiveViewSelect(char *relname, List 
*aliases, Node *query);
 %type <partelem>       part_elem
 %type <list>           part_params
 %type <partboundspec> PartitionBoundSpec
-%type <node>           partbound_datum PartitionRangeDatum
-%type <list>           hash_partbound partbound_datum_list range_datum_list
+%type <list>           hash_partbound
 %type <defelt>         hash_partbound_elem
 
 /*
@@ -2731,7 +2730,7 @@ PartitionBoundSpec:
                                }
 
                        /* a LIST partition */
-                       | FOR VALUES IN_P '(' partbound_datum_list ')'
+                       | FOR VALUES IN_P '(' expr_list ')'
                                {
                                        PartitionBoundSpec *n = 
makeNode(PartitionBoundSpec);
 
@@ -2744,7 +2743,7 @@ PartitionBoundSpec:
                                }
 
                        /* a RANGE partition */
-                       | FOR VALUES FROM '(' range_datum_list ')' TO '(' 
range_datum_list ')'
+                       | FOR VALUES FROM '(' expr_list ')' TO '(' expr_list ')'
                                {
                                        PartitionBoundSpec *n = 
makeNode(PartitionBoundSpec);
 
@@ -2787,59 +2786,6 @@ hash_partbound:
                        }
                ;
 
-partbound_datum:
-                       Sconst                  { $$ = makeStringConst($1, @1); 
}
-                       | NumericOnly   { $$ = makeAConst($1, @1); }
-                       | TRUE_P                { $$ = 
makeStringConst(pstrdup("true"), @1); }
-                       | FALSE_P               { $$ = 
makeStringConst(pstrdup("false"), @1); }
-                       | NULL_P                { $$ = makeNullAConst(@1); }
-               ;
-
-partbound_datum_list:
-                       partbound_datum                                         
{ $$ = list_make1($1); }
-                       | partbound_datum_list ',' partbound_datum
-                                                                               
                { $$ = lappend($1, $3); }
-               ;
-
-range_datum_list:
-                       PartitionRangeDatum                                     
{ $$ = list_make1($1); }
-                       | range_datum_list ',' PartitionRangeDatum
-                                                                               
                { $$ = lappend($1, $3); }
-               ;
-
-PartitionRangeDatum:
-                       MINVALUE
-                               {
-                                       PartitionRangeDatum *n = 
makeNode(PartitionRangeDatum);
-
-                                       n->kind = 
PARTITION_RANGE_DATUM_MINVALUE;
-                                       n->value = NULL;
-                                       n->location = @1;
-
-                                       $$ = (Node *) n;
-                               }
-                       | MAXVALUE
-                               {
-                                       PartitionRangeDatum *n = 
makeNode(PartitionRangeDatum);
-
-                                       n->kind = 
PARTITION_RANGE_DATUM_MAXVALUE;
-                                       n->value = NULL;
-                                       n->location = @1;
-
-                                       $$ = (Node *) n;
-                               }
-                       | partbound_datum
-                               {
-                                       PartitionRangeDatum *n = 
makeNode(PartitionRangeDatum);
-
-                                       n->kind = PARTITION_RANGE_DATUM_VALUE;
-                                       n->value = $1;
-                                       n->location = @1;
-
-                                       $$ = (Node *) n;
-                               }
-               ;
-
 /*****************************************************************************
  *
  *     ALTER TYPE
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index bd6201e50a..153c8641e9 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -507,6 +507,13 @@ check_agglevels_and_constraints(ParseState *pstate, Node 
*expr)
                                err = _("grouping operations are not allowed in 
trigger WHEN conditions");
 
                        break;
+               case EXPR_KIND_PARTITION_BOUND:
+                       if (isAgg)
+                               err = _("aggregate functions are not allowed in 
partition bound");
+                       else
+                               err = _("grouping operations are not allowed in 
partition bound");
+
+                       break;
                case EXPR_KIND_PARTITION_EXPRESSION:
                        if (isAgg)
                                err = _("aggregate functions are not allowed in 
partition key expressions");
@@ -896,6 +903,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc 
*wfunc,
                case EXPR_KIND_TRIGGER_WHEN:
                        err = _("window functions are not allowed in trigger 
WHEN conditions");
                        break;
+               case EXPR_KIND_PARTITION_BOUND:
+                       err = _("window functions are not allowed in partition 
bound");
+                       break;
                case EXPR_KIND_PARTITION_EXPRESSION:
                        err = _("window functions are not allowed in partition 
key expressions");
                        break;
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index bff237094a..397b945825 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1843,6 +1843,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
                case EXPR_KIND_TRIGGER_WHEN:
                        err = _("cannot use subquery in trigger WHEN 
condition");
                        break;
+               case EXPR_KIND_PARTITION_BOUND:
+                       err = _("cannot use subquery in partition bound");
+                       break;
                case EXPR_KIND_PARTITION_EXPRESSION:
                        err = _("cannot use subquery in partition key 
expression");
                        break;
@@ -3471,6 +3474,8 @@ ParseExprKindName(ParseExprKind exprKind)
                        return "EXECUTE";
                case EXPR_KIND_TRIGGER_WHEN:
                        return "WHEN";
+               case EXPR_KIND_PARTITION_BOUND:
+                       return "partition bound";
                case EXPR_KIND_PARTITION_EXPRESSION:
                        return "PARTITION BY";
                case EXPR_KIND_CALL_ARGUMENT:
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 4661fc4f62..e997bb1871 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2364,6 +2364,9 @@ check_srf_call_placement(ParseState *pstate, Node 
*last_srf, int location)
                case EXPR_KIND_TRIGGER_WHEN:
                        err = _("set-returning functions are not allowed in 
trigger WHEN conditions");
                        break;
+               case EXPR_KIND_PARTITION_BOUND:
+                       err = _("set-returning functions are not allowed in 
partition bound");
+                       break;
                case EXPR_KIND_PARTITION_EXPRESSION:
                        err = _("set-returning functions are not allowed in 
partition key expressions");
                        break;
diff --git a/src/backend/parser/parse_utilcmd.c 
b/src/backend/parser/parse_utilcmd.c
index bebee9d540..8bbbfa03b6 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -49,7 +49,9 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
 #include "optimizer/planner.h"
+#include "optimizer/var.h"
 #include "parser/analyze.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
@@ -138,9 +140,12 @@ static void transformConstraintAttrs(CreateStmtContext 
*cxt,
 static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
 static void setSchemaName(char *context_schema, char **stmt_schema_name);
 static void transformPartitionCmd(CreateStmtContext *cxt, PartitionCmd *cmd);
+static List *transformPartitionRangeBounds(ParseState *pstate, List *blist,
+                                                         Relation parent);
 static void validateInfiniteBounds(ParseState *pstate, List *blist);
-static Const *transformPartitionBoundValue(ParseState *pstate, A_Const *con,
-                                                        const char *colName, 
Oid colType, int32 colTypmod);
+static Const *transformPartitionBoundValue(ParseState *pstate, Node *con,
+                                                        const char *colName, 
Oid colType, int32 colTypmod,
+                                                        Oid partCollation);
 
 
 /*
@@ -3624,6 +3629,7 @@ transformPartitionBound(ParseState *pstate, Relation 
parent,
                char       *colname;
                Oid                     coltype;
                int32           coltypmod;
+               Oid                     partcollation;
 
                if (spec->strategy != PARTITION_STRATEGY_LIST)
                        ereport(ERROR,
@@ -3643,17 +3649,19 @@ transformPartitionBound(ParseState *pstate, Relation 
parent,
                /* Need its type data too */
                coltype = get_partition_col_typid(key, 0);
                coltypmod = get_partition_col_typmod(key, 0);
+               partcollation = get_partition_col_collation(key, 0);
 
                result_spec->listdatums = NIL;
                foreach(cell, spec->listdatums)
                {
-                       A_Const    *con = castNode(A_Const, lfirst(cell));
+                       Node       *expr = (Node *) lfirst (cell);
                        Const      *value;
                        ListCell   *cell2;
                        bool            duplicate;
 
-                       value = transformPartitionBoundValue(pstate, con,
-                                                                               
                 colname, coltype, coltypmod);
+                       value = transformPartitionBoundValue(pstate, expr,
+                                                                               
                 colname, coltype, coltypmod,
+                                                                               
                 partcollation);
 
                        /* Don't add to the result if the value is a duplicate 
*/
                        duplicate = false;
@@ -3676,11 +3684,6 @@ transformPartitionBound(ParseState *pstate, Relation 
parent,
        }
        else if (strategy == PARTITION_STRATEGY_RANGE)
        {
-               ListCell   *cell1,
-                                  *cell2;
-               int                     i,
-                                       j;
-
                if (spec->strategy != PARTITION_STRATEGY_RANGE)
                        ereport(ERROR,
                                        
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
@@ -3697,23 +3700,78 @@ transformPartitionBound(ParseState *pstate, Relation 
parent,
                                         errmsg("TO must specify exactly one 
value per partitioning column")));
 
                /*
-                * Once we see MINVALUE or MAXVALUE for one column, the 
remaining
-                * columns must be the same.
+                * Convert raw parser nodes into PartitionRangeDatum nodes and 
perform
+                * any necessary validation.
                 */
-               validateInfiniteBounds(pstate, spec->lowerdatums);
-               validateInfiniteBounds(pstate, spec->upperdatums);
+               result_spec->lowerdatums =
+                                       transformPartitionRangeBounds(pstate, 
spec->lowerdatums,
+                                                                               
                  parent);
+               result_spec->upperdatums =
+                                       transformPartitionRangeBounds(pstate, 
spec->upperdatums,
+                                                                               
                  parent);
+       }
+       else
+               elog(ERROR, "unexpected partition strategy: %d", (int) 
strategy);
 
-               /* Transform all the constants */
-               i = j = 0;
-               result_spec->lowerdatums = result_spec->upperdatums = NIL;
-               forboth(cell1, spec->lowerdatums, cell2, spec->upperdatums)
+       return result_spec;
+}
+
+/*
+ * transformPartitionRangeBounds
+ *             This converts the expressions for range partition bounds from 
the raw
+ *             grammar representation to PartitionRangeDatum structs
+ */
+static List *
+transformPartitionRangeBounds(ParseState *pstate, List *blist,
+                                                         Relation parent)
+{
+       List       *result = NIL;
+       PartitionKey key = RelationGetPartitionKey(parent);
+       List       *partexprs = get_partition_exprs(key);
+       ListCell   *lc;
+       int                     i,
+                               j;
+
+       i = j = 0;
+       foreach(lc, blist)
+       {
+               Node *expr = lfirst(lc);
+               PartitionRangeDatum *prd = NULL;
+
+               /*
+                * Infinite range bounds -- "minvalue" and "maxvalue" -- get 
passed
+                * in as ColumnRefs.
+                */
+               if (IsA(expr, ColumnRef))
+               {
+                       ColumnRef *cref = (ColumnRef *) expr;
+                       char *cname = NULL;
+
+                       if (list_length(cref->fields) == 1 &&
+                               IsA(linitial(cref->fields), String))
+                               cname = strVal(linitial(cref->fields));
+
+                       Assert(cname != NULL);
+                       if (strcmp("minvalue", cname) == 0)
+                       {
+                               prd = makeNode(PartitionRangeDatum);
+                               prd->kind = PARTITION_RANGE_DATUM_MINVALUE;
+                               prd->value = NULL;
+                       }
+                       else if (strcmp("maxvalue", cname) == 0)
+                       {
+                               prd = makeNode(PartitionRangeDatum);
+                               prd->kind = PARTITION_RANGE_DATUM_MAXVALUE;
+                               prd->value = NULL;
+                       }
+               }
+
+               if (prd == NULL)
                {
-                       PartitionRangeDatum *ldatum = (PartitionRangeDatum *) 
lfirst(cell1);
-                       PartitionRangeDatum *rdatum = (PartitionRangeDatum *) 
lfirst(cell2);
                        char       *colname;
                        Oid                     coltype;
                        int32           coltypmod;
-                       A_Const    *con;
+                       Oid                     partcollation;
                        Const      *value;
 
                        /* Get the column's name in case we need to output an 
error */
@@ -3728,50 +3786,38 @@ transformPartitionBound(ParseState *pstate, Relation 
parent,
                                                                                
         false, false);
                                ++j;
                        }
+
                        /* Need its type data too */
                        coltype = get_partition_col_typid(key, i);
                        coltypmod = get_partition_col_typmod(key, i);
+                       partcollation = get_partition_col_collation(key, i);
 
-                       if (ldatum->value)
-                       {
-                               con = castNode(A_Const, ldatum->value);
-                               value = transformPartitionBoundValue(pstate, 
con,
-                                                                               
                         colname,
-                                                                               
                         coltype, coltypmod);
-                               if (value->constisnull)
-                                       ereport(ERROR,
-                                                       
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-                                                        errmsg("cannot specify 
NULL in range bound")));
-                               ldatum = copyObject(ldatum);    /* don't 
scribble on input */
-                               ldatum->value = (Node *) value;
-                       }
-
-                       if (rdatum->value)
-                       {
-                               con = castNode(A_Const, rdatum->value);
-                               value = transformPartitionBoundValue(pstate, 
con,
-                                                                               
                         colname,
-                                                                               
                         coltype, coltypmod);
-                               if (value->constisnull)
-                                       ereport(ERROR,
-                                                       
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-                                                        errmsg("cannot specify 
NULL in range bound")));
-                               rdatum = copyObject(rdatum);    /* don't 
scribble on input */
-                               rdatum->value = (Node *) value;
-                       }
-
-                       result_spec->lowerdatums = 
lappend(result_spec->lowerdatums,
-                                                                               
           ldatum);
-                       result_spec->upperdatums = 
lappend(result_spec->upperdatums,
-                                                                               
           rdatum);
-
+                       value = transformPartitionBoundValue(pstate, expr,
+                                                                               
                 colname,
+                                                                               
                 coltype, coltypmod,
+                                                                               
                 partcollation);
+                       if (value->constisnull)
+                               ereport(ERROR,
+                                               
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                                                errmsg("cannot specify NULL in 
range bound")));
+                       prd = makeNode(PartitionRangeDatum);
+                       prd->kind = PARTITION_RANGE_DATUM_VALUE;
+                       prd->value = (Node *) value;
                        ++i;
                }
-       }
-       else
-               elog(ERROR, "unexpected partition strategy: %d", (int) 
strategy);
 
-       return result_spec;
+               prd->location = exprLocation(expr);
+
+               result = lappend(result, prd);
+       }
+
+       /*
+        * Once we see MINVALUE or MAXVALUE for one column, the remaining
+        * columns must be the same.
+        */
+       validateInfiniteBounds(pstate, result);
+
+       return result;
 }
 
 /*
@@ -3820,13 +3866,31 @@ validateInfiniteBounds(ParseState *pstate, List *blist)
  * Transform one constant in a partition bound spec
  */
 static Const *
-transformPartitionBoundValue(ParseState *pstate, A_Const *con,
-                                                        const char *colName, 
Oid colType, int32 colTypmod)
+transformPartitionBoundValue(ParseState *pstate, Node *val,
+                                                        const char *colName, 
Oid colType, int32 colTypmod,
+                                                        Oid partCollation)
 {
        Node       *value;
+       Oid                     exprCollOid;
 
-       /* Make it into a Const */
-       value = (Node *) make_const(pstate, &con->val, con->location);
+       /* Transform raw parsetree */
+       value = transformExpr(pstate, val, EXPR_KIND_PARTITION_BOUND);
+
+       /*
+        * Check that the input expression's collation is compatible with one
+        * specified for the parent's partition key (partcollation).  Don't
+        * throw an error if it's the default collation which we'll replace with
+        * the parent's collation anyway.
+        */
+       exprCollOid = exprCollation(value);
+       if (OidIsValid(exprCollOid) &&
+               exprCollOid != DEFAULT_COLLATION_OID &&
+               exprCollOid != partCollation)
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                errmsg("collation of partition bound value for 
column \"%s\" does not match partition key collation \"%s\"",
+                                               colName, 
get_collation_name(partCollation)),
+                                parser_errposition(pstate, 
exprLocation(val))));
 
        /* Coerce to correct type */
        value = coerce_to_target_type(pstate,
@@ -3842,21 +3906,26 @@ transformPartitionBoundValue(ParseState *pstate, 
A_Const *con,
                                (errcode(ERRCODE_DATATYPE_MISMATCH),
                                 errmsg("specified value cannot be cast to type 
%s for column \"%s\"",
                                                format_type_be(colType), 
colName),
-                                parser_errposition(pstate, con->location)));
+                                parser_errposition(pstate, 
exprLocation(val))));
 
        /* Simplify the expression, in case we had a coercion */
        if (!IsA(value, Const))
                value = (Node *) expression_planner((Expr *) value);
 
-       /* Fail if we don't have a constant (i.e., non-immutable coercion) */
-       if (!IsA(value, Const))
+       /* Make sure the expression does not refer to any vars. */
+       if (contain_var_clause(value))
                ereport(ERROR,
-                               (errcode(ERRCODE_DATATYPE_MISMATCH),
-                                errmsg("specified value cannot be cast to type 
%s for column \"%s\"",
-                                               format_type_be(colType), 
colName),
-                                errdetail("The cast requires a non-immutable 
conversion."),
-                                errhint("Try putting the literal value in 
single quotes."),
-                                parser_errposition(pstate, con->location)));
+                               (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+                                errmsg("cannot use column references in 
partition bound expression")));
+
+       /*
+        * If it is still not a Const, evaluate the expression assigning the
+        * partition key's collation to the resulting Const expression.
+        */
+       value = (Node *) evaluate_expr((Expr *) value, colType, colTypmod,
+                                                                  
partCollation);
+       if (!IsA(value, Const))
+               elog(ERROR, "could not evaluate partition bound expression");
 
        return (Const *) value;
 }
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 5c8580e478..1bf34c30f2 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -88,4 +88,6 @@ extern Query *inline_set_returning_function(PlannerInfo *root,
 extern List *expand_function_arguments(List *args, Oid result_type,
                                                  HeapTuple func_tuple);
 
+extern Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
+                                                  Oid result_collation);
 #endif                                                 /* CLAUSES_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index f4e1cdd85b..22854b5e28 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -69,6 +69,7 @@ typedef enum ParseExprKind
        EXPR_KIND_TRIGGER_WHEN,         /* WHEN condition in CREATE TRIGGER */
        EXPR_KIND_POLICY,                       /* USING or WITH CHECK expr in 
policy */
        EXPR_KIND_PARTITION_EXPRESSION, /* PARTITION BY expression */
+       EXPR_KIND_PARTITION_BOUND,      /* partition bound value */
        EXPR_KIND_CALL_ARGUMENT         /* procedure argument in CALL */
 } ParseExprKind;
 
diff --git a/src/include/utils/partcache.h b/src/include/utils/partcache.h
index d058a1d194..7c2f973f68 100644
--- a/src/include/utils/partcache.h
+++ b/src/include/utils/partcache.h
@@ -93,4 +93,10 @@ get_partition_col_typmod(PartitionKey key, int col)
        return key->parttypmod[col];
 }
 
+static inline Oid
+get_partition_col_collation(PartitionKey key, int col)
+{
+       return key->partcollation[col];
+}
+
 #endif                                                 /* PARTCACHE_H */
diff --git a/src/test/regress/expected/create_table.out 
b/src/test/regress/expected/create_table.out
index 7e52c27e3f..ff3489c6a1 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -460,19 +460,40 @@ DROP TABLE partitioned, partitioned2;
 CREATE TABLE list_parted (
        a int
 ) PARTITION BY LIST (a);
--- syntax allows only string literal, numeric literal and null to be
--- specified for a partition bound value
 CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
 CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE part_3 PARTITION OF list_parted FOR VALUES IN ((2+1));
 CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
-CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
-ERROR:  syntax error at or near "int"
-LINE 1: ... fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
-                                                              ^
-CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
-ERROR:  syntax error at or near "::"
-LINE 1: ...fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
-                                                                ^
+\d+ list_parted
+                          Partitioned table "public.list_parted"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | 
Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a      | integer |           |          |         | plain   |              | 
+Partition key: LIST (a)
+Partitions: part_1 FOR VALUES IN (1),
+            part_2 FOR VALUES IN (2),
+            part_3 FOR VALUES IN (3),
+            part_null FOR VALUES IN (NULL)
+
+-- forbidden expressions for partition bound
+CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN 
(somename);
+ERROR:  column "somename" does not exist
+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 (a);
+ERROR:  cannot use column references in partition bound expression
+CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN 
(sum(a));
+ERROR:  aggregate functions are not allowed in partition bound
+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 
((select 1));
+ERROR:  cannot use subquery in partition bound
+LINE 1: ...expr_fail PARTITION OF list_parted FOR VALUES IN ((select 1)...
+                                                             ^
+CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN 
(generate_series(4, 6));
+ERROR:  set-returning functions are not allowed in partition bound
+LINE 1: ...expr_fail PARTITION OF list_parted FOR VALUES IN (generate_s...
+                                                             ^
 -- syntax does not allow empty list of values for list partitions
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
 ERROR:  syntax error at or near ")"
@@ -501,19 +522,15 @@ ERROR:  specified value cannot be cast to type boolean 
for column "a"
 LINE 1: ...REATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
                                                                     ^
 DROP TABLE bools;
--- specified literal can be cast, but cast isn't immutable
+-- specified literal can be cast, and the cast may be immutable
 CREATE TABLE moneyp (
        a money
 ) PARTITION BY LIST (a);
 CREATE TABLE moneyp_10 PARTITION OF moneyp FOR VALUES IN (10);
-ERROR:  specified value cannot be cast to type money for column "a"
-LINE 1: ...EATE TABLE moneyp_10 PARTITION OF moneyp FOR VALUES IN (10);
-                                                                   ^
-DETAIL:  The cast requires a non-immutable conversion.
-HINT:  Try putting the literal value in single quotes.
-CREATE TABLE moneyp_10 PARTITION OF moneyp FOR VALUES IN ('10');
+CREATE TABLE moneyp_11 PARTITION OF moneyp FOR VALUES IN ('11');
+CREATE TABLE moneyp_12 PARTITION OF moneyp FOR VALUES IN (to_char(12, 
'99')::int);
 DROP TABLE moneyp;
--- immutable cast should work, though
+-- cast is immutable
 CREATE TABLE bigintp (
        a bigint
 ) PARTITION BY LIST (a);
@@ -774,6 +791,22 @@ create table parted_collate_must_match1 partition of 
parted_collate_must_match
 create table parted_collate_must_match2 partition of parted_collate_must_match
   (b collate "POSIX") for values from ('m') to ('z');
 drop table parted_collate_must_match;
+-- check for collation mismatch between partition key and partition bound 
values
+create table test_part_coll_posix (a text) partition by range (a collate 
"POSIX");
+create table test_part_coll_c partition of test_part_coll_posix for values 
from ('a' collate "C") to ('m');
+ERROR:  collation of partition bound value for column "a" does not match 
partition key collation "POSIX"
+LINE 1: ...artition of test_part_coll_posix for values from ('a' collat...
+                                                             ^
+create table test_part_coll_ok partition of test_part_coll_posix for values 
from ('a' collate "POSIX") to ('m');
+drop table test_part_coll_posix;
+create table test_part_coll_default (a text) partition by range (a);
+create table test_part_coll_c partition of test_part_coll_default for values 
from ('a' collate "C") to ('m');
+ERROR:  collation of partition bound value for column "a" does not match 
partition key collation "default"
+LINE 1: ...tition of test_part_coll_default for values from ('a' collat...
+                                                             ^
+create table test_part_coll_ok partition of test_part_coll_default for values 
from ('a') to ('m');
+create table test_part_coll_ok_too partition of test_part_coll_default for 
values from ('m' collate "default") to ('z');
+drop table test_part_coll_default;
 -- Partition bound in describe output
 \d+ part_b
                                    Table "public.part_b"
diff --git a/src/test/regress/sql/create_table.sql 
b/src/test/regress/sql/create_table.sql
index a2cae9663c..64a21244a6 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -436,13 +436,18 @@ DROP TABLE partitioned, partitioned2;
 CREATE TABLE list_parted (
        a int
 ) PARTITION BY LIST (a);
--- syntax allows only string literal, numeric literal and null to be
--- specified for a partition bound value
 CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
 CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE part_3 PARTITION OF list_parted FOR VALUES IN ((2+1));
 CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
-CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
-CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+\d+ list_parted
+
+-- forbidden expressions for partition bound
+CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN 
(somename);
+CREATE TABLE part_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));
+CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN 
((select 1));
+CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN 
(generate_series(4, 6));
 
 -- syntax does not allow empty list of values for list partitions
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
@@ -462,15 +467,16 @@ CREATE TABLE bools (
 CREATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
 DROP TABLE bools;
 
--- specified literal can be cast, but cast isn't immutable
+-- specified literal can be cast, and the cast may be immutable
 CREATE TABLE moneyp (
        a money
 ) PARTITION BY LIST (a);
 CREATE TABLE moneyp_10 PARTITION OF moneyp FOR VALUES IN (10);
-CREATE TABLE moneyp_10 PARTITION OF moneyp FOR VALUES IN ('10');
+CREATE TABLE moneyp_11 PARTITION OF moneyp FOR VALUES IN ('11');
+CREATE TABLE moneyp_12 PARTITION OF moneyp FOR VALUES IN (to_char(12, 
'99')::int);
 DROP TABLE moneyp;
 
--- immutable cast should work, though
+-- cast is immutable
 CREATE TABLE bigintp (
        a bigint
 ) PARTITION BY LIST (a);
@@ -686,6 +692,17 @@ create table parted_collate_must_match2 partition of 
parted_collate_must_match
   (b collate "POSIX") for values from ('m') to ('z');
 drop table parted_collate_must_match;
 
+-- check for collation mismatch between partition key and partition bound 
values
+create table test_part_coll_posix (a text) partition by range (a collate 
"POSIX");
+create table test_part_coll_c partition of test_part_coll_posix for values 
from ('a' collate "C") to ('m');
+create table test_part_coll_ok partition of test_part_coll_posix for values 
from ('a' collate "POSIX") to ('m');
+drop table test_part_coll_posix;
+create table test_part_coll_default (a text) partition by range (a);
+create table test_part_coll_c partition of test_part_coll_default for values 
from ('a' collate "C") to ('m');
+create table test_part_coll_ok partition of test_part_coll_default for values 
from ('a') to ('m');
+create table test_part_coll_ok_too partition of test_part_coll_default for 
values from ('m' collate "default") to ('z');
+drop table test_part_coll_default;
+
 -- Partition bound in describe output
 \d+ part_b
 
-- 
2.11.0

Reply via email to