Обсуждение: Re: Possible bug: SQL function parameter in window frame definition

Поиск
Список
Период
Сортировка

Re: Possible bug: SQL function parameter in window frame definition

От
Andrew Gierth
Дата:
[moving to -hackers, removing OP and -general]

>>>>> "Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:

 Tom> Also, in HEAD I'd be inclined to add assertions about utilityStmt
 Tom> being NULL.

Tried this. The assertion is hit:

#3  0x0000000000bb9144 in ExceptionalCondition (conditionName=0xd3c7a9 "query->utilityStmt == NULL", 
    errorType=0xc3da24 "FailedAssertion", fileName=0xd641f8 "nodeFuncs.c", lineNumber=2280) at assert.c:54
#4  0x000000000081268e in query_tree_walker (query=0x80bb34220, walker=0x98d150 <rangeTableEntry_used_walker>, 
    context=0x7fffffffc768, flags=0) at nodeFuncs.c:2280
#5  0x0000000000815a29 in query_or_expression_tree_walker (node=0x80bb34220, walker=0x98d150
<rangeTableEntry_used_walker>,
 
    context=0x7fffffffc768, flags=0) at nodeFuncs.c:3344
#6  0x000000000098d13d in rangeTableEntry_used (node=0x80bb34220, rt_index=1, sublevels_up=0) at rewriteManip.c:900
#7  0x0000000000698ce6 in transformRuleStmt (stmt=0x80241bd20, 
    queryString=0x80241b120 "create rule r3 as on delete to rules_src do notify rules_src_deletion;",
actions=0x7fffffffc968,
 
    whereClause=0x7fffffffc960) at parse_utilcmd.c:2883
#8  0x00000000009819c5 in DefineRule (stmt=0x80241bd20, 
    queryString=0x80241b120 "create rule r3 as on delete to rules_src do notify rules_src_deletion;") at
rewriteDefine.c:206

Any suggestions where best to fix this? transformRuleStmt could be
taught to skip a lot of the per-Query stuff it does in the event that
the Query is actually a NOTIFY, or a check for NOTIFY could be added
further down the stack, e.g. in rangeTableEntry_used. Any preferences?

-- 
Andrew (irc:RhodiumToad)



Re: Possible bug: SQL function parameter in window frame definition

От
Tom Lane
Дата:
Andrew Gierth <andrew@tao11.riddles.org.uk> writes:
> "Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
>  Tom> Also, in HEAD I'd be inclined to add assertions about utilityStmt
>  Tom> being NULL.

> Tried this. The assertion is hit:
> ...

> Any suggestions where best to fix this? transformRuleStmt could be
> taught to skip a lot of the per-Query stuff it does in the event that
> the Query is actually a NOTIFY, or a check for NOTIFY could be added
> further down the stack, e.g. in rangeTableEntry_used. Any preferences?

Hm.  transformRuleStmt already does special-case utility statements to
some extent, so my inclination would be to make it do more of that.
However, it looks like that might end up with rather spaghetti-ish
code, as that function is kind of messy already.

Or we could abandon the notion of adding the assertion.  I don't
know how much work it's worth.

            regards, tom lane



Re: Possible bug: SQL function parameter in window frame definition

От
Andrew Gierth
Дата:
>>>>> "Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:

 Tom> Hm. transformRuleStmt already does special-case utility statements
 Tom> to some extent, so my inclination would be to make it do more of
 Tom> that. However, it looks like that might end up with rather
 Tom> spaghetti-ish code, as that function is kind of messy already.

 Tom> Or we could abandon the notion of adding the assertion. I don't
 Tom> know how much work it's worth.

Fixing transformRuleStmt just pushes the issue along another step:
InsertRule wants to do recordDependencyOnExpr on the rule actions,
which just does find_expr_references_walker.

I'm going to leave the assertion out for now and put in a comment for
future reference.

-- 
Andrew (irc:RhodiumToad)



Re: Possible bug: SQL function parameter in window frame definition

От
Tom Lane
Дата:
Andrew Gierth <andrew@tao11.riddles.org.uk> writes:
> I'm going to leave the assertion out for now and put in a comment for
> future reference.

WFM.  At this point it's clear it would be a separate piece of work
not something to slide into the bug-fix patch, anyway.

            regards, tom lane



Re: Possible bug: SQL function parameter in window frame definition

От
Andrew Gierth
Дата:
>>>>> "Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:

 >> I'm going to leave the assertion out for now and put in a comment
 >> for future reference.

 Tom> WFM. At this point it's clear it would be a separate piece of work
 Tom> not something to slide into the bug-fix patch, anyway.

OK. So here's the final patch.

(For the benefit of anyone in -hackers not following the original thread
in -general, the problem here is that expressions in window framing
clauses were not being walked or mutated by query_tree_walker /
query_tree_mutator. This has been wrong ever since 9.0, but somehow
nobody seems to have noticed until now.)

-- 
Andrew (irc:RhodiumToad)

diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index dd0a7d8dac..03582781f6 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -2214,18 +2214,13 @@ find_expr_references_walker(Node *node,
                                context->addrs);
         }
 
-        /* query_tree_walker ignores ORDER BY etc, but we need those opers */
-        find_expr_references_walker((Node *) query->sortClause, context);
-        find_expr_references_walker((Node *) query->groupClause, context);
-        find_expr_references_walker((Node *) query->windowClause, context);
-        find_expr_references_walker((Node *) query->distinctClause, context);
-
         /* Examine substructure of query */
         context->rtables = lcons(query->rtable, context->rtables);
         result = query_tree_walker(query,
                                    find_expr_references_walker,
                                    (void *) context,
-                                   QTW_IGNORE_JOINALIASES);
+                                   QTW_IGNORE_JOINALIASES |
+                                   QTW_EXAMINE_SORTGROUP);
         context->rtables = list_delete_first(context->rtables);
         return result;
     }
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 18bd5ac903..95051629e2 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -2278,6 +2278,13 @@ query_tree_walker(Query *query,
 {
     Assert(query != NULL && IsA(query, Query));
 
+    /*
+     * We don't walk any utilityStmt here. However, we can't easily assert
+     * that it is absent, since there are at least two code paths by which
+     * action statements from CREATE RULE end up here, and NOTIFY is allowed
+     * in a rule action.
+     */
+
     if (walker((Node *) query->targetList, context))
         return true;
     if (walker((Node *) query->withCheckOptions, context))
@@ -2296,6 +2303,49 @@ query_tree_walker(Query *query,
         return true;
     if (walker(query->limitCount, context))
         return true;
+    /*
+     * Most callers aren't interested in SortGroupClause nodes since those
+     * don't contain actual expressions. However they do contain OIDs which
+     * may be needed by dependency walkers etc.
+     */
+    if ((flags & QTW_EXAMINE_SORTGROUP))
+    {
+        if (walker((Node *) query->groupClause, context))
+            return true;
+        if (walker((Node *) query->windowClause, context))
+            return true;
+        if (walker((Node *) query->sortClause, context))
+            return true;
+        if (walker((Node *) query->distinctClause, context))
+            return true;
+    }
+    else
+    {
+        /*
+         * But we need to walk the expressions under WindowClause nodes even
+         * if we're not interested in SortGroupClause nodes.
+         */
+        ListCell   *lc;
+        foreach(lc, query->windowClause)
+        {
+            WindowClause *wc = lfirst_node(WindowClause, lc);
+            if (walker(wc->startOffset, context))
+                return true;
+            if (walker(wc->endOffset, context))
+                return true;
+        }
+    }
+    /*
+     * groupingSets and rowMarks are not walked:
+     *
+     * groupingSets contain only ressortgrouprefs (integers) which are
+     * meaningless without the corresponding groupClause or tlist.
+     * Accordingly, any walker that needs to care about them needs to handle
+     * them itself in its Query processing.
+     *
+     * rowMarks is not walked because it contains only rangetable indexes (and
+     * flags etc.) and therefore should be handled at Query level similarly.
+     */
     if (!(flags & QTW_IGNORE_CTE_SUBQUERIES))
     {
         if (walker((Node *) query->cteList, context))
@@ -3153,6 +3203,56 @@ query_tree_mutator(Query *query,
     MUTATE(query->havingQual, query->havingQual, Node *);
     MUTATE(query->limitOffset, query->limitOffset, Node *);
     MUTATE(query->limitCount, query->limitCount, Node *);
+
+    /*
+     * Most callers aren't interested in SortGroupClause nodes since those
+     * don't contain actual expressions. However they do contain OIDs, which
+     * may be of interest to some mutators.
+     */
+
+    if ((flags & QTW_EXAMINE_SORTGROUP))
+    {
+        MUTATE(query->groupClause, query->groupClause, List *);
+        MUTATE(query->windowClause, query->windowClause, List *);
+        MUTATE(query->sortClause, query->sortClause, List *);
+        MUTATE(query->distinctClause, query->distinctClause, List *);
+    }
+    else
+    {
+        /*
+         * But we need to mutate the expressions under WindowClause nodes even
+         * if we're not interested in SortGroupClause nodes.
+         */
+        List       *resultlist;
+        ListCell   *temp;
+
+        resultlist = NIL;
+        foreach(temp, query->windowClause)
+        {
+            WindowClause *wc = lfirst_node(WindowClause, temp);
+            WindowClause *newnode;
+
+            FLATCOPY(newnode, wc, WindowClause);
+            MUTATE(newnode->startOffset, wc->startOffset, Node *);
+            MUTATE(newnode->endOffset, wc->endOffset, Node *);
+
+            resultlist = lappend(resultlist, (Node *) newnode);
+        }
+        query->windowClause = resultlist;
+    }
+
+    /*
+     * groupingSets and rowMarks are not mutated:
+     *
+     * groupingSets contain only ressortgroup refs (integers) which are
+     * meaningless without the groupClause or tlist. Accordingly, any mutator
+     * that needs to care about them needs to handle them itself in its Query
+     * processing.
+     *
+     * rowMarks contains only rangetable indexes (and flags etc.) and
+     * therefore should be handled at Query level similarly.
+     */
+
     if (!(flags & QTW_IGNORE_CTE_SUBQUERIES))
         MUTATE(query->cteList, query->cteList, List *);
     else                        /* else copy CTE list as-is */
diff --git a/src/include/nodes/nodeFuncs.h b/src/include/nodes/nodeFuncs.h
index 0cb931c82c..4b5408fa9b 100644
--- a/src/include/nodes/nodeFuncs.h
+++ b/src/include/nodes/nodeFuncs.h
@@ -27,6 +27,7 @@
 #define QTW_EXAMINE_RTES_AFTER        0x20    /* examine RTE nodes after their
                                              * contents */
 #define QTW_DONT_COPY_QUERY            0x40    /* do not copy top Query */
+#define QTW_EXAMINE_SORTGROUP        0x80    /* include SortGroupNode lists */
 
 /* callback function for check_functions_in_node */
 typedef bool (*check_function_callback) (Oid func_id, void *context);
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index edc93d5729..d5fd4045f9 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -3821,3 +3821,45 @@ SELECT i, b, bool_and(b) OVER w, bool_or(b) OVER w
  5 | t | t        | t
 (5 rows)
 
+-- Tests for problems with failure to walk or mutate expressions
+-- within window frame clauses.
+-- test walker (fails with collation error if expressions are not walked)
+SELECT array_agg(i) OVER w
+  FROM generate_series(1,5) i
+WINDOW w AS (ORDER BY i ROWS BETWEEN (('foo' < 'foobar')::integer) PRECEDING AND CURRENT ROW);
+ array_agg 
+-----------
+ {1}
+ {1,2}
+ {2,3}
+ {3,4}
+ {4,5}
+(5 rows)
+
+-- test mutator (fails when inlined if expressions are not mutated)
+CREATE FUNCTION pg_temp.f(group_size BIGINT) RETURNS SETOF integer[]
+AS $$
+    SELECT array_agg(s) OVER w
+      FROM generate_series(1,5) s
+    WINDOW w AS (ORDER BY s ROWS BETWEEN CURRENT ROW AND GROUP_SIZE FOLLOWING)
+$$ LANGUAGE SQL STABLE;
+EXPLAIN (costs off) SELECT * FROM pg_temp.f(2);
+                      QUERY PLAN                      
+------------------------------------------------------
+ Subquery Scan on f
+   ->  WindowAgg
+         ->  Sort
+               Sort Key: s.s
+               ->  Function Scan on generate_series s
+(5 rows)
+
+SELECT * FROM pg_temp.f(2);
+    f    
+---------
+ {1,2,3}
+ {2,3,4}
+ {3,4,5}
+ {4,5}
+ {5}
+(5 rows)
+
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index fc6d4cc903..fe273aa31e 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -1257,3 +1257,22 @@ SELECT to_char(SUM(n::float8) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FO
 SELECT i, b, bool_and(b) OVER w, bool_or(b) OVER w
   FROM (VALUES (1,true), (2,true), (3,false), (4,false), (5,true)) v(i,b)
   WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+
+-- Tests for problems with failure to walk or mutate expressions
+-- within window frame clauses.
+
+-- test walker (fails with collation error if expressions are not walked)
+SELECT array_agg(i) OVER w
+  FROM generate_series(1,5) i
+WINDOW w AS (ORDER BY i ROWS BETWEEN (('foo' < 'foobar')::integer) PRECEDING AND CURRENT ROW);
+
+-- test mutator (fails when inlined if expressions are not mutated)
+CREATE FUNCTION pg_temp.f(group_size BIGINT) RETURNS SETOF integer[]
+AS $$
+    SELECT array_agg(s) OVER w
+      FROM generate_series(1,5) s
+    WINDOW w AS (ORDER BY s ROWS BETWEEN CURRENT ROW AND GROUP_SIZE FOLLOWING)
+$$ LANGUAGE SQL STABLE;
+
+EXPLAIN (costs off) SELECT * FROM pg_temp.f(2);
+SELECT * FROM pg_temp.f(2);

Re: Possible bug: SQL function parameter in window frame definition

От
Tom Lane
Дата:
Andrew Gierth <andrew@tao11.riddles.org.uk> writes:
> OK. So here's the final patch.

> (For the benefit of anyone in -hackers not following the original thread
> in -general, the problem here is that expressions in window framing
> clauses were not being walked or mutated by query_tree_walker /
> query_tree_mutator. This has been wrong ever since 9.0, but somehow
> nobody seems to have noticed until now.)

Two nitpicky suggestions:

* Please run it through pgindent.  Otherwise v13+ are going to be randomly
different from older branches in this area, once we next pgindent HEAD.

* I think you missed s/walk/mutate/ in some of the comments you copied
into query_tree_mutator.

Looks good otherwise.

            regards, tom lane



Re: Possible bug: SQL function parameter in window frame definition

От
Andrew Gierth
Дата:
>>>>> "Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:

 Tom> * Please run it through pgindent. Otherwise v13+ are going to be
 Tom> randomly different from older branches in this area, once we next
 Tom> pgindent HEAD.

gotcha.

 Tom> * I think you missed s/walk/mutate/ in some of the comments you
 Tom> copied into query_tree_mutator.

... where? The only mention of "walk" near query_tree_mutator is in its
header comment, which I didn't touch.

-- 
Andrew (irc:RhodiumToad)



Re: Possible bug: SQL function parameter in window frame definition

От
Tom Lane
Дата:
Andrew Gierth <andrew@tao11.riddles.org.uk> writes:
> "Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
>  Tom> * I think you missed s/walk/mutate/ in some of the comments you
>  Tom> copied into query_tree_mutator.

> ... where? The only mention of "walk" near query_tree_mutator is in its
> header comment, which I didn't touch.

Wup, sorry, I misparsed the patch.  On second read there's no issue there.

            regards, tom lane