Re: Since '2001-09-09 01:46:40'::timestamp microseconds are lost when extracting epoch - Mailing list pgsql-hackers

From Tom Lane
Subject Re: Since '2001-09-09 01:46:40'::timestamp microseconds are lost when extracting epoch
Date
Msg-id 298489.1604188643@sss.pgh.pa.us
Whole thread Raw
In response to Re: Since '2001-09-09 01:46:40'::timestamp microseconds are lost when extracting epoch  (Tom Lane <tgl@sss.pgh.pa.us>)
Responses Re: Since '2001-09-09 01:46:40'::timestamp microseconds are lost when extracting epoch  (Tom Lane <tgl@sss.pgh.pa.us>)
List pgsql-hackers
I wrote:
> However: suppose that we continue to translate these things into FuncExpr
> nodes, the same as always, but we add a new CoercionForm variant, say
> COERCE_SQL_SYNTAX.  99% of the system ignores FuncExpr.funcformat,
> and would continue to do so, but ruleutils.c would take it to mean
> that (1) the call should be reverse-listed as some special SQL syntax
> and (2) the funcid is one of a small set of built-in functions for
> which ruleutils.c knows what to emit.  (If it doesn't recognize the
> funcid, it could either throw an error, or fall back to normal display
> of the node.)  For cases such as EXTRACT, this would also represent
> a promise that specific arguments are Const nodes from which the
> desired keyword can be extracted.

Attached is a draft patch that does this.  I'm fairly pleased with it,
but there are some loose ends as described below.  As the patch stands,
it reverse-lists all our special-format function call syntaxes
*except* EXTRACT.  I left that out since I think we want to apply the
reverse-listing change when we add the numeric-output extraction
functions, as I said upthread.

The main thing that's incomplete here is that the switch on function
OID fails to cover some cases that ought to be covered, as a result
of limitations of Gen_fmgrtab.pl:

* Some C functions such as text_substr have multiple pg_proc entries,
and Gen_fmgrtab.pl chooses the wrong one for our purpose.  We could
either invent new Gen_fmgrtab.pl behavior to allow having macros for
all the pg_proc entries, or we could add duplicate C functions so that
the pg_proc entries can point to different C symbols.

* Some of the functions we need to reference aren't C functions at
all, but SQL functions, for instance OID 1305 is defined as
    select ($1, ($1 + $2)) overlaps ($3, ($3 + $4))
I think our best bet here is to replace these SQL definitions with
C equivalents, because really this implementation is pretty sucky.
Even if we manage to inline the SQL definition, that's expensive
to do; and evaluating some of the arguments twice is not nice either.

> This is kind of an abuse of "CoercionForm", since that typedef name
> implies that it only talks about how to handle cast cases, but
> semantically it's always been a how-to-display-function-calls thing.
> We could either hold our noses about that or rename the typedef.

I did nothing about that here, since it'd bloat the patch without
making anything but cosmetic changes.  I'm tempted to propose though
that we rename "CoercionForm" to "DisplayForm" and rename its
COERCE_XXX values to DISPLAY_XXX, to make this less confusing.

Another bit of follow-up work we could contemplate is to get rid of
the SQLValueFunction node type, since there's nothing it does that
we couldn't do with regular FuncExpr nodes and COERCE_SQL_SYNTAX.
But that's just cleanup, and I don't think it would save a very
large amount of code.

Thoughts?

            regards, tom lane

diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 2b4d7654cc..f14236ad3a 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2682,11 +2682,12 @@ _copyFuncCall(const FuncCall *from)
     COPY_NODE_FIELD(args);
     COPY_NODE_FIELD(agg_order);
     COPY_NODE_FIELD(agg_filter);
+    COPY_NODE_FIELD(over);
     COPY_SCALAR_FIELD(agg_within_group);
     COPY_SCALAR_FIELD(agg_star);
     COPY_SCALAR_FIELD(agg_distinct);
     COPY_SCALAR_FIELD(func_variadic);
-    COPY_NODE_FIELD(over);
+    COPY_SCALAR_FIELD(funcformat);
     COPY_LOCATION_FIELD(location);

     return newnode;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e2d1b987bf..8985b11f8f 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2377,11 +2377,12 @@ _equalFuncCall(const FuncCall *a, const FuncCall *b)
     COMPARE_NODE_FIELD(args);
     COMPARE_NODE_FIELD(agg_order);
     COMPARE_NODE_FIELD(agg_filter);
+    COMPARE_NODE_FIELD(over);
     COMPARE_SCALAR_FIELD(agg_within_group);
     COMPARE_SCALAR_FIELD(agg_star);
     COMPARE_SCALAR_FIELD(agg_distinct);
     COMPARE_SCALAR_FIELD(func_variadic);
-    COMPARE_NODE_FIELD(over);
+    COMPARE_SCALAR_FIELD(funcformat);
     COMPARE_LOCATION_FIELD(location);

     return true;
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 49de285f01..ee033ae779 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -582,7 +582,7 @@ makeDefElemExtended(char *nameSpace, char *name, Node *arg,
  * supply.  Any non-default parameters have to be inserted by the caller.
  */
 FuncCall *
-makeFuncCall(List *name, List *args, int location)
+makeFuncCall(List *name, List *args, CoercionForm funcformat, int location)
 {
     FuncCall   *n = makeNode(FuncCall);

@@ -590,11 +590,12 @@ makeFuncCall(List *name, List *args, int location)
     n->args = args;
     n->agg_order = NIL;
     n->agg_filter = NULL;
+    n->over = NULL;
     n->agg_within_group = false;
     n->agg_star = false;
     n->agg_distinct = false;
     n->func_variadic = false;
-    n->over = NULL;
+    n->funcformat = funcformat;
     n->location = location;
     return n;
 }
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 530328af43..cafe19b2f0 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2766,11 +2766,12 @@ _outFuncCall(StringInfo str, const FuncCall *node)
     WRITE_NODE_FIELD(args);
     WRITE_NODE_FIELD(agg_order);
     WRITE_NODE_FIELD(agg_filter);
+    WRITE_NODE_FIELD(over);
     WRITE_BOOL_FIELD(agg_within_group);
     WRITE_BOOL_FIELD(agg_star);
     WRITE_BOOL_FIELD(agg_distinct);
     WRITE_BOOL_FIELD(func_variadic);
-    WRITE_NODE_FIELD(over);
+    WRITE_ENUM_FIELD(funcformat, CoercionForm);
     WRITE_LOCATION_FIELD(location);
 }

diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 480d168346..20e840ce20 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -12977,6 +12977,7 @@ a_expr:        c_expr                                    { $$ = $1; }
                 {
                     $$ = (Node *) makeFuncCall(SystemFuncName("timezone"),
                                                list_make2($5, $1),
+                                               COERCE_SQL_SYNTAX,
                                                @2);
                 }
         /*
@@ -13040,6 +13041,7 @@ a_expr:        c_expr                                    { $$ = $1; }
                 {
                     FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
                                                list_make2($3, $5),
+                                               COERCE_EXPLICIT_CALL,
                                                @2);
                     $$ = (Node *) makeSimpleA_Expr(AEXPR_LIKE, "~~",
                                                    $1, (Node *) n, @2);
@@ -13053,6 +13055,7 @@ a_expr:        c_expr                                    { $$ = $1; }
                 {
                     FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
                                                list_make2($4, $6),
+                                               COERCE_EXPLICIT_CALL,
                                                @2);
                     $$ = (Node *) makeSimpleA_Expr(AEXPR_LIKE, "!~~",
                                                    $1, (Node *) n, @2);
@@ -13066,6 +13069,7 @@ a_expr:        c_expr                                    { $$ = $1; }
                 {
                     FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
                                                list_make2($3, $5),
+                                               COERCE_EXPLICIT_CALL,
                                                @2);
                     $$ = (Node *) makeSimpleA_Expr(AEXPR_ILIKE, "~~*",
                                                    $1, (Node *) n, @2);
@@ -13079,6 +13083,7 @@ a_expr:        c_expr                                    { $$ = $1; }
                 {
                     FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
                                                list_make2($4, $6),
+                                               COERCE_EXPLICIT_CALL,
                                                @2);
                     $$ = (Node *) makeSimpleA_Expr(AEXPR_ILIKE, "!~~*",
                                                    $1, (Node *) n, @2);
@@ -13088,6 +13093,7 @@ a_expr:        c_expr                                    { $$ = $1; }
                 {
                     FuncCall *n = makeFuncCall(SystemFuncName("similar_to_escape"),
                                                list_make1($4),
+                                               COERCE_EXPLICIT_CALL,
                                                @2);
                     $$ = (Node *) makeSimpleA_Expr(AEXPR_SIMILAR, "~",
                                                    $1, (Node *) n, @2);
@@ -13096,6 +13102,7 @@ a_expr:        c_expr                                    { $$ = $1; }
                 {
                     FuncCall *n = makeFuncCall(SystemFuncName("similar_to_escape"),
                                                list_make2($4, $6),
+                                               COERCE_EXPLICIT_CALL,
                                                @2);
                     $$ = (Node *) makeSimpleA_Expr(AEXPR_SIMILAR, "~",
                                                    $1, (Node *) n, @2);
@@ -13104,6 +13111,7 @@ a_expr:        c_expr                                    { $$ = $1; }
                 {
                     FuncCall *n = makeFuncCall(SystemFuncName("similar_to_escape"),
                                                list_make1($5),
+                                               COERCE_EXPLICIT_CALL,
                                                @2);
                     $$ = (Node *) makeSimpleA_Expr(AEXPR_SIMILAR, "!~",
                                                    $1, (Node *) n, @2);
@@ -13112,6 +13120,7 @@ a_expr:        c_expr                                    { $$ = $1; }
                 {
                     FuncCall *n = makeFuncCall(SystemFuncName("similar_to_escape"),
                                                list_make2($5, $7),
+                                               COERCE_EXPLICIT_CALL,
                                                @2);
                     $$ = (Node *) makeSimpleA_Expr(AEXPR_SIMILAR, "!~",
                                                    $1, (Node *) n, @2);
@@ -13172,6 +13181,7 @@ a_expr:        c_expr                                    { $$ = $1; }
                                  parser_errposition(@3)));
                     $$ = (Node *) makeFuncCall(SystemFuncName("overlaps"),
                                                list_concat($1, $3),
+                                               COERCE_SQL_SYNTAX,
                                                @2);
                 }
             | a_expr IS TRUE_P                            %prec IS
@@ -13359,19 +13369,33 @@ a_expr:        c_expr                                    { $$ = $1; }
                 }
             | a_expr IS NORMALIZED                                %prec IS
                 {
-                    $$ = (Node *) makeFuncCall(SystemFuncName("is_normalized"), list_make1($1), @2);
+                    $$ = (Node *) makeFuncCall(SystemFuncName("is_normalized"),
+                                               list_make1($1),
+                                               COERCE_SQL_SYNTAX,
+                                               @2);
                 }
             | a_expr IS unicode_normal_form NORMALIZED            %prec IS
                 {
-                    $$ = (Node *) makeFuncCall(SystemFuncName("is_normalized"), list_make2($1, makeStringConst($3,
@3)),@2); 
+                    $$ = (Node *) makeFuncCall(SystemFuncName("is_normalized"),
+                                               list_make2($1, makeStringConst($3, @3)),
+                                               COERCE_SQL_SYNTAX,
+                                               @2);
                 }
             | a_expr IS NOT NORMALIZED                            %prec IS
                 {
-                    $$ = makeNotExpr((Node *) makeFuncCall(SystemFuncName("is_normalized"), list_make1($1), @2), @2);
+                    $$ = makeNotExpr((Node *) makeFuncCall(SystemFuncName("is_normalized"),
+                                                           list_make1($1),
+                                                           COERCE_SQL_SYNTAX,
+                                                           @2),
+                                     @2);
                 }
             | a_expr IS NOT unicode_normal_form NORMALIZED        %prec IS
                 {
-                    $$ = makeNotExpr((Node *) makeFuncCall(SystemFuncName("is_normalized"), list_make2($1,
makeStringConst($4,@4)), @2), @2); 
+                    $$ = makeNotExpr((Node *) makeFuncCall(SystemFuncName("is_normalized"),
+                                                           list_make2($1, makeStringConst($4, @4)),
+                                                           COERCE_SQL_SYNTAX,
+                                                           @2),
+                                     @2);
                 }
             | DEFAULT
                 {
@@ -13621,31 +13645,41 @@ c_expr:        columnref                                { $$ = $1; }

 func_application: func_name '(' ')'
                 {
-                    $$ = (Node *) makeFuncCall($1, NIL, @1);
+                    $$ = (Node *) makeFuncCall($1, NIL,
+                                               COERCE_EXPLICIT_CALL,
+                                               @1);
                 }
             | func_name '(' func_arg_list opt_sort_clause ')'
                 {
-                    FuncCall *n = makeFuncCall($1, $3, @1);
+                    FuncCall *n = makeFuncCall($1, $3,
+                                               COERCE_EXPLICIT_CALL,
+                                               @1);
                     n->agg_order = $4;
                     $$ = (Node *)n;
                 }
             | func_name '(' VARIADIC func_arg_expr opt_sort_clause ')'
                 {
-                    FuncCall *n = makeFuncCall($1, list_make1($4), @1);
+                    FuncCall *n = makeFuncCall($1, list_make1($4),
+                                               COERCE_EXPLICIT_CALL,
+                                               @1);
                     n->func_variadic = true;
                     n->agg_order = $5;
                     $$ = (Node *)n;
                 }
             | func_name '(' func_arg_list ',' VARIADIC func_arg_expr opt_sort_clause ')'
                 {
-                    FuncCall *n = makeFuncCall($1, lappend($3, $6), @1);
+                    FuncCall *n = makeFuncCall($1, lappend($3, $6),
+                                               COERCE_EXPLICIT_CALL,
+                                               @1);
                     n->func_variadic = true;
                     n->agg_order = $7;
                     $$ = (Node *)n;
                 }
             | func_name '(' ALL func_arg_list opt_sort_clause ')'
                 {
-                    FuncCall *n = makeFuncCall($1, $4, @1);
+                    FuncCall *n = makeFuncCall($1, $4,
+                                               COERCE_EXPLICIT_CALL,
+                                               @1);
                     n->agg_order = $5;
                     /* Ideally we'd mark the FuncCall node to indicate
                      * "must be an aggregate", but there's no provision
@@ -13655,7 +13689,9 @@ func_application: func_name '(' ')'
                 }
             | func_name '(' DISTINCT func_arg_list opt_sort_clause ')'
                 {
-                    FuncCall *n = makeFuncCall($1, $4, @1);
+                    FuncCall *n = makeFuncCall($1, $4,
+                                               COERCE_EXPLICIT_CALL,
+                                               @1);
                     n->agg_order = $5;
                     n->agg_distinct = true;
                     $$ = (Node *)n;
@@ -13672,7 +13708,9 @@ func_application: func_name '(' ')'
                      * so that later processing can detect what the argument
                      * really was.
                      */
-                    FuncCall *n = makeFuncCall($1, NIL, @1);
+                    FuncCall *n = makeFuncCall($1, NIL,
+                                               COERCE_EXPLICIT_CALL,
+                                               @1);
                     n->agg_star = true;
                     $$ = (Node *)n;
                 }
@@ -13746,6 +13784,7 @@ func_expr_common_subexpr:
                 {
                     $$ = (Node *) makeFuncCall(SystemFuncName("pg_collation_for"),
                                                list_make1($4),
+                                               COERCE_SQL_SYNTAX,
                                                @1);
                 }
             | CURRENT_DATE
@@ -13812,31 +13851,49 @@ func_expr_common_subexpr:
                 { $$ = makeTypeCast($3, $5, @1); }
             | EXTRACT '(' extract_list ')'
                 {
-                    $$ = (Node *) makeFuncCall(SystemFuncName("date_part"), $3, @1);
+                    $$ = (Node *) makeFuncCall(SystemFuncName("date_part"),
+                                               $3,
+                                               COERCE_SQL_SYNTAX,
+                                               @1);
                 }
             | NORMALIZE '(' a_expr ')'
                 {
-                    $$ = (Node *) makeFuncCall(SystemFuncName("normalize"), list_make1($3), @1);
+                    $$ = (Node *) makeFuncCall(SystemFuncName("normalize"),
+                                               list_make1($3),
+                                               COERCE_SQL_SYNTAX,
+                                               @1);
                 }
             | NORMALIZE '(' a_expr ',' unicode_normal_form ')'
                 {
-                    $$ = (Node *) makeFuncCall(SystemFuncName("normalize"), list_make2($3, makeStringConst($5, @5)),
@1);
+                    $$ = (Node *) makeFuncCall(SystemFuncName("normalize"),
+                                               list_make2($3, makeStringConst($5, @5)),
+                                               COERCE_SQL_SYNTAX,
+                                               @1);
                 }
             | OVERLAY '(' overlay_list ')'
                 {
-                    $$ = (Node *) makeFuncCall(SystemFuncName("overlay"), $3, @1);
+                    $$ = (Node *) makeFuncCall(SystemFuncName("overlay"),
+                                               $3,
+                                               COERCE_SQL_SYNTAX,
+                                               @1);
                 }
             | POSITION '(' position_list ')'
                 {
                     /* position(A in B) is converted to position(B, A) */
-                    $$ = (Node *) makeFuncCall(SystemFuncName("position"), $3, @1);
+                    $$ = (Node *) makeFuncCall(SystemFuncName("position"),
+                                               $3,
+                                               COERCE_SQL_SYNTAX,
+                                               @1);
                 }
             | SUBSTRING '(' substr_list ')'
                 {
                     /* substring(A from B for C) is converted to
                      * substring(A, B, C) - thomas 2000-11-28
                      */
-                    $$ = (Node *) makeFuncCall(SystemFuncName("substring"), $3, @1);
+                    $$ = (Node *) makeFuncCall(SystemFuncName("substring"),
+                                               $3,
+                                               COERCE_SQL_SYNTAX,
+                                               @1);
                 }
             | TREAT '(' a_expr AS Typename ')'
                 {
@@ -13849,28 +13906,41 @@ func_expr_common_subexpr:
                      * Convert SystemTypeName() to SystemFuncName() even though
                      * at the moment they result in the same thing.
                      */
-                    $$ = (Node *) makeFuncCall(SystemFuncName(((Value *)llast($5->names))->val.str),
-                                                list_make1($3),
-                                                @1);
+                    $$ = (Node *) makeFuncCall(SystemFuncName(((Value *) llast($5->names))->val.str),
+                                               list_make1($3),
+                                               COERCE_EXPLICIT_CALL,
+                                               @1);
                 }
             | TRIM '(' BOTH trim_list ')'
                 {
                     /* various trim expressions are defined in SQL
                      * - thomas 1997-07-19
                      */
-                    $$ = (Node *) makeFuncCall(SystemFuncName("btrim"), $4, @1);
+                    $$ = (Node *) makeFuncCall(SystemFuncName("btrim"),
+                                               $4,
+                                               COERCE_SQL_SYNTAX,
+                                               @1);
                 }
             | TRIM '(' LEADING trim_list ')'
                 {
-                    $$ = (Node *) makeFuncCall(SystemFuncName("ltrim"), $4, @1);
+                    $$ = (Node *) makeFuncCall(SystemFuncName("ltrim"),
+                                               $4,
+                                               COERCE_SQL_SYNTAX,
+                                               @1);
                 }
             | TRIM '(' TRAILING trim_list ')'
                 {
-                    $$ = (Node *) makeFuncCall(SystemFuncName("rtrim"), $4, @1);
+                    $$ = (Node *) makeFuncCall(SystemFuncName("rtrim"),
+                                               $4,
+                                               COERCE_SQL_SYNTAX,
+                                               @1);
                 }
             | TRIM '(' trim_list ')'
                 {
-                    $$ = (Node *) makeFuncCall(SystemFuncName("btrim"), $3, @1);
+                    $$ = (Node *) makeFuncCall(SystemFuncName("btrim"),
+                                               $3,
+                                               COERCE_SQL_SYNTAX,
+                                               @1);
                 }
             | NULLIF '(' a_expr ',' a_expr ')'
                 {
@@ -13923,7 +13993,10 @@ func_expr_common_subexpr:
                 {
                     /* xmlexists(A PASSING [BY REF] B [BY REF]) is
                      * converted to xmlexists(A, B)*/
-                    $$ = (Node *) makeFuncCall(SystemFuncName("xmlexists"), list_make2($3, $4), @1);
+                    $$ = (Node *) makeFuncCall(SystemFuncName("xmlexists"),
+                                               list_make2($3, $4),
+                                               COERCE_SQL_SYNTAX,
+                                               @1);
                 }
             | XMLFOREST '(' xml_attribute_list ')'
                 {
@@ -14453,10 +14526,10 @@ extract_arg:
         ;

 unicode_normal_form:
-            NFC                                        { $$ = "nfc"; }
-            | NFD                                    { $$ = "nfd"; }
-            | NFKC                                    { $$ = "nfkc"; }
-            | NFKD                                    { $$ = "nfkd"; }
+            NFC                                        { $$ = "NFC"; }
+            | NFD                                    { $$ = "NFD"; }
+            | NFKC                                    { $$ = "NFKC"; }
+            | NFKD                                    { $$ = "NFKD"; }
         ;

 /* OVERLAY() arguments */
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 7460e61160..ea4a1f5aeb 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -541,10 +541,11 @@ transformRangeFunction(ParseState *pstate, RangeFunction *r)
                 list_length(fc->args) > 1 &&
                 fc->agg_order == NIL &&
                 fc->agg_filter == NULL &&
+                fc->over == NULL &&
                 !fc->agg_star &&
                 !fc->agg_distinct &&
                 !fc->func_variadic &&
-                fc->over == NULL &&
+                fc->funcformat == COERCE_EXPLICIT_CALL &&
                 coldeflist == NIL)
             {
                 ListCell   *lc;
@@ -558,6 +559,7 @@ transformRangeFunction(ParseState *pstate, RangeFunction *r)

                     newfc = makeFuncCall(SystemFuncName("unnest"),
                                          list_make1(arg),
+                                         COERCE_EXPLICIT_CALL,
                                          fc->location);

                     newfexpr = transformExpr(pstate, (Node *) newfc,
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index a7a31704fb..8b4e3ca5e1 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -91,11 +91,12 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
     bool        is_column = (fn == NULL);
     List       *agg_order = (fn ? fn->agg_order : NIL);
     Expr       *agg_filter = NULL;
+    WindowDef  *over = (fn ? fn->over : NULL);
     bool        agg_within_group = (fn ? fn->agg_within_group : false);
     bool        agg_star = (fn ? fn->agg_star : false);
     bool        agg_distinct = (fn ? fn->agg_distinct : false);
     bool        func_variadic = (fn ? fn->func_variadic : false);
-    WindowDef  *over = (fn ? fn->over : NULL);
+    CoercionForm funcformat = (fn ? fn->funcformat : COERCE_EXPLICIT_CALL);
     bool        could_be_projection;
     Oid            rettype;
     Oid            funcid;
@@ -221,6 +222,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
                            agg_order == NIL && agg_filter == NULL &&
                            !agg_star && !agg_distinct && over == NULL &&
                            !func_variadic && argnames == NIL &&
+                           funcformat == COERCE_EXPLICIT_CALL &&
                            list_length(funcname) == 1 &&
                            (actual_arg_types[0] == RECORDOID ||
                             ISCOMPLEX(actual_arg_types[0])));
@@ -742,7 +744,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
         funcexpr->funcresulttype = rettype;
         funcexpr->funcretset = retset;
         funcexpr->funcvariadic = func_variadic;
-        funcexpr->funcformat = COERCE_EXPLICIT_CALL;
+        funcexpr->funcformat = funcformat;
         /* funccollid and inputcollid will be set by parse_collate.c */
         funcexpr->args = fargs;
         funcexpr->location = location;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 015b0538e3..254c0f65c2 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -604,6 +604,7 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
         castnode->location = -1;
         funccallnode = makeFuncCall(SystemFuncName("nextval"),
                                     list_make1(castnode),
+                                    COERCE_EXPLICIT_CALL,
                                     -1);
         constraint = makeNode(Constraint);
         constraint->contype = CONSTR_DEFAULT;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 6c656586e8..407719e494 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -443,6 +443,7 @@ static void get_agg_expr(Aggref *aggref, deparse_context *context,
 static void get_agg_combine_expr(Node *node, deparse_context *context,
                                  void *callback_arg);
 static void get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context);
+static bool get_func_sql_syntax(FuncExpr *expr, deparse_context *context);
 static void get_coercion_expr(Node *arg, deparse_context *context,
                               Oid resulttype, int32 resulttypmod,
                               Node *parentNode);
@@ -9159,7 +9160,8 @@ looks_like_function(Node *node)
     {
         case T_FuncExpr:
             /* OK, unless it's going to deparse as a cast */
-            return (((FuncExpr *) node)->funcformat == COERCE_EXPLICIT_CALL);
+            return (((FuncExpr *) node)->funcformat == COERCE_EXPLICIT_CALL ||
+                    ((FuncExpr *) node)->funcformat == COERCE_SQL_SYNTAX);
         case T_NullIfExpr:
         case T_CoalesceExpr:
         case T_MinMaxExpr:
@@ -9261,6 +9263,17 @@ get_func_expr(FuncExpr *expr, deparse_context *context,
         return;
     }

+    /*
+     * If the function was called using one of the SQL spec's random special
+     * syntaxes, try to reproduce that.  If we don't recognize the function,
+     * fall through.
+     */
+    if (expr->funcformat == COERCE_SQL_SYNTAX)
+    {
+        if (get_func_sql_syntax(expr, context))
+            return;
+    }
+
     /*
      * Normal function: display as proname(args).  First we need to extract
      * the argument datatypes.
@@ -9496,6 +9509,204 @@ get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context)
     }
 }

+/*
+ * get_func_sql_syntax        - Parse back a SQL-syntax function call
+ *
+ * Returns true if we successfully deparsed, false if we did not
+ * recognize the function.
+ */
+static bool
+get_func_sql_syntax(FuncExpr *expr, deparse_context *context)
+{
+    StringInfo    buf = context->buf;
+    Oid            funcoid = expr->funcid;
+
+    switch (funcoid)
+    {
+        case F_TIMESTAMP_IZONE:
+        case F_TIMESTAMP_ZONE:
+        case F_TIMESTAMPTZ_IZONE:
+        case F_TIMESTAMPTZ_ZONE:
+        case F_TIMETZ_IZONE:
+        case F_TIMETZ_ZONE:
+            /* AT TIME ZONE ... note reversed argument order */
+            appendStringInfoChar(buf, '(');
+            get_rule_expr((Node *) lsecond(expr->args), context, false);
+            appendStringInfoString(buf, " AT TIME ZONE ");
+            get_rule_expr((Node *) linitial(expr->args), context, false);
+            appendStringInfoChar(buf, ')');
+            return true;
+
+            /* XXX this fails to cover all built-in "overlaps" functions */
+        case F_OVERLAPS_TIME:
+        case F_OVERLAPS_TIMESTAMP:
+        case F_OVERLAPS_TIMETZ:
+            /* (x1, x2) OVERLAPS (y1, y2) */
+            appendStringInfoString(buf, "((");
+            get_rule_expr((Node *) linitial(expr->args), context, false);
+            appendStringInfoString(buf, ", ");
+            get_rule_expr((Node *) lsecond(expr->args), context, false);
+            appendStringInfoString(buf, ") OVERLAPS (");
+            get_rule_expr((Node *) lthird(expr->args), context, false);
+            appendStringInfoString(buf, ", ");
+            get_rule_expr((Node *) lfourth(expr->args), context, false);
+            appendStringInfoString(buf, "))");
+            return true;
+
+        case F_UNICODE_IS_NORMALIZED:
+            /* IS xxx NORMALIZED */
+            appendStringInfoString(buf, "((");
+            get_rule_expr((Node *) linitial(expr->args), context, false);
+            appendStringInfoString(buf, ") IS");
+            if (list_length(expr->args) == 2)
+            {
+                Const       *con = (Const *) lsecond(expr->args);
+
+                Assert(IsA(con, Const) &&
+                       con->consttype == TEXTOID &&
+                       !con->constisnull);
+                appendStringInfo(buf, " %s",
+                                 TextDatumGetCString(con->constvalue));
+            }
+            appendStringInfoString(buf, " NORMALIZED)");
+            return true;
+
+        case F_PG_COLLATION_FOR:
+            /* COLLATION FOR */
+            appendStringInfoString(buf, "COLLATION FOR (");
+            get_rule_expr((Node *) linitial(expr->args), context, false);
+            appendStringInfoChar(buf, ')');
+            return true;
+
+            /*
+             * XXX EXTRACT, a/k/a date_part(), is intentionally not covered
+             * yet.  Add it after we change the return type to numeric.
+             */
+
+        case F_UNICODE_NORMALIZE_FUNC:
+            /* NORMALIZE() */
+            appendStringInfoString(buf, "NORMALIZE(");
+            get_rule_expr((Node *) linitial(expr->args), context, false);
+            if (list_length(expr->args) == 2)
+            {
+                Const       *con = (Const *) lsecond(expr->args);
+
+                Assert(IsA(con, Const) &&
+                       con->consttype == TEXTOID &&
+                       !con->constisnull);
+                appendStringInfo(buf, ", %s",
+                                 TextDatumGetCString(con->constvalue));
+            }
+            appendStringInfoChar(buf, ')');
+            return true;
+
+        case F_BITOVERLAY:
+        case F_BITOVERLAY_NO_LEN:
+        case F_BYTEAOVERLAY:
+        case F_BYTEAOVERLAY_NO_LEN:
+        case F_TEXTOVERLAY:
+        case F_TEXTOVERLAY_NO_LEN:
+            /* OVERLAY() */
+            appendStringInfoString(buf, "OVERLAY(");
+            get_rule_expr((Node *) linitial(expr->args), context, false);
+            appendStringInfoString(buf, " PLACING ");
+            get_rule_expr((Node *) lsecond(expr->args), context, false);
+            appendStringInfoString(buf, " FROM ");
+            get_rule_expr((Node *) lthird(expr->args), context, false);
+            if (list_length(expr->args) == 4)
+            {
+                appendStringInfoString(buf, " FOR ");
+                get_rule_expr((Node *) lfourth(expr->args), context, false);
+            }
+            appendStringInfoChar(buf, ')');
+            return true;
+
+        case F_BITPOSITION:
+        case F_BYTEAPOS:
+        case F_TEXTPOS:
+            /* POSITION() ... extra parens since args are b_expr not a_expr */
+            appendStringInfoString(buf, "POSITION((");
+            get_rule_expr((Node *) lsecond(expr->args), context, false);
+            appendStringInfoString(buf, ") IN (");
+            get_rule_expr((Node *) linitial(expr->args), context, false);
+            appendStringInfoString(buf, "))");
+            return true;
+
+        case F_BITSUBSTR:
+        case F_BITSUBSTR_NO_LEN:
+        case F_BYTEA_SUBSTR:
+        case F_BYTEA_SUBSTR_NO_LEN:
+        case F_TEXT_SUBSTR:        /* XXX fails, because this is the wrong OID */
+        case F_TEXT_SUBSTR_NO_LEN:    /* XXX fails likewise */
+        case F_TEXTREGEXSUBSTR:
+            /* SUBSTRING FROM/FOR */
+            appendStringInfoString(buf, "SUBSTRING(");
+            get_rule_expr((Node *) linitial(expr->args), context, false);
+            appendStringInfoString(buf, " FROM ");
+            get_rule_expr((Node *) lsecond(expr->args), context, false);
+            if (list_length(expr->args) == 3)
+            {
+                appendStringInfoString(buf, " FOR ");
+                get_rule_expr((Node *) lthird(expr->args), context, false);
+            }
+            appendStringInfoChar(buf, ')');
+            return true;
+
+        case F_BTRIM:
+        case F_BTRIM1:
+        case F_BYTEATRIM:
+            /* TRIM() */
+            appendStringInfoString(buf, "TRIM(BOTH");
+            if (list_length(expr->args) == 2)
+            {
+                appendStringInfoChar(buf, ' ');
+                get_rule_expr((Node *) lsecond(expr->args), context, false);
+            }
+            appendStringInfoString(buf, " FROM ");
+            get_rule_expr((Node *) linitial(expr->args), context, false);
+            appendStringInfoChar(buf, ')');
+            return true;
+
+        case F_LTRIM:
+        case F_LTRIM1:
+            /* TRIM() */
+            appendStringInfoString(buf, "TRIM(LEADING");
+            if (list_length(expr->args) == 2)
+            {
+                appendStringInfoChar(buf, ' ');
+                get_rule_expr((Node *) lsecond(expr->args), context, false);
+            }
+            appendStringInfoString(buf, " FROM ");
+            get_rule_expr((Node *) linitial(expr->args), context, false);
+            appendStringInfoChar(buf, ')');
+            return true;
+
+        case F_RTRIM:
+        case F_RTRIM1:
+            /* TRIM() */
+            appendStringInfoString(buf, "TRIM(TRAILING");
+            if (list_length(expr->args) == 2)
+            {
+                appendStringInfoChar(buf, ' ');
+                get_rule_expr((Node *) lsecond(expr->args), context, false);
+            }
+            appendStringInfoString(buf, " FROM ");
+            get_rule_expr((Node *) linitial(expr->args), context, false);
+            appendStringInfoChar(buf, ')');
+            return true;
+
+        case F_XMLEXISTS:
+            /* XMLEXISTS ... extra parens because args are c_expr */
+            appendStringInfoString(buf, "XMLEXISTS((");
+            get_rule_expr((Node *) linitial(expr->args), context, false);
+            appendStringInfoString(buf, ") PASSING (");
+            get_rule_expr((Node *) lsecond(expr->args), context, false);
+            appendStringInfoString(buf, "))");
+            return true;
+    }
+    return false;
+}
+
 /* ----------
  * get_coercion_expr
  *
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 31d9aedeeb..7ebd794713 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -79,7 +79,8 @@ extern ColumnDef *makeColumnDef(const char *colname,
 extern FuncExpr *makeFuncExpr(Oid funcid, Oid rettype, List *args,
                               Oid funccollid, Oid inputcollid, CoercionForm fformat);

-extern FuncCall *makeFuncCall(List *name, List *args, int location);
+extern FuncCall *makeFuncCall(List *name, List *args,
+                              CoercionForm funcformat, int location);

 extern Expr *make_opclause(Oid opno, Oid opresulttype, bool opretset,
                            Expr *leftop, Expr *rightop,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ff584f2955..34090eddf9 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -353,11 +353,12 @@ typedef struct FuncCall
     List       *args;            /* the arguments (list of exprs) */
     List       *agg_order;        /* ORDER BY (list of SortBy) */
     Node       *agg_filter;        /* FILTER clause, if any */
+    struct WindowDef *over;        /* OVER clause, if any */
     bool        agg_within_group;    /* ORDER BY appeared in WITHIN GROUP */
     bool        agg_star;        /* argument was really '*' */
     bool        agg_distinct;    /* arguments were labeled DISTINCT */
     bool        func_variadic;    /* last argument was labeled VARIADIC */
-    struct WindowDef *over;        /* OVER clause, if any */
+    CoercionForm funcformat;    /* how to display this node */
     int            location;        /* token location, or -1 if unknown */
 } FuncCall;

diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index fd65ee8f9c..5b190bb99b 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -445,7 +445,10 @@ typedef enum CoercionContext
 } CoercionContext;

 /*
- * CoercionForm - how to display a node that could have come from a cast
+ * CoercionForm - how to display a FuncExpr or related node
+ *
+ * "Coercion" is a bit of a misnomer, since this value records other
+ * special syntaxes besides casts, but for now we'll keep this naming.
  *
  * NB: equal() ignores CoercionForm fields, therefore this *must* not carry
  * any semantically significant information.  We need that behavior so that
@@ -457,7 +460,8 @@ typedef enum CoercionForm
 {
     COERCE_EXPLICIT_CALL,        /* display as a function call */
     COERCE_EXPLICIT_CAST,        /* display as an explicit cast */
-    COERCE_IMPLICIT_CAST        /* implicit cast, so hide it */
+    COERCE_IMPLICIT_CAST,        /* implicit cast, so hide it */
+    COERCE_SQL_SYNTAX            /* display with SQL-mandated special syntax */
 } CoercionForm;

 /*
diff --git a/src/test/modules/test_rls_hooks/test_rls_hooks.c b/src/test/modules/test_rls_hooks/test_rls_hooks.c
index 0bfa878a25..c0aaabdcdb 100644
--- a/src/test/modules/test_rls_hooks/test_rls_hooks.c
+++ b/src/test/modules/test_rls_hooks/test_rls_hooks.c
@@ -95,7 +95,10 @@ test_rls_hooks_permissive(CmdType cmdtype, Relation relation)
      */

     n = makeFuncCall(list_make2(makeString("pg_catalog"),
-                                makeString("current_user")), NIL, 0);
+                                makeString("current_user")),
+                     NIL,
+                     COERCE_EXPLICIT_CALL,
+                     -1);

     c = makeNode(ColumnRef);
     c->fields = list_make1(makeString("username"));
@@ -155,7 +158,10 @@ test_rls_hooks_restrictive(CmdType cmdtype, Relation relation)
     policy->roles = construct_array(&role, 1, OIDOID, sizeof(Oid), true, TYPALIGN_INT);

     n = makeFuncCall(list_make2(makeString("pg_catalog"),
-                                makeString("current_user")), NIL, 0);
+                                makeString("current_user")),
+                     NIL,
+                     COERCE_EXPLICIT_CALL,
+                     -1);

     c = makeNode(ColumnRef);
     c->fields = list_make1(makeString("supervisor"));
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index f10a3a7a12..a3a3b12941 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -1710,7 +1710,7 @@ select pg_get_viewdef('tt20v', true);
      i4.i4,                                 +
      i8.i8                                  +
     FROM COALESCE(1, 2) c(c),               +
-     pg_collation_for('x'::text) col(col),  +
+     COLLATION FOR ('x'::text) col(col),    +
      CURRENT_DATE d(d),                     +
      LOCALTIMESTAMP(3) t(t),                +
      CAST(1 + 2 AS integer) i4(i4),         +
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 639b50308e..c300965554 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2726,10 +2726,10 @@ create temp table tmptz (f1 timestamptz primary key);
 insert into tmptz values ('2017-01-18 00:00+00');
 explain (costs off)
 select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
-                                           QUERY PLAN
--------------------------------------------------------------------------------------------------
+                                             QUERY PLAN
+-----------------------------------------------------------------------------------------------------
  Seq Scan on tmptz
-   Filter: (timezone('utc'::text, f1) = 'Wed Jan 18 00:00:00 2017'::timestamp without time zone)
+   Filter: ((f1 AT TIME ZONE 'utc'::text) = 'Wed Jan 18 00:00:00 2017'::timestamp without time zone)
 (2 rows)

 select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';

pgsql-hackers by date:

Previous
From: Fabrízio de Royes Mello
Date:
Subject: Re: Add important info about ANALYZE after create Functional Index
Next
From: Euler Taveira
Date:
Subject: Re: cleanup temporary files after crash