Hi,
Sorry for the delay in response to this. I spent time today looking at
v4 and here are my comments.
1/
v4 captures the timestamp conditionally inside the pgss hook, meaning that
there is an ownership issue with this field. It's a core field, so only
core should set it, and extensions should only read it.
It is best that queryDesc->estate->es_exec_start is set inside ExecutorStart.
```
/* pgss_ExecutorStart (original) */
static void
pgss_ExecutorStart(QueryDesc *queryDesc, int eflags)
{
if (prev_ExecutorStart)
prev_ExecutorStart(queryDesc, eflags);
else
standard_ExecutorStart(queryDesc, eflags);
/*
* Capture the statement start timestamp into EState here after the estate
* has been created by standard_ExecutorStart.
*/
if (pgss_enabled(nesting_level) && queryDesc->plannedstmt->queryId
!= INT64CONST(0))
queryDesc->estate->es_exec_start = GetCurrentStatementStartTimestamp();
}
```
Also, in pgss_ProcessUtility, we should not call
GetCurrentStatementStartTimestamp() directly,
but rather should track an execution start time field in PlannedStmt.
```
@@ -1212,7 +1228,8 @@ pgss_ProcessUtility(PlannedStmt *pstmt, const
char *queryString,
NULL,
0,
0,
- saved_planOrigin);
+ saved_planOrigin,
+ GetCurrentStatementStartTimestamp());
```
PlannedStmt docs already acknowledge it being used this way:
```
* For simplicity in APIs, we also wrap utility statements in PlannedStmt
* nodes; in such cases, commandType == CMD_UTILITY, the statement itself
* is in the utilityStmt field, and the rest of the struct is mostly dummy.
* (We do use canSetTag, stmt_location, stmt_len, and possibly queryId.)
```
Note that ProcessUtility receives a potentially read-only tree when called from
the plan cache (readOnlyTree == true). We need to copyObject before writing
es_exec_start, otherwise we corrupt the cached plan. standard_ProcessUtility
already does this copy internally, but hooks never see it:
```
/*
* If the given node tree is read-only, make a copy to ensure that parse
* transformations don't damage the original tree. This could be
* refactored to avoid making unnecessary copies in more cases, but it's
* not clear that it's worth a great deal of trouble over. Statements
* that are complex enough to be expensive to copy are exactly the ones
* we'd need to copy, so that only marginal savings seem possible.
*/
if (readOnlyTree)
pstmt = copyObject(pstmt);
```
So the copy needs to happen in ProcessUtility() itself, before calling into
hooks:
```
if (readOnlyTree)
pstmt = copyObject(pstmt);
pstmt->es_exec_start = GetCurrentStatementStartTimestamp();
```
Also note that currently pgss_ProcessUtility already sets
pstmt->queryId before calling
through to standard_ProcessUtility, which looks like a latent bug.
```
if (enabled)
pstmt->queryId = INT64CONST(0);
```
Adding TimestampTz to PlannedStmt also requires teaching gen_node_support.pl
about the type.
2/
Also, Why do we need to track last_execution_start time for the planner?
This field is for the last time the query execution start.
```
@@ -958,7 +965,8 @@ pgss_planner(Query *parse,
NULL,
0,
0,
- result->planOrigin);
+ result->planOrigin,
+ GetCurrentStatementStartTimestamp());
```
3/ DDL tests are needed. Right now, we are only testing DML.
4/ To some points made earlier here regarding the test [1]
> +SELECT now() AS ref_ts_ext \gset
> +-- Use \bind \g to force the extended query protocol.
> +SELECT pg_sleep(0.5) AS "DEFERRED_END" \bind \g
> +-- Capture a timestamp *after* the sleep finishes but *before* the next
> +-- extended-protocol statement replaces the unnamed portal.
> +SELECT now() AS ref_ts_ext2 \gset
> +-- The pg_sleep query's last_execution_start should be close to ref_ts_ext
> +-- (before the sleep), NOT to ref_ts_ext2 (after the sleep).
> I think the test doesn't work, or at least, doesn't check what you're
> expecting. If I set last_execution_start to
> GetCurrentStatementStartTimestamp(), it still passes.
correct. The deferred test needs to be done in an explicit transaction
> You could do something like that (and avoid a pg_sleep):
> BEGIN;
> SELECT 1 AS DEFERRED_END, statement_timestamp() AS query_stmt_ts \bind \gset
> -- With an explicit transaction + extended protocol, the portal is left
> opened
> -- ExecutorEnd will only be called when the next command is processed
> END;
That is a better, and much simpler test.
[1]
https://www.postgresql.org/message-id/CAO6_XqoRLxL2%2BFsaE3JbwOH%2BoZ%3DCAjN8yK9-%2Bp7QeH_LY5B%2Bag%40mail.gmail.com
--
Sami Imseih
Amazon Web Services (AWS)