Re: using expression syntax for partition bounds - Mailing list pgsql-hackers

From Kyotaro HORIGUCHI
Subject Re: using expression syntax for partition bounds
Date
Msg-id 20180706.142603.165190373.horiguchi.kyotaro@lab.ntt.co.jp
Whole thread Raw
In response to Re: using expression syntax for partition bounds  (Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>)
Responses Re: using expression syntax for partition bounds
List pgsql-hackers
Hello.

cf-bot compained on this but I wondered why it got so many
errors. At the first look I found a spare semicolon before a bare
then calause:p

> -    if (!IsA(value, Const));
> +    if (!IsA(value, Const))
>          elog(ERROR, "could not evaluate partition bound expression");

The attached is the fixed and rebsaed version.

regards.

-- 
Kyotaro Horiguchi
NTT Open Source Software Center
From bbf76ee4a185133e625be88bf0784f2bc308772b Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Fri, 6 Jul 2018 14:05:22 +0900
Subject: [PATCH] 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           |   8 ++
 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         | 201 ++++++++++++++++++-----------
 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 |  49 ++++---
 src/test/regress/sql/create_table.sql      |  16 ++-
 14 files changed, 221 insertions(+), 166 deletions(-)

diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 1cce00eaf9..aa70beeb32 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -87,9 +87,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_expr</replaceable> [, ...] ) |
+FROM ( { <replaceable class="parameter">partition_bound_expr</replaceable> | MINVALUE | MAXVALUE } [, ...] )
+  TO ( { <replaceable class="parameter">partition_bound_expr</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 2a1eac9592..fcbabc78ff 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -86,9 +86,9 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 
 <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_expr</replaceable> [, ...] ) |
+FROM ( { <replaceable class="parameter">partition_bound_expr</replaceable> | MINVALUE | MAXVALUE } [, ...] )
+  TO ( { <replaceable class="parameter">partition_bound_expr</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>
 
@@ -273,12 +273,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_expr</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 7c0cf0d7ee..fc24e6fa2b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -796,6 +796,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);
@@ -838,6 +839,13 @@ 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, 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 505ae0af85..77ebb40ef2 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,
@@ -4840,7 +4838,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 90dfac2cb1..183622fe44 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
 
 /*
@@ -2739,7 +2738,7 @@ PartitionBoundSpec:
                 }
 
             /* a LIST partition */
-            | FOR VALUES IN_P '(' partbound_datum_list ')'
+            | FOR VALUES IN_P '(' expr_list ')'
                 {
                     PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
 
@@ -2752,7 +2751,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);
 
@@ -2795,59 +2794,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 61727e1d71..55a5304a38 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -499,6 +499,13 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
             else
                 err = _("grouping operations are not allowed in EXECUTE parameters");
 
+            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_TRIGGER_WHEN:
             if (isAgg)
@@ -899,6 +906,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
         case EXPR_KIND_PARTITION_EXPRESSION:
             err = _("window functions are not allowed in partition key expressions");
             break;
+        case EXPR_KIND_PARTITION_BOUND:
+            err = _("window functions are not allowed in partition bound");
+            break;
         case EXPR_KIND_CALL_ARGUMENT:
             err = _("window functions are not allowed in CALL arguments");
             break;
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 385e54a9b6..f8759f185b 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1849,6 +1849,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
         case EXPR_KIND_CALL_ARGUMENT:
             err = _("cannot use subquery in CALL argument");
             break;
+        case EXPR_KIND_PARTITION_BOUND:
+            err = _("cannot use subquery in partition bound");
+            break;
 
             /*
              * There is intentionally no default: case here, so that the
@@ -3473,6 +3476,8 @@ ParseExprKindName(ParseExprKind exprKind)
             return "WHEN";
         case EXPR_KIND_PARTITION_EXPRESSION:
             return "PARTITION BY";
+        case EXPR_KIND_PARTITION_BOUND:
+            return "partition bound";
         case EXPR_KIND_CALL_ARGUMENT:
             return "CALL";
 
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index abe1dbc521..5190b628bd 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2348,6 +2348,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
         case EXPR_KIND_PARTITION_EXPRESSION:
             err = _("set-returning functions are not allowed in partition key expressions");
             break;
+        case EXPR_KIND_PARTITION_BOUND:
+            err = _("set-returning functions are not allowed in partition bound");
+            break;
         case EXPR_KIND_CALL_ARGUMENT:
             err = _("set-returning functions are not allowed in CALL arguments");
             break;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 17b54b20cc..a94cf863c1 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -48,7 +48,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);
 
 
 /*
@@ -3649,6 +3654,7 @@ transformPartitionBound(ParseState *pstate, Relation parent,
         char       *colname;
         Oid            coltype;
         int32        coltypmod;
+        Oid            partcollation;
 
         if (spec->strategy != PARTITION_STRATEGY_LIST)
             ereport(ERROR,
@@ -3668,17 +3674,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;
@@ -3701,11 +3709,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),
@@ -3722,23 +3725,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 */
@@ -3753,50 +3811,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;
 }
 
 /*
@@ -3845,13 +3891,14 @@ 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;
 
-    /* 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);
 
     /* Coerce to correct type */
     value = coerce_to_target_type(pstate,
@@ -3867,21 +3914,29 @@ 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 */
+    /* We effectively ignore the value's collation. */
     if (!IsA(value, Const))
-        value = (Node *) expression_planner((Expr *) value);
+        value = (Node *) eval_const_expressions(NULL, value);
 
-    /* Fail if we don't have a constant (i.e., non-immutable coercion) */
-    if (!IsA(value, Const))
+    /*
+     * 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(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 applying the
+     * partition key's collation.
+     */
+    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 ed854fdd40..06034d499b 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 0230543810..27398dd0b8 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 873c60fafd..dffe561ebf 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 672719e5d5..4202594c73 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -444,19 +444,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
+                                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 ")"
@@ -490,12 +511,8 @@ 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
 CREATE TABLE bigintp (
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78944950fe..30ad89b864 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -427,13 +427,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 ();
@@ -458,7 +463,8 @@ 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
-- 
2.16.3


pgsql-hackers by date:

Previous
From: Michael Paquier
Date:
Subject: Re: [HACKERS] Replication status in logical replication
Next
From: Michael Paquier
Date:
Subject: Re: Non-reserved replication slots and slot advancing